From 5f9f76790301d563f3f9e42ecbb3a2faf492e55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Wed, 10 Jan 2024 01:24:52 +0000 Subject: [PATCH 1/7] fix: Fix light client sync miss some tx. (#2992) * refactor: Rename connector to synchronizer * fix: fix light sync logic 1. Use `partial` and `delete` for set_scripts 2. Sync the next block number after all addresses are synced to the block number 3. Rename `blockStartNumber` to `localSavedBlockNumber`, rename `blockEndNumber` to `syncedBlockNumber` --- .../src/components/MultisigAddress/hooks.ts | 2 +- .../neuron-ui/src/services/remote/multisig.ts | 2 +- .../src/block-sync-renderer/index.ts | 2 +- ...exer-connector.ts => full-synchronizer.ts} | 4 +- ...ght-connector.ts => light-synchronizer.ts} | 77 ++++--- .../src/block-sync-renderer/sync/queue.ts | 14 +- .../sync/{connector.ts => synchronizer.ts} | 2 +- .../src/block-sync-renderer/task.ts | 2 +- .../database/chain/entities/sync-progress.ts | 12 +- ...90361215400-ResetSyncProgressPrimaryKey.ts | 8 +- .../1702781527414-RenameSyncProgress.ts | 21 ++ .../src/database/chain/ormconfig.ts | 2 + .../src/models/chain/live-cell.ts | 2 +- .../src/services/live-cell-service.ts | 2 +- .../neuron-wallet/src/services/multisig.ts | 2 +- .../src/services/sync-progress.ts | 25 ++- packages/neuron-wallet/src/utils/ckb-rpc.ts | 2 +- ...ctor.test.ts => full-synchronizer.test.ts} | 20 +- ...tor.test.ts => light-synchronizer.test.ts} | 198 +++++++++--------- .../tests/block-sync-renderer/queue.test.ts | 4 +- ...connector.test.ts => synchronizer.test.ts} | 48 +++-- .../tests/models/chain/live-cell.test.ts | 2 +- .../services/tx/transaction-generator.test.ts | 2 +- 23 files changed, 258 insertions(+), 197 deletions(-) rename packages/neuron-wallet/src/block-sync-renderer/sync/{indexer-connector.ts => full-synchronizer.ts} (95%) rename packages/neuron-wallet/src/block-sync-renderer/sync/{light-connector.ts => light-synchronizer.ts} (80%) rename packages/neuron-wallet/src/block-sync-renderer/sync/{connector.ts => synchronizer.ts} (99%) create mode 100644 packages/neuron-wallet/src/database/chain/migrations/1702781527414-RenameSyncProgress.ts rename packages/neuron-wallet/tests/block-sync-renderer/{indexer-connector.test.ts => full-synchronizer.test.ts} (96%) rename packages/neuron-wallet/tests/block-sync-renderer/{light-connector.test.ts => light-synchronizer.test.ts} (66%) rename packages/neuron-wallet/tests/block-sync-renderer/{connector.test.ts => synchronizer.test.ts} (88%) diff --git a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts index c302ce905e..c2e0613d4b 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts +++ b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts @@ -335,7 +335,7 @@ export const useSubscription = ({ const tmp: Record = {} res.result.forEach(v => { if (hashToPayload[v.hash]) { - tmp[hashToPayload[v.hash]] = v.blockStartNumber + tmp[hashToPayload[v.hash]] = v.localSavedBlockNumber } }) setMultisigSyncProgress(tmp) diff --git a/packages/neuron-ui/src/services/remote/multisig.ts b/packages/neuron-ui/src/services/remote/multisig.ts index 4ac2b4aa2b..79332443d4 100644 --- a/packages/neuron-ui/src/services/remote/multisig.ts +++ b/packages/neuron-ui/src/services/remote/multisig.ts @@ -49,6 +49,6 @@ export const generateMultisigSendAllTx = remoteApi<{ multisigConfig: MultisigConfig }>('generate-multisig-send-all-tx') export const loadMultisigTxJson = remoteApi('load-multisig-tx-json') -export const getMultisigSyncProgress = remoteApi( +export const getMultisigSyncProgress = remoteApi( 'get-sync-progress-by-addresses' ) diff --git a/packages/neuron-wallet/src/block-sync-renderer/index.ts b/packages/neuron-wallet/src/block-sync-renderer/index.ts index f6e0e0ac95..95576c84a7 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/index.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/index.ts @@ -9,7 +9,7 @@ import DataUpdateSubject from '../models/subjects/data-update' import AddressCreatedSubject from '../models/subjects/address-created-subject' import WalletDeletedSubject from '../models/subjects/wallet-deleted-subject' import TxDbChangedSubject from '../models/subjects/tx-db-changed-subject' -import { LumosCellQuery, LumosCell } from './sync/connector' +import { LumosCellQuery, LumosCell } from './sync/synchronizer' import { WorkerMessage, StartParams, QueryIndexerParams } from './task' import logger from '../utils/logger' import CommonUtils from '../utils/common' diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts similarity index 95% rename from packages/neuron-wallet/src/block-sync-renderer/sync/indexer-connector.ts rename to packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts index cec772ad50..972d37e5f0 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-connector.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts @@ -3,11 +3,11 @@ import logger from '../../utils/logger' import CommonUtils from '../../utils/common' import RpcService from '../../services/rpc-service' import { Address } from '../../models/address' -import { Connector } from './connector' +import { Synchronizer } from './synchronizer' import { NetworkType } from '../../models/network' import IndexerCacheService from './indexer-cache-service' -export default class IndexerConnector extends Connector { +export default class FullSynchronizer extends Synchronizer { private rpcService: RpcService constructor(addresses: Address[], nodeUrl: string, indexerUrl: string, nodeType: NetworkType) { diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts similarity index 80% rename from packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts rename to packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts index 9a02faa48d..00ad630bea 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts @@ -4,7 +4,7 @@ import { Address } from '../../models/address' import AddressMeta from '../../database/address/meta' import { scheduler } from 'timers/promises' import SyncProgressService from '../../services/sync-progress' -import { Connector, AppendScript } from './connector' +import { Synchronizer, AppendScript } from './synchronizer' import { computeScriptHash as scriptToHash } from '@ckb-lumos/base/lib/utils' import { FetchTransactionReturnType, LightRPC, LightScriptFilter } from '../../utils/ckb-rpc' import Multisig from '../../services/multisig' @@ -24,7 +24,7 @@ import NetworksService from '../../services/networks' const unpackGroup = molecule.vector(blockchain.OutPoint) -export default class LightConnector extends Connector { +export default class LightSynchronizer extends Synchronizer { private lightRpc: LightRPC private addressMetas: AddressMeta[] @@ -87,7 +87,7 @@ export default class LightConnector extends Connector { private async synchronize() { const syncScripts = await this.upsertTxHashes() await this.updateSyncedBlockOfScripts(syncScripts) - const minSyncBlockNumber = await SyncProgressService.getCurrentWalletMinBlockNumber() + const minSyncBlockNumber = await SyncProgressService.getCurrentWalletMinSyncedBlockNumber() const hasNextBlock = await this.notifyAndSyncNext(minSyncBlockNumber) if (!hasNextBlock) { await this.updateBlockStartNumber(minSyncBlockNumber) @@ -138,7 +138,7 @@ export default class LightConnector extends Connector { const txs = await this.getTransactions({ script: syncScript.script, scriptType: syncScript.scriptType, - blockRange: [BI.from(syncStatus.blockEndNumber).toHexString(), syncScript.blockNumber], + blockRange: [BI.from(syncStatus.syncedBlockNumber).toHexString(), syncScript.blockNumber], }) insertTxCaches.push( ...txs.map(v => ({ @@ -164,7 +164,7 @@ export default class LightConnector extends Connector { syncScripts.forEach(v => { const currentSyncProgress = syncStatusMap.get(scriptToHash(v.script)) if (currentSyncProgress) { - currentSyncProgress.blockEndNumber = parseInt(v.blockNumber) + currentSyncProgress.syncedBlockNumber = parseInt(v.blockNumber) updatedSyncProgress.push(currentSyncProgress) } }) @@ -196,35 +196,42 @@ export default class LightConnector extends Connector { })) }) .flat() - const walletMinBlockNumber = await SyncProgressService.getWalletMinBlockNumber() + const walletMinBlockNumber = await SyncProgressService.getWalletMinLocalSavedBlockNumber() const wallets = await WalletService.getInstance().getAll() const walletStartBlockMap = wallets.reduce>( (pre, cur) => ({ ...pre, [cur.id]: cur.startBlockNumber }), {} ) const otherTypeSyncBlockNumber = await SyncProgressService.getOtherTypeSyncBlockNumber() - const setScriptsParams = [ - ...allScripts.map(v => { - const blockNumber = Math.max( - parseInt(walletStartBlockMap[v.walletId] ?? '0x0'), - walletMinBlockNumber?.[v.walletId] ?? 0, - parseInt(existSyncscripts[scriptToHash(v.script)]?.blockNumber ?? '0x0') - ) - return { + const addScripts = [ + ...allScripts + .filter(v => !existSyncscripts[scriptToHash(v.script)]) + .map(v => { + const blockNumber = Math.max( + parseInt(walletStartBlockMap[v.walletId] ?? '0x0'), + walletMinBlockNumber?.[v.walletId] ?? 0 + ) + return { + ...v, + blockNumber: `0x${blockNumber.toString(16)}`, + } + }), + ...appendScripts + .filter(v => !existSyncscripts[scriptToHash(v.script)]) + .map(v => ({ ...v, - blockNumber: `0x${blockNumber.toString(16)}`, - } - }), - ...appendScripts.map(v => ({ - ...v, - blockNumber: - existSyncscripts[scriptToHash(v.script)]?.blockNumber ?? - `0x${(otherTypeSyncBlockNumber[scriptToHash(v.script)] ?? 0).toString(16)}`, - })), + blockNumber: `0x${(otherTypeSyncBlockNumber[scriptToHash(v.script)] ?? 0).toString(16)}`, + })), ] - await this.lightRpc.setScripts(setScriptsParams) + await this.lightRpc.setScripts(addScripts, 'partial') + const allScriptHashes = new Set([ + ...allScripts.map(v => scriptToHash(v.script)), + ...appendScripts.map(v => scriptToHash(v.script)), + ]) + const deleteScript = syncScripts.filter(v => !allScriptHashes.has(scriptToHash(v.script))) + await this.lightRpc.setScripts(deleteScript, 'delete') const walletIds = [...new Set(this.addressMetas.map(v => v.walletId))] - await SyncProgressService.resetSyncProgress(setScriptsParams) + await SyncProgressService.resetSyncProgress(addScripts) await SyncProgressService.updateSyncProgressFlag(walletIds) await SyncProgressService.removeByHashesAndAddressType( SyncAddressType.Multisig, @@ -281,9 +288,25 @@ export default class LightConnector extends Connector { this.initSyncProgress(scripts) } + private async checkTxExist(txHashes: string[]) { + const transactions = await this.lightRpc + .createBatchRequest<'getTransaction', string[], TransactionWithStatus[]>(txHashes.map(v => ['getTransaction', v])) + .exec() + return transactions.every(v => !!v.transaction) + } + async processTxsInNextBlockNumber() { const [nextBlockNumber, txHashesInNextBlock] = await this.getTxHashesWithNextUnprocessedBlockNumber() - if (nextBlockNumber !== undefined && txHashesInNextBlock.length) { + const minSyncBlockNumber = await SyncProgressService.getCurrentWalletMinSyncedBlockNumber() + if ( + nextBlockNumber !== undefined && + txHashesInNextBlock.length && + // For light client, if tx hash has been called with fetch_transaction, the tx can not return by get_transactions + // So before derived address synced to bigger than next synced block number, do not sync the next block number + minSyncBlockNumber >= parseInt(nextBlockNumber) && + // check whether the tx is sync from light client, after split the light client and full DB file, this check will remove + (await this.checkTxExist(txHashesInNextBlock)) + ) { this.processingBlockNumber = nextBlockNumber await this.fetchPreviousOutputs(txHashesInNextBlock) this.transactionsSubject.next({ txHashes: txHashesInNextBlock, params: this.processingBlockNumber }) @@ -296,7 +319,7 @@ export default class LightConnector extends Connector { } else { return } - const minCachedBlockNumber = await SyncProgressService.getCurrentWalletMinBlockNumber() + const minCachedBlockNumber = await SyncProgressService.getCurrentWalletMinSyncedBlockNumber() await this.updateBlockStartNumber(Math.min(parseInt(blockNumber), minCachedBlockNumber)) this.processNextBlockNumber() } diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts index 36944d7dcc..a09055eb58 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts @@ -12,13 +12,13 @@ import AddressParser from '../../models/address-parser' import Multisig from '../../models/multisig' import BlockHeader from '../../models/chain/block-header' import TxAddressFinder from './tx-address-finder' -import IndexerConnector from './indexer-connector' +import FullSynchronizer from './full-synchronizer' import IndexerCacheService from './indexer-cache-service' import logger from '../../utils/logger' import CommonUtils from '../../utils/common' import { ShouldInChildProcess } from '../../exceptions' -import { AppendScript, BlockTips, Connector } from './connector' -import LightConnector from './light-connector' +import { AppendScript, BlockTips, Synchronizer } from './synchronizer' +import LightSynchronizer from './light-synchronizer' import { generateRPC } from '../../utils/ckb-rpc' import { BUNDLED_LIGHT_CKB_URL } from '../../utils/const' import { NetworkType } from '../../models/network' @@ -30,7 +30,7 @@ export default class Queue { #indexerUrl: string #addresses: AddressInterface[] #rpcService: RpcService - #indexerConnector: Connector | undefined + #indexerConnector: Synchronizer | undefined #checkAndSaveQueue: QueueObject<{ txHashes: CKBComponents.Hash[]; params: unknown }> | undefined #lockArgsSet: Set = new Set() @@ -67,9 +67,9 @@ export default class Queue { logger.info('Queue:\tstart') try { if (this.#url === BUNDLED_LIGHT_CKB_URL) { - this.#indexerConnector = new LightConnector(this.#addresses, this.#url) + this.#indexerConnector = new LightSynchronizer(this.#addresses, this.#url) } else { - this.#indexerConnector = new IndexerConnector(this.#addresses, this.#url, this.#indexerUrl, this.#nodeType) + this.#indexerConnector = new FullSynchronizer(this.#addresses, this.#url, this.#indexerUrl, this.#nodeType) } await this.#indexerConnector!.connect() } catch (error) { @@ -110,7 +110,7 @@ export default class Queue { }) } - getIndexerConnector = (): Connector => this.#indexerConnector! + getIndexerConnector = (): Synchronizer => this.#indexerConnector! stop = () => this.#indexerConnector!.stop() diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts similarity index 99% rename from packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts rename to packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts index fd19a734f2..7b1fd0b4e8 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts @@ -49,7 +49,7 @@ export interface AppendScript { scriptType: CKBRPC.ScriptType } -export abstract class Connector { +export abstract class Synchronizer { public readonly blockTipsSubject: Subject = new Subject() public readonly transactionsSubject = new Subject<{ txHashes: CKBComponents.Hash[]; params: string }>() protected indexer: CkbIndexer diff --git a/packages/neuron-wallet/src/block-sync-renderer/task.ts b/packages/neuron-wallet/src/block-sync-renderer/task.ts index 0706e72e64..b0e0cc6c27 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/task.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/task.ts @@ -1,4 +1,4 @@ -import type { LumosCellQuery } from './sync/connector' +import type { LumosCellQuery } from './sync/synchronizer' import initConnection from '../database/chain/ormconfig' import { register as registerTxStatusListener } from './tx-status-listener' import SyncQueue from './sync/queue' diff --git a/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts b/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts index abacf26067..75229b5ec4 100644 --- a/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts +++ b/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts @@ -28,10 +28,13 @@ export default class SyncProgress { walletId!: string @Column() - blockStartNumber: number = 0 + lightStartBlockNumber: number = 0 @Column() - blockEndNumber: number = 0 + localSavedBlockNumber: number = 0 + + @Column() + syncedBlockNumber: number = 0 @Column({ type: 'varchar' }) cursor?: HexString @@ -58,8 +61,9 @@ export default class SyncProgress { res.scriptType = obj.scriptType res.delete = false res.addressType = obj.addressType ?? SyncAddressType.Default - res.blockStartNumber = parseInt(obj.blockNumber) - res.blockEndNumber = parseInt(obj.blockNumber) + res.lightStartBlockNumber = parseInt(obj.blockNumber) + res.localSavedBlockNumber = parseInt(obj.blockNumber) + res.syncedBlockNumber = parseInt(obj.blockNumber) return res } } diff --git a/packages/neuron-wallet/src/database/chain/migrations/1690361215400-ResetSyncProgressPrimaryKey.ts b/packages/neuron-wallet/src/database/chain/migrations/1690361215400-ResetSyncProgressPrimaryKey.ts index 17bbd1cef2..99c3ab1888 100644 --- a/packages/neuron-wallet/src/database/chain/migrations/1690361215400-ResetSyncProgressPrimaryKey.ts +++ b/packages/neuron-wallet/src/database/chain/migrations/1690361215400-ResetSyncProgressPrimaryKey.ts @@ -1,15 +1,15 @@ import {MigrationInterface, QueryRunner} from "typeorm"; -import SyncProgress from "../entities/sync-progress"; +const chunk = 100 export class ResetSyncProgressPrimaryKey1690361215400 implements MigrationInterface { name = 'ResetSyncProgressPrimaryKey1690361215400' public async up(queryRunner: QueryRunner): Promise { - const syncProgresses = await queryRunner.manager.find(SyncProgress) + const syncProgresses = await queryRunner.manager.query('select * from sync_progress') await queryRunner.query(`DROP TABLE "sync_progress"`) await queryRunner.query(`CREATE TABLE "sync_progress" ("hash" varchar NOT NULL, "args" varchar NOT NULL, "codeHash" varchar NOT NULL, "hashType" varchar NOT NULL, "scriptType" varchar NOT NULL, "walletId" varchar NOT NULL, "blockStartNumber" integer NOT NULL, "blockEndNumber" integer, "cursor" varchar, "delete" boolean, "addressType" integer, PRIMARY KEY ("hash", "walletId"))`) - for (let index = 0; index < syncProgresses.length; index += 500) { - await queryRunner.manager.save(syncProgresses.slice(index, index + 500)) + for (let index = 0; index < syncProgresses.length; index += chunk) { + await queryRunner.manager.query(`INSERT INTO sync_progress VALUES ${syncProgresses.slice(index, index + chunk).reduce((pre: string, cur: any) => `${pre ? `${pre},` : ''}("${cur.hash}","${cur.args}","${cur.codeHash}","${cur.hashType}","${cur.scriptType}","${cur.walletId}",${cur.blockStartNumber},${cur.blockEndNumber},${cur.cursor ? `"${cur.cursor}"` : 'NULL'},${cur.delete},${cur.addressType})`, '')};`) } } diff --git a/packages/neuron-wallet/src/database/chain/migrations/1702781527414-RenameSyncProgress.ts b/packages/neuron-wallet/src/database/chain/migrations/1702781527414-RenameSyncProgress.ts new file mode 100644 index 0000000000..4a6c4b78d8 --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1702781527414-RenameSyncProgress.ts @@ -0,0 +1,21 @@ +import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; + +export class RenameSyncProgress1702781527414 implements MigrationInterface { + name = 'RenameSyncProgress1702781527414' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.renameColumn('sync_progress', 'blockStartNumber', 'localSavedBlockNumber') + await queryRunner.renameColumn('sync_progress', 'blockEndNumber', 'syncedBlockNumber') + await queryRunner.addColumn('sync_progress', new TableColumn({ + name: 'lightStartBlockNumber', + type: 'integer', + default: 0 + })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.renameColumn('sync_progress', 'localSavedBlockNumber', 'blockStartNumber') + await queryRunner.renameColumn('sync_progress', 'syncedBlockNumber', 'blockEndNumber') + } + +} diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index d87ac1cbd0..f7667901a4 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -58,6 +58,7 @@ import { ResetSyncProgressPrimaryKey1690361215400 } from './migrations/169036121 import { TxLockAddArgs1694746034975 } from './migrations/1694746034975-TxLockAddArgs' import { IndexerTxHashCacheRemoveField1701234043431 } from './migrations/1701234043431-IndexerTxHashCacheRemoveField' import { CreateCellLocalInfo1701234043432 } from './migrations/1701234043432-CreateCellLocalInfo' +import { RenameSyncProgress1702781527414 } from './migrations/1702781527414-RenameSyncProgress' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' @@ -132,6 +133,7 @@ const connectOptions = async (genesisBlockHash: string): Promise = { type: ScriptHashType.Type, diff --git a/packages/neuron-wallet/src/services/live-cell-service.ts b/packages/neuron-wallet/src/services/live-cell-service.ts index 8c6694e935..4b7766d3db 100644 --- a/packages/neuron-wallet/src/services/live-cell-service.ts +++ b/packages/neuron-wallet/src/services/live-cell-service.ts @@ -1,7 +1,7 @@ import Script from '../models/chain/script' import LiveCell from '../models/chain/live-cell' import { queryIndexer } from '../block-sync-renderer/index' -import { LumosCell, LumosCellQuery } from '../block-sync-renderer/sync/connector' +import { LumosCell, LumosCellQuery } from '../block-sync-renderer/sync/synchronizer' export default class LiveCellService { private static instance: LiveCellService diff --git a/packages/neuron-wallet/src/services/multisig.ts b/packages/neuron-wallet/src/services/multisig.ts index 922fbf7161..d26b678b17 100644 --- a/packages/neuron-wallet/src/services/multisig.ts +++ b/packages/neuron-wallet/src/services/multisig.ts @@ -270,7 +270,7 @@ export default class MultisigService { .where({ hash: In(multisigScriptHashList) }) .getMany() const syncBlockNumbersMap: Record = syncBlockNumbers.reduce( - (pre, cur) => ({ ...pre, [cur.hash]: cur.blockStartNumber }), + (pre, cur) => ({ ...pre, [cur.hash]: cur.localSavedBlockNumber }), {} ) await getConnection() diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts index 962f5ad287..2d42843bab 100644 --- a/packages/neuron-wallet/src/services/sync-progress.ts +++ b/packages/neuron-wallet/src/services/sync-progress.ts @@ -50,8 +50,8 @@ export default class SyncProgressService { await getConnection() .createQueryBuilder() .update(SyncProgress) - .set({ blockStartNumber: blockNumber }) - .where({ args: In(blake160s), blockStartNumber: LessThan(blockNumber) }) + .set({ localSavedBlockNumber: blockNumber }) + .where({ args: In(blake160s), localSavedBlockNumber: LessThan(blockNumber) }) .execute() } @@ -78,7 +78,7 @@ export default class SyncProgressService { return result } - static async getCurrentWalletMinBlockNumber() { + static async getCurrentWalletMinSyncedBlockNumber() { const currentWallet = WalletService.getInstance().getCurrent() const item = await getConnection() .getRepository(SyncProgress) @@ -88,27 +88,30 @@ export default class SyncProgressService { addressType: SyncAddressType.Default, ...(currentWallet ? { walletId: currentWallet.id } : {}), }) - .orderBy('blockEndNumber', 'ASC') + .orderBy('syncedBlockNumber', 'ASC') .getOne() - return item?.blockEndNumber || 0 + return item?.syncedBlockNumber || 0 } - static async getWalletMinBlockNumber() { + static async getWalletMinLocalSavedBlockNumber() { const items = await getConnection() .getRepository(SyncProgress) .createQueryBuilder() - .select('MIN(blockStartNumber) as blockStartNumber, walletId') + .select('MIN(localSavedBlockNumber) as localSavedBlockNumber, walletId') .where({ addressType: SyncAddressType.Default }) .groupBy('walletId') - .getRawMany<{ blockStartNumber: number; walletId: string }>() - return items.reduce>((pre, cur) => ({ ...pre, [cur.walletId]: cur.blockStartNumber }), {}) + .getRawMany<{ localSavedBlockNumber: number; walletId: string }>() + return items.reduce>( + (pre, cur) => ({ ...pre, [cur.walletId]: cur.localSavedBlockNumber }), + {} + ) } static async getOtherTypeSyncBlockNumber() { const items = await getConnection().getRepository(SyncProgress).find({ addressType: SyncAddressType.Multisig, }) - return items.reduce>((pre, cur) => ({ ...pre, [cur.hash]: cur.blockStartNumber }), {}) + return items.reduce>((pre, cur) => ({ ...pre, [cur.hash]: cur.localSavedBlockNumber }), {}) } static async getSyncProgressByHashes(hashes: string[]) { @@ -123,7 +126,7 @@ export default class SyncProgressService { await getConnection() .createQueryBuilder() .update(SyncProgress) - .set({ blockStartNumber: 0, blockEndNumber: 0 }) + .set({ localSavedBlockNumber: 0, syncedBlockNumber: 0 }) .execute() } } diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts index 0d4c69b4d2..fc33fbefe7 100644 --- a/packages/neuron-wallet/src/utils/ckb-rpc.ts +++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts @@ -144,7 +144,7 @@ export type FetchTransactionReturnType = { } export class LightRPC extends Base { - setScripts: (params: LightScriptFilter[]) => Promise + setScripts: (params: LightScriptFilter[], setScriptCommand: 'all' | 'partial' | 'delete') => Promise getScripts: () => Promise // TODO: the type is not the same as full node here // @ts-ignore diff --git a/packages/neuron-wallet/tests/block-sync-renderer/indexer-connector.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts similarity index 96% rename from packages/neuron-wallet/tests/block-sync-renderer/indexer-connector.test.ts rename to packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts index b79147f525..19a6ef6f18 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/indexer-connector.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts @@ -3,7 +3,7 @@ import { when } from 'jest-when' import { AddressType } from '../../src/models/keys/address' import { Address, AddressVersion } from '../../src/models/address' import SystemScriptInfo from '../../src/models/system-script-info' -import IndexerConnector from '../../src/block-sync-renderer/sync/indexer-connector' +import FullSynchronizer from '../../src/block-sync-renderer/sync/full-synchronizer' import { flushPromises } from '../test-utils' const stubbedTipFn = jest.fn() @@ -15,8 +15,8 @@ const stubbedLoggerErrorFn = jest.fn() const stubbedNextUnprocessedBlock = jest.fn() const stubbedCellCellectFn = jest.fn() -const connectIndexer = async (indexerConnector: IndexerConnector) => { - const connectPromise = indexerConnector.connect() +const connectIndexer = async (synchronizer: FullSynchronizer) => { + const connectPromise = synchronizer.connect() const errSpy = jest.fn() connectPromise.catch(err => { errSpy(err) @@ -27,7 +27,7 @@ const connectIndexer = async (indexerConnector: IndexerConnector) => { describe('unit tests for IndexerConnector', () => { const nodeUrl = 'http://nodeurl:8114' - let stubbedIndexerConnector: any + let stubbedFullSynchronizer: any let stubbedIndexerConstructor: any let stubbedIndexerCacheService: any @@ -76,7 +76,7 @@ describe('unit tests for IndexerConnector', () => { upsertTxHashes: stubbedUpsertTxHashesFn, })) }) - stubbedIndexerConnector = require('../../src/block-sync-renderer/sync/indexer-connector').default + stubbedFullSynchronizer = require('../../src/block-sync-renderer/sync/full-synchronizer').default beforeEach(() => { resetMocks() @@ -91,7 +91,7 @@ describe('unit tests for IndexerConnector', () => { describe('when init with indexer folder path', () => { beforeEach(() => { - new stubbedIndexerConnector([], nodeUrl, STUB_URI) + new stubbedFullSynchronizer([], nodeUrl, STUB_URI) }) it('inits lumos indexer with a node url and indexer folder path', () => { expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl, STUB_URI) @@ -99,7 +99,7 @@ describe('unit tests for IndexerConnector', () => { }) describe('when init without indexer folder path', () => { beforeEach(() => { - new stubbedIndexerConnector([], nodeUrl) + new stubbedFullSynchronizer([], nodeUrl) }) it('inits mercury indexer with a node url and a default port', () => { expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl, STUB_URI) @@ -141,7 +141,7 @@ describe('unit tests for IndexerConnector', () => { blockTimestamp: fakeTx3.transaction.blockTimestamp, } - let indexerConnector: IndexerConnector + let indexerConnector: FullSynchronizer const shortAddressInfo = { lock: SystemScriptInfo.generateSecpScript('0x36c329ed630d6ce750712a477543672adab57f4c'), } @@ -179,7 +179,7 @@ describe('unit tests for IndexerConnector', () => { const addressesToWatch = [addressObj1, addressObj2] beforeEach(() => { - indexerConnector = new stubbedIndexerConnector(addressesToWatch, '', '') + indexerConnector = new stubbedFullSynchronizer(addressesToWatch, '', '') }) describe('polls for new data', () => { describe('#transactionsSubject', () => { @@ -187,7 +187,7 @@ describe('unit tests for IndexerConnector', () => { beforeEach(() => { stubbedTipFn.mockReturnValueOnce(fakeTip1) - indexerConnector = new stubbedIndexerConnector(addressesToWatch, '', '') + indexerConnector = new stubbedFullSynchronizer(addressesToWatch, '', '') transactionsSubject = indexerConnector.transactionsSubject }) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts similarity index 66% rename from packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts rename to packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts index e02ec508e5..4b23436501 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts @@ -1,14 +1,14 @@ import type { Script } from '@ckb-lumos/base' -import LightConnector from '../../src/block-sync-renderer/sync/light-connector' +import LightSynchronizer from '../../src/block-sync-renderer/sync/light-synchronizer' import AddressMeta from '../../src/database/address/meta' const getSyncStatusMock = jest.fn() -const getCurrentWalletMinBlockNumberMock = jest.fn() +const getCurrentWalletMinSyncedBlockNumberMock = jest.fn() const getAllSyncStatusToMapMock = jest.fn() const resetSyncProgressMock = jest.fn() const updateSyncStatusMock = jest.fn() const updateSyncProgressFlagMock = jest.fn() -const getWalletMinBlockNumberMock = jest.fn() +const getWalletMinLocalSavedBlockNumberMock = jest.fn() const removeByHashesAndAddressType = jest.fn() const getOtherTypeSyncProgressMock = jest.fn() const getOtherTypeSyncBlockNumberMock = jest.fn() @@ -26,11 +26,11 @@ const walletGetAllMock = jest.fn() function mockReset() { getSyncStatusMock.mockReset() - getCurrentWalletMinBlockNumberMock.mockReset() + getCurrentWalletMinSyncedBlockNumberMock.mockReset() getAllSyncStatusToMapMock.mockReset() resetSyncProgressMock.mockReset() updateSyncStatusMock.mockReset() - getWalletMinBlockNumberMock.mockReset() + getWalletMinLocalSavedBlockNumberMock.mockReset() getOtherTypeSyncProgressMock.mockReset() getOtherTypeSyncBlockNumberMock.mockReset() @@ -50,12 +50,12 @@ function mockReset() { jest.mock('../../src/services/sync-progress', () => { return class { static getSyncStatus: any = () => getSyncStatusMock() - static getCurrentWalletMinBlockNumber: any = () => getCurrentWalletMinBlockNumberMock() + static getCurrentWalletMinSyncedBlockNumber: any = () => getCurrentWalletMinSyncedBlockNumberMock() static getAllSyncStatusToMap: any = () => getAllSyncStatusToMapMock() static resetSyncProgress: any = (arg: any) => resetSyncProgressMock(arg) static updateSyncStatus: any = (hash: string, update: any) => updateSyncStatusMock(hash, update) static updateSyncProgressFlag: any = (walletIds: string[]) => updateSyncProgressFlagMock(walletIds) - static getWalletMinBlockNumber: any = () => getWalletMinBlockNumberMock() + static getWalletMinLocalSavedBlockNumber: any = () => getWalletMinLocalSavedBlockNumberMock() static removeByHashesAndAddressType: any = (type: number, scripts: Script[]) => removeByHashesAndAddressType(type, scripts) @@ -103,7 +103,7 @@ const script: Script = { // const scriptHash = scriptToHash(script) const address = 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2q8ux5aqem92xnwfmj5cl6e233phlwlysqhjx5w' -describe('test light connector', () => { +describe('test light synchronizer', () => { beforeEach(() => { walletGetAllMock.mockReturnValue([]) createBatchRequestMock.mockResolvedValue([]) @@ -116,21 +116,23 @@ describe('test light connector', () => { describe('test initSyncProgress', () => { it('there is not exist addressmata', async () => { - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') //@ts-ignore await connect.initSyncProgress() expect(getScriptsMock).toBeCalledTimes(0) }) it('append multisig script', async () => { getScriptsMock.mockResolvedValue([]) - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') getOtherTypeSyncBlockNumberMock.mockResolvedValueOnce({}) //@ts-ignore await connect.initSyncProgress([{ walletId: 'walletId', script, addressType: 1, scriptType: 'lock' }]) expect(getScriptsMock).toBeCalledTimes(1) - expect(setScriptsMock).toBeCalledWith([ - { script, scriptType: 'lock', walletId: 'walletId', blockNumber: '0x0', addressType: 1 }, - ]) + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [{ script, scriptType: 'lock', walletId: 'walletId', blockNumber: '0x0', addressType: 1 }], + 'partial' + ) }) it('there is not exist sync scripts with light client', async () => { getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }]) @@ -142,36 +144,29 @@ describe('test light connector', () => { addressType: 0, blake160: script.args, }) - const connect = new LightConnector([addressMeta], '') + const connect = new LightSynchronizer([addressMeta], '') //@ts-ignore await connect.initSyncProgress() - expect(setScriptsMock).toBeCalledWith([ - { - script: addressMeta.generateDefaultLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - { - script: addressMeta.generateACPLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0x0', - }, - { - script: addressMeta.generateLegacyACPLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0x0', - }, - ]) + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + { + script: addressMeta.generateACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + { + script: addressMeta.generateLegacyACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0x0', + }, + ], + 'partial' + ) + expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') expect(resetSyncProgressMock).toBeCalledWith([ - { - script: addressMeta.generateDefaultLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, { script: addressMeta.generateACPLockScript().toSDK(), scriptType: 'lock', @@ -197,30 +192,34 @@ describe('test light connector', () => { addressType: 0, blake160: script.args, }) - getWalletMinBlockNumberMock.mockResolvedValue({ walletId: 170 }) - const connect = new LightConnector([addressMeta], '') + getWalletMinLocalSavedBlockNumberMock.mockResolvedValue({ walletId: 170 }) + const connect = new LightSynchronizer([addressMeta], '') //@ts-ignore await connect.initSyncProgress() - expect(setScriptsMock).toBeCalledWith([ - { - script: addressMeta.generateDefaultLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - { - script: addressMeta.generateACPLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - { - script: addressMeta.generateLegacyACPLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - ]) + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + { + script: addressMeta.generateDefaultLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xaa', + }, + { + script: addressMeta.generateACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xaa', + }, + { + script: addressMeta.generateLegacyACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xaa', + }, + ], + 'partial' + ) }) it('set new script with start block number in wallet', async () => { getScriptsMock.mockResolvedValue([]) @@ -232,37 +231,42 @@ describe('test light connector', () => { addressType: 0, blake160: script.args, }) - getWalletMinBlockNumberMock.mockResolvedValue({}) + getWalletMinLocalSavedBlockNumberMock.mockResolvedValue({}) walletGetAllMock.mockReturnValue([{ id: 'walletId', startBlockNumber: '0xaa' }]) - const connect = new LightConnector([addressMeta], '') + const connect = new LightSynchronizer([addressMeta], '') //@ts-ignore await connect.initSyncProgress() - expect(setScriptsMock).toBeCalledWith([ - { - script: addressMeta.generateDefaultLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - { - script: addressMeta.generateACPLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - { - script: addressMeta.generateLegacyACPLockScript().toSDK(), - scriptType: 'lock', - walletId: 'walletId', - blockNumber: '0xaa', - }, - ]) + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + { + script: addressMeta.generateDefaultLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xaa', + }, + { + script: addressMeta.generateACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xaa', + }, + { + script: addressMeta.generateLegacyACPLockScript().toSDK(), + scriptType: 'lock', + walletId: 'walletId', + blockNumber: '0xaa', + }, + ], + 'partial' + ) + expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') }) }) describe('test initSync', () => { it('pollingIndexer is false', async () => { - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') //@ts-ignore connect.synchronize = jest.fn() //@ts-ignore @@ -270,7 +274,7 @@ describe('test light connector', () => { expect(schedulerWaitMock).toBeCalledTimes(0) }) it('pollingIndexer is true', async () => { - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') schedulerWaitMock.mockImplementation(() => { connect.stop() }) @@ -290,14 +294,14 @@ describe('test light connector', () => { mockFn.mockReset() }) it('connect success', async () => { - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') //@ts-ignore connect.initSync = mockFn await connect.connect() expect(mockFn).toBeCalledTimes(1) }) it('connect failed', async () => { - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') //@ts-ignore connect.initSync = mockFn mockFn.mockImplementation(() => { @@ -309,7 +313,7 @@ describe('test light connector', () => { describe('test stop', () => { it('test stop', () => { - const connect = new LightConnector([], '') + const connect = new LightSynchronizer([], '') //@ts-ignore connect.pollingIndexer = true connect.stop() @@ -319,28 +323,28 @@ describe('test light connector', () => { }) describe('#notifyCurrentBlockNumberProcessed', () => { - const connector = new LightConnector([], '') + const synchronizer = new LightSynchronizer([], '') const updateBlockStartNumberMock = jest.fn() beforeAll(() => { // @ts-ignore private property - connector.updateBlockStartNumber = updateBlockStartNumberMock + synchronizer.updateBlockStartNumber = updateBlockStartNumberMock }) beforeEach(() => { updateBlockStartNumberMock.mockReset() }) it('last process block number finish', async () => { // @ts-ignore private property - connector.processingBlockNumber = '0xaa' - getCurrentWalletMinBlockNumberMock.mockResolvedValueOnce(100) - await connector.notifyCurrentBlockNumberProcessed('0xaa') + synchronizer.processingBlockNumber = '0xaa' + getCurrentWalletMinSyncedBlockNumberMock.mockResolvedValueOnce(100) + await synchronizer.notifyCurrentBlockNumberProcessed('0xaa') // @ts-ignore private property - expect(connector.processingBlockNumber).toBeUndefined() + expect(synchronizer.processingBlockNumber).toBeUndefined() expect(updateBlockStartNumberMock).toBeCalledWith(100) }) it('not last process block number finish', async () => { // @ts-ignore private property - connector.processingBlockNumber = undefined - await connector.notifyCurrentBlockNumberProcessed('0xaa') + synchronizer.processingBlockNumber = undefined + await synchronizer.notifyCurrentBlockNumberProcessed('0xaa') expect(updateBlockStartNumberMock).toBeCalledTimes(0) }) }) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts index 9886c3e881..d181a4ae5c 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts @@ -161,7 +161,7 @@ describe('queue', () => { jest.doMock('utils/logger', () => { return { error: stubbedLoggerErrorFn, info: jest.fn() } }) - jest.doMock('../../src/block-sync-renderer/sync/indexer-connector', () => { + jest.doMock('../../src/block-sync-renderer/sync/full-synchronizer', () => { return stubbedIndexerConnector }) jest.doMock('../../src/block-sync-renderer/sync/tx-address-finder', () => { @@ -274,7 +274,7 @@ describe('queue', () => { message: [fakeWalletId], }) }) - it('notify indexer connector of processed block number', () => { + it('notify full synchronizer of processed block number', () => { expect(stubbedNotifyCurrentBlockNumberProcessedFn).toHaveBeenCalledWith( fakeTxs[0].transaction.blockNumber ) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/connector.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts similarity index 88% rename from packages/neuron-wallet/tests/block-sync-renderer/connector.test.ts rename to packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts index acc7b37354..e3b610763c 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/connector.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts @@ -2,7 +2,7 @@ import { scriptToAddress } from '../../src/utils/scriptAndAddress' import { AddressType } from '../../src/models/keys/address' import { Address, AddressVersion } from '../../src/models/address' import SystemScriptInfo from '../../src/models/system-script-info' -import { Connector, type LumosCell, type LumosCellQuery } from '../../src/block-sync-renderer/sync/connector' +import { Synchronizer, type LumosCell, type LumosCellQuery } from '../../src/block-sync-renderer/sync/synchronizer' import AddressMeta from '../../src/database/address/meta' import IndexerTxHashCache from '../../src/database/chain/entities/indexer-tx-hash-cache' import { ScriptHashType } from '../../src/models/chain/script' @@ -15,7 +15,7 @@ const stubbedCellCollectorConstructor = jest.fn() const stubbedBlockTipsSubscribe = jest.fn() const stubbedCellCellectFn = jest.fn() -class TestConnector extends Connector { +class TestSynchronizer extends Synchronizer { async connect() {} async processTxsInNextBlockNumber(): Promise { return stubbedProcessTxsInNextBlockNumberFn() @@ -113,7 +113,7 @@ describe('unit tests for IndexerConnector', () => { const STUB_URI = 'stub_uri' it('inits lumos indexer with a node url and indexer folder path', () => { - new TestConnector({ + new TestSynchronizer({ addresses: [], nodeUrl, indexerUrl: STUB_URI, @@ -122,18 +122,22 @@ describe('unit tests for IndexerConnector', () => { }) it('init with addresses', () => { - const connector = new TestConnector({ + const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, indexerUrl: STUB_URI, }) - expect(connector.getAddressesByWalletId().get(walletId1)?.[0]).toStrictEqual(AddressMeta.fromObject(addressObj1)) - expect(connector.getAddressesByWalletId().get(walletId2)?.[0]).toStrictEqual(AddressMeta.fromObject(addressObj2)) + expect(synchronizer.getAddressesByWalletId().get(walletId1)?.[0]).toStrictEqual( + AddressMeta.fromObject(addressObj1) + ) + expect(synchronizer.getAddressesByWalletId().get(walletId2)?.[0]).toStrictEqual( + AddressMeta.fromObject(addressObj2) + ) }) }) describe('#getTxHashesWithNextUnprocessedBlockNumber', () => { - const connector = new TestConnector({ + const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, indexerUrl: '', @@ -141,7 +145,7 @@ describe('unit tests for IndexerConnector', () => { it('no cached tx', async () => { stubbedNextUnprocessedTxsGroupedByBlockNumberFn.mockResolvedValue([]) // @ts-ignore private method - const result = await connector.getTxHashesWithNextUnprocessedBlockNumber() + const result = await synchronizer.getTxHashesWithNextUnprocessedBlockNumber() expect(result).toStrictEqual([undefined, []]) }) it('get cached tx and sort by block number', async () => { @@ -167,25 +171,25 @@ describe('unit tests for IndexerConnector', () => { ] ) // @ts-ignore private method - const result = await connector.getTxHashesWithNextUnprocessedBlockNumber() + const result = await synchronizer.getTxHashesWithNextUnprocessedBlockNumber() expect(result).toStrictEqual(['2', ['hash2']]) }) }) describe('#notifyAndSyncNext', () => { - const connector = new TestConnector({ + const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, indexerUrl: '', }) - connector.blockTipsSubject.subscribe(stubbedBlockTipsSubscribe) + synchronizer.blockTipsSubject.subscribe(stubbedBlockTipsSubscribe) it('exist unprocessed block and no current process block', async () => { //@ts-ignore private property - connector.processingBlockNumber = undefined + synchronizer.processingBlockNumber = undefined stubbedNextUnprocessedBlockFn.mockResolvedValue('10') //@ts-ignore private method - await connector.notifyAndSyncNext(100) + await synchronizer.notifyAndSyncNext(100) expect(stubbedBlockTipsSubscribe).toHaveBeenCalledWith({ cacheTipNumber: 10, indexerTipNumber: 100, @@ -194,10 +198,10 @@ describe('unit tests for IndexerConnector', () => { }) it('exist unprocessed block and has current process block', async () => { //@ts-ignore private property - connector.processingBlockNumber = '5' + synchronizer.processingBlockNumber = '5' stubbedNextUnprocessedBlockFn.mockResolvedValue('10') //@ts-ignore private method - await connector.notifyAndSyncNext(100) + await synchronizer.notifyAndSyncNext(100) expect(stubbedBlockTipsSubscribe).toHaveBeenCalledWith({ cacheTipNumber: 10, indexerTipNumber: 100, @@ -206,10 +210,10 @@ describe('unit tests for IndexerConnector', () => { }) it('no unprocessed block', async () => { //@ts-ignore private property - connector.processingBlockNumber = '5' + synchronizer.processingBlockNumber = '5' stubbedNextUnprocessedBlockFn.mockResolvedValue(undefined) //@ts-ignore private method - await connector.notifyAndSyncNext(100) + await synchronizer.notifyAndSyncNext(100) expect(stubbedBlockTipsSubscribe).toHaveBeenCalledWith({ cacheTipNumber: 100, indexerTipNumber: 100, @@ -263,7 +267,7 @@ describe('unit tests for IndexerConnector', () => { } const fakeCells = [fakeCell1, fakeCell2] - const connector = new TestConnector({ + const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, indexerUrl: '', @@ -291,7 +295,7 @@ describe('unit tests for IndexerConnector', () => { ]) //@ts-ignore - cells = await connector.getLiveCellsByScript(query) + cells = await synchronizer.getLiveCellsByScript(query) }) it('transform the query parameter', () => { expect(stubbedCellCollectorConstructor.mock.calls[0][1]).toEqual({ @@ -373,13 +377,13 @@ describe('unit tests for IndexerConnector', () => { const promises = Promise.all([ new Promise(resolve => { - connector.getLiveCellsByScript(query1).then(cells => { + synchronizer.getLiveCellsByScript(query1).then(cells => { results.push(cells) resolve() }) }), new Promise(resolve => { - connector.getLiveCellsByScript(query2).then(cells => { + synchronizer.getLiveCellsByScript(query2).then(cells => { results.push(cells) resolve() }) @@ -399,7 +403,7 @@ describe('unit tests for IndexerConnector', () => { it('throws error', async () => { let err try { - await connector.getLiveCellsByScript({ lock: null, type: null, data: null }) + await synchronizer.getLiveCellsByScript({ lock: null, type: null, data: null }) } catch (error) { err = error } diff --git a/packages/neuron-wallet/tests/models/chain/live-cell.test.ts b/packages/neuron-wallet/tests/models/chain/live-cell.test.ts index 9385e21de1..84a9b97830 100644 --- a/packages/neuron-wallet/tests/models/chain/live-cell.test.ts +++ b/packages/neuron-wallet/tests/models/chain/live-cell.test.ts @@ -1,5 +1,5 @@ import Script, { ScriptHashType } from '../../../src/models/chain/script' -import { LumosCell } from '../../../src/block-sync-renderer/sync/connector' +import { LumosCell } from '../../../src/block-sync-renderer/sync/synchronizer' import LiveCell from '../../../src/models/chain/live-cell' describe('LiveCell Test', () => { diff --git a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts index 46551c7a02..8a87127ffa 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts @@ -87,7 +87,7 @@ import HdPublicKeyInfo from '../../../src/database/chain/entities/hd-public-key- import AssetAccount from '../../../src/models/asset-account' import MultisigConfigModel from '../../../src/models/multisig-config' import MultisigOutput from '../../../src/database/chain/entities/multisig-output' -import { LumosCell } from '../../../src/block-sync-renderer/sync/connector' +import { LumosCell } from '../../../src/block-sync-renderer/sync/synchronizer' describe('TransactionGenerator', () => { beforeAll(async () => { From 8c0d834d27a48e29d5baacc314983826de95ccb8 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Wed, 10 Jan 2024 10:46:32 +0800 Subject: [PATCH 2/7] fix: Actions on receive address is confusing (#2999) * fix: issue 305 * fix: add clearTimeout * fix: AddressQrCodeWithCopyZone * fix: CopyQrCode --------- Co-authored-by: Chen Yu --- .../src/components/Receive/index.tsx | 107 ++++++++++-------- .../components/Receive/receive.module.scss | 36 ++++-- .../components/SUDTReceiveDialog/index.tsx | 45 ++------ .../sUDTReceiveDialog.module.scss | 19 +--- 4 files changed, 95 insertions(+), 112 deletions(-) diff --git a/packages/neuron-ui/src/components/Receive/index.tsx b/packages/neuron-ui/src/components/Receive/index.tsx index 6c02d5eaf1..5387b873d0 100644 --- a/packages/neuron-ui/src/components/Receive/index.tsx +++ b/packages/neuron-ui/src/components/Receive/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback } from 'react' +import React, { useMemo, useCallback, useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useState as useGlobalState } from 'states' import Dialog from 'widgets/Dialog' @@ -7,7 +7,7 @@ import Button from 'widgets/Button' import CopyZone from 'widgets/CopyZone' import QRCode from 'widgets/QRCode' import Tooltip from 'widgets/Tooltip' -import { AddressTransform, Download, Copy, Attention } from 'widgets/Icons/icon' +import { AddressTransform, Download, Copy, Attention, SuccessNoBorder } from 'widgets/Icons/icon' import VerifyHardwareAddress from './VerifyHardwareAddress' import styles from './receive.module.scss' import { useCopyAndDownloadQrCode, useSwitchAddress } from './hooks' @@ -15,42 +15,74 @@ import { useCopyAndDownloadQrCode, useSwitchAddress } from './hooks' type AddressTransformWithCopyZoneProps = { showAddress: string isInShortFormat: boolean - className?: string onClick: () => void } -export const AddressTransformWithCopyZone = ({ +export const AddressQrCodeWithCopyZone = ({ showAddress, isInShortFormat, onClick, - className, }: AddressTransformWithCopyZoneProps) => { const [t] = useTranslation() const transformLabel = t( isInShortFormat ? 'receive.turn-into-full-version-format' : 'receive.turn-into-deprecated-format' ) + const [isCopySuccess, setIsCopySuccess] = useState(false) + const timer = useRef>() + const { ref, onCopyQrCode, onDownloadQrCode, showCopySuccess } = useCopyAndDownloadQrCode() + const stopPropagation = useCallback((e: React.SyntheticEvent) => { e.stopPropagation() }, []) + const onCopy = useCallback(() => { + onCopyQrCode() + setIsCopySuccess(true) + + clearTimeout(timer.current!) + timer.current = setTimeout(() => { + setIsCopySuccess(false) + }, 1000) + }, [showAddress, setIsCopySuccess, timer]) return ( -
- - {showAddress} - - +
+
+ +
+ + {isCopySuccess ? ( + + ) : ( + + )} +
+ {showCopySuccess && } +
+ +
+ + {showAddress} + + +
) } @@ -73,7 +105,6 @@ const Receive = ({ onClose, address }: { onClose?: () => void; address?: string } const { isInShortFormat, setIsInShortFormat, address: showAddress } = useSwitchAddress(accountAddress) - const { ref, onCopyQrCode, onDownloadQrCode, showCopySuccess } = useCopyAndDownloadQrCode() return ( void; address?: string showFooter={false} className={styles.dialog} > -
-
- -
- -
- - -
-
- setIsInShortFormat(is => !is)} - /> -
+
+ setIsInShortFormat(is => !is)} + /> {isSingleAddress && }
- - {showCopySuccess && }
) } diff --git a/packages/neuron-ui/src/components/Receive/receive.module.scss b/packages/neuron-ui/src/components/Receive/receive.module.scss index d07aff7ecc..68b6ab7a56 100644 --- a/packages/neuron-ui/src/components/Receive/receive.module.scss +++ b/packages/neuron-ui/src/components/Receive/receive.module.scss @@ -24,24 +24,41 @@ margin: 4px 0; .actions { + position: absolute; + width: 128px; + height: 128px; + top: 0; + left: 46px; display: flex; justify-content: center; - gap: 16px; - margin-top: 8px; + align-items: center; + gap: 24px; + &:hover { + background: rgba(0, 0, 0, 0.5); + & > button { + display: flex; + align-items: center; + justify-content: center; + } + } & > button { + display: none; min-width: 0; width: 24px; height: 24px; border-radius: 50%; color: var(--primary-color); background: var(--tag-green-bg-color); - & { - svg { - width: 12px; - g, - path { - fill: var(--primary-color); - } + svg { + width: 12px; + } + } + + .actionBtn { + svg { + g, + path { + fill: var(--primary-color); } } } @@ -94,6 +111,7 @@ .copyAddress { width: 424px; + margin-top: 16px; } .showAddress { diff --git a/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx b/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx index 371e2db970..cf9b2915ca 100644 --- a/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx @@ -1,11 +1,9 @@ -import React, { useCallback, useRef, useState } from 'react' +import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { addressToScript, bech32Address, AddressPrefix } from '@nervosnetwork/ckb-sdk-utils' import SUDTAvatar from 'widgets/SUDTAvatar' -import { AddressTransformWithCopyZone } from 'components/Receive' -import QRCode, { copyCanvas, downloadCanvas } from 'widgets/QRCode' +import { AddressQrCodeWithCopyZone } from 'components/Receive' import Dialog from 'widgets/Dialog' -import Button from 'widgets/Button' import Alert from 'widgets/Alert' import { CONSTANTS } from 'utils' @@ -37,24 +35,6 @@ const SUDTReceiveDialog = ({ data, onClose }: { data: DataProps; onClose?: () => const [t] = useTranslation() const [isInShortFormat, setIsInShortFormat] = useState(false) const { address, accountName, tokenName, symbol } = data - const ref = useRef(null) - const onDownloadQrCode = useCallback(() => { - const canvasElement = ref.current?.querySelector('canvas') - if (canvasElement) { - downloadCanvas(canvasElement) - } - }, [ref]) - const [showCopySuccess, setShowCopySuccess] = useState(false) - const onCopyQrCode = useCallback(() => { - setShowCopySuccess(false) - const canvasElement = ref.current?.querySelector('canvas') - if (canvasElement) { - copyCanvas(canvasElement) - setTimeout(() => { - setShowCopySuccess(true) - }, 1) - } - }, [ref]) const displayedAddr = isInShortFormat ? toShortAddr(address) : address @@ -82,21 +62,12 @@ const SUDTReceiveDialog = ({ data, onClose }: { data: DataProps; onClose?: () =>
-
- -
-
- setIsInShortFormat(is => !is)} - /> -
-
-
+ + setIsInShortFormat(is => !is)} + /> ) diff --git a/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss b/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss index 0c8359a35e..0f96d5495a 100644 --- a/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss +++ b/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss @@ -10,6 +10,7 @@ .container { width: 680px; + padding-bottom: 24px; } .info { @@ -75,18 +76,6 @@ } } -.copyContainer { - text-align: center; - display: flex; - width: 100%; - justify-content: center; - margin-top: 16px; - - .copyTransformWrapper { - width: 452px; - } -} - .qrCode { position: relative; @keyframes fade-away { @@ -125,9 +114,3 @@ } } } - -.actions { - margin: 16px 0 24px; - @include dialog-footer; - column-gap: 24px; -} From 1a647368704d6c418f0376b3543fb9baefa9a261 Mon Sep 17 00:00:00 2001 From: Natixe <49234582+Natixe@users.noreply.github.com> Date: Wed, 10 Jan 2024 04:32:52 +0100 Subject: [PATCH 3/7] Adds French 'fr' translation for wallet and UI functions (#3012) Co-authored-by: Chen Yu --- _typos.toml | 2 +- packages/neuron-ui/.storybook/electron.js | 2 +- packages/neuron-ui/src/locales/en.json | 3 +- packages/neuron-ui/src/locales/fr.json | 1191 +++++++++++++++++ packages/neuron-ui/src/locales/zh-tw.json | 3 +- packages/neuron-ui/src/locales/zh.json | 3 +- .../src/tests/calendar/index.test.ts | 29 +- packages/neuron-ui/src/utils/const.ts | 2 +- packages/neuron-ui/src/utils/i18n.ts | 2 + packages/neuron-wallet/src/locales/fr.ts | 303 +++++ packages/neuron-wallet/src/locales/i18n.ts | 2 + .../neuron-wallet/src/services/settings.ts | 2 +- .../tests/services/setting.test.ts | 2 + 13 files changed, 1538 insertions(+), 8 deletions(-) create mode 100644 packages/neuron-ui/src/locales/fr.json create mode 100644 packages/neuron-wallet/src/locales/fr.ts diff --git a/_typos.toml b/_typos.toml index 369e09719d..78683d1960 100644 --- a/_typos.toml +++ b/_typos.toml @@ -6,4 +6,4 @@ numer = "numer" lastest = "lastest" [files] -extend-exclude = ["CHANGELOG.md", "**/migrations/*.ts"] +extend-exclude = ["CHANGELOG.md", "**/migrations/*.ts", "**/fr.{ts,json}"] diff --git a/packages/neuron-ui/.storybook/electron.js b/packages/neuron-ui/.storybook/electron.js index 78003d8100..9978d578ce 100644 --- a/packages/neuron-ui/.storybook/electron.js +++ b/packages/neuron-ui/.storybook/electron.js @@ -1,5 +1,5 @@ const sendSyncValues = { - 'get-locale': 'zh', + 'get-locale': ('zh', 'fr'), 'get-version': '0.103.1', } diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index c66755a57d..e0d6782f8f 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -411,7 +411,8 @@ "en": "English", "en-US": "English(United States)", "zh": "中文(简体)", - "zh-TW": "中文(繁體)" + "zh-TW": "中文(繁體)", + "fr": "Français" }, "data": { "ckb-node-data": "CKB Node Config & Storage", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json new file mode 100644 index 0000000000..e7445298cc --- /dev/null +++ b/packages/neuron-ui/src/locales/fr.json @@ -0,0 +1,1191 @@ +{ + "translation": { + "launch-screen": { + "loading-wallets": "Chargement des Wallets" + }, + "contextmenu": { + "cut": "Couper", + "copy": "Copier", + "paste": "Coller", + "selectall": "Tout sélectionner" + }, + "navbar": { + "overview": "Aperçu", + "wallet": "Wallet", + "send": "Envoyer", + "receive": "Recevoir", + "history": "Historique", + "addresses": "Adresses", + "nervos-dao": "Nervos DAO", + "settings": "Paramètres", + "special-assets": "Actifs personnalisés", + "sync-not-start": "Synchronisation pas encore démarrée", + "connecting": "Connexion", + "experimental-functions": "Expérimental", + "s-udt": "Comptes d'actifs", + "update-neuron-with-ckb": "La version du noeud CKB ne correspond pas à Neuron (v{{ version }}), ce qui peut entraîner des problèmes de compatibilité. Veuillez mettre à jour vers la dernière version de Neuron.", + "ckb-node-compatible": "Le noeud CKB n'est pas compatible avec Neuron (v{{ version }}), veuillez vérifier avant toute opération ultérieure.", + "ckb-without-indexer": "Veuillez ajouter l'option '--indexer' pour démarrer le noeud local" + }, + "network-status": { + "tooltip": { + "block-synced": "Bloc synchronisé", + "looking-valid-target": "Recherche de la cible valide présumée", + "set-start-block-number": "Définir le numéro de bloc de départ" + }, + "migrating": "migration en cours..." + }, + "import-hardware": { + "title": { + "select-model": "Veuillez connecter votre appareil et sélectionner le modèle", + "detect-device": "Détection de l'appareil", + "name-wallet": "Nommer le Wallet", + "choose-address": "Choisissez une adresse de l'appareil" + }, + "actions": { + "confirm": "Veuillez confirmer sur votre appareil...", + "success": "Importation réussie !", + "next": "Suivant", + "cancel": "Annuler", + "back": "Retour", + "rescan": "Rescan", + "close": "fermer", + "finish": "fin de la création" + }, + "errors": { + "multi-device" : "Plusieurs appareils détectés. Un seul appareil du même modèle peut être connecté", + "device-not-found" : "Aucun appareil n'a été détecté. Veuillez connecter votre appareil.", + "ckb-app-not-found" : "L'application CKB ne s'ouvre pas. Veuillez ouvrir l'application CKB sur votre appareil." + }, + "waiting" : "Waiting for CKB app...", + "abort" : "En attente de l'application CKB... Action annulée", + "app-version" : "CKB App v{{version}} en cours d'exécution", + "firmware-version" : "Firmware v{{version}} détecté", + "wallet-name" : "Nom du portefeuille", + "select-model" : "Select Model", + "other-device" : "Autres" + }, + "hardware-sign": { + "annuler" : "Annuler", + "title" : "Signer via le portefeuille matériel", + "device" : "Appareil :", + "status" : { + "label" : "Status :", + "connect" : "Connecté, prêt à signer", + "user-input" : "Connecté, en attente de confirmation sur l'appareil...", + "disconnect" : "Déconnecté, veuillez confirmer que l'appareil est connecté et ouvrir l'application Nervos" + }, + "inputs": "Inputs(signing {{index}} of {{length}})", + "outputs": "Outputs({{length}})", + "actions": { + "close": "Fermer", + "success": "Transaction réussie !", + "rescan": "Rescan" + } + }, + "hardware-verify-address": { + "title": "Verify address via hardware wallet", + "device": "Appareil:", + "address": "Address:", + "verified": "Vérifié", + "invalid": "Invalid", + "status": { + "label" : "Status :", + "connect" : "Connecté, prêt pour la vérification", + "user-input" : "Connecté, en attente de confirmation sur l'appareil...", + "disconnect" : "Déconnecté, veuillez confirmer que l'appareil est connecté et ouvrir l'application Nervos" + }, + "actions": { + "close": "Fermer", + "reconnect": "Reconnecter", + "copy-address": "Copier l'adresse", + "verify": "Vérifier l'adresse", + "finish": "Ok" + } + }, + "offline-sign": { + "title": "Signature hors ligne", + "json-file": "Fichier JSON :", + "status": { + "label": "Statut :", + "unsigned": "Non signé", + "partially-signed": "Partiellement signé", + "signed": "Signé" + }, + "wallet": "Wallet :", + "content": "Contenu :", + "export": "Exporter Tx", + "sign-and-export": "Signer et exporter", + "sign-and-broadcast": "Signer et diffuser", + "actions": { + "broadcast": "Diffuser", + "sign": "Signer et exporter", + "cancel": "Annuler" + } + }, + "overview": { + "date": "Date", + "type": "Type", + "balance": "Solde", + "recent-activities": "Activités récentes", + "no-recent-activities": "Aucune activité récente", + "activity": "Activité", + "datetime": "Date et heure", + "status": "Statut", + "amount": "Montant", + "address": "Adresse", + "sent": "Envoyé", + "sending": "Envoi", + "received": "Reçu", + "receiving": "Réception", + "more": "Plus", + "copy-balance": "Copier le solde", + "create": "Créer un compte d'actif {{name}}", + "destroy": "Détruire le compte d'actif {{name}}", + "createSUDT": "Créer un compte d'actif <0>", + "destroySUDT": "Détruire le compte d'actif <0>", + "wallet-ready": "Le Wallet est prêt, bienvenue", + "send": "Envoyer", + "receive": "Recevoir", + "locked": "Montant verrouillé", + "locked-balance": "Solde verrouillé" + }, + "wizard": { + "welcome-to-nervos-neuron": "Bienvenue sur Neuron", + "create-or-import-your-first-wallet": "Créez ou importez votre premier Wallet", + "create-new-wallet": "Créer un Wallet", + "import-mnemonic": "Importer la graine du Wallet", + "import-keystore": "Importer depuis le fichier Keystore", + "import-wallet": "Importer un Wallet", + "import-hardware-wallet": "Importer un Wallet matériel", + "new-wallet": "Nouveau Wallet", + "wallet-seed": "Graine du Wallet", + "keystore": "Fichier Keystore", + "hardware-wallet": "Wallet matériel", + "next": "Suivant", + "back": "Retour", + "name": "Nom", + "your-wallet-seed-is": "Votre nouvelle graine de Wallet a été générée", + "input-your-seed": "Veuillez entrer votre graine de Wallet", + "password": "Mot de passe", + "confirm-password": "Confirmer le mot de passe", + "set-wallet-name-and-password": "Nommez votre nouveau Wallet et choisissez un mot de passe fort pour le protéger", + "set-wallet-name": "Donnez un nom à votre nouveau Wallet", + "set-a-strong-password-to-protect-your-wallet": "Créez un mot de passe fort pour protéger votre Wallet", + "wallet-suffix": "Wallet {{suffix}}", + "write-down-seed": "Notez votre graine de Wallet et enregistrez-la dans un endroit sûr", + "new-name": "Utilisez un nom inutilisé pour le nouveau Wallet", + "complex-password": "Le mot de passe doit contenir entre 8 et 50 caractères, comprenant au moins trois catégories parmi les suivantes : lettres majuscules, lettres minuscules, chiffres et symboles spéciaux.", + "same-password": "Le mot de passe et la confirmation du mot de passe doivent correspondre", + "input-seed-verify": "Saisissez la graine créée pour vérification", + "no-wallet": "Pas de Wallet ?", + "create-wallet": "Créer un Wallet", + "repeat-password": "Répéter le mot de passe", + "finish-create": "Terminer la création", + "creating-wallet": "Le Wallet est en cours de préparation, veuillez patienter", + "add-one": "Ajouter un autre" + }, + "import-keystore": { + "title": "Importer le fichier Keystore", + "select-file": "Choisir le fichier", + "label": { + "path": "Fichier Keystore", + "name": "Nom du Wallet", + "password": "Mot de passe" + }, + "placeholder": { + "path": "Cliquez pour sélectionner le fichier Keystore", + "name": "Nom pour le nouveau Wallet", + "password": "Mot de passe pour vérifier le fichier Keystore" + }, + "button": { + "back": "Retour", + "submit": "Soumettre" + } + }, + "detail": { + "more-transactions": "Plus" + }, + "send": { + "address": "Envoyer à", + "input-address": "Veuillez saisir l'adresse de réception", + "amount": "Montant (CKB)", + "input-amount": "Veuillez saisir le montant", + "send": "Envoyer", + "reset": "Réinitialiser", + "confirm-password": "Confirmer le mot de passe", + "to": "À", + "add-receiving-address": "Ajouter une adresse de réception", + "input-password-to-confirm": "Saisissez le mot de passe pour confirmer", + "scan-to-get-address": "Scanner le code QR pour lire l'adresse à laquelle envoyer", + "total-amount": "Montant total (CKB)", + "description": "Description", + "description-optional": "Veuillez saisir une description, facultatif", + "balance": "Solde", + "add-one": "Ajouter une autre", + "remove-this": "Supprimer ceci", + "fee": "Frais de transaction (CKB)", + "advanced-fee-settings": "Paramètres avancés des frais", + "price": "Prix", + "pick-price": "Choix rapide du prix", + "custom-price": "Prix personnalisé", + "slow": "Lent", + "standard": "Standard", + "fast": "Rapide", + "total-cycles": "Total des cycles RISC-V", + "scan-screen-qr-code": "Scanner le code QR à l'écran", + "set-locktime": "Définir le verrouillage temporel", + "remove-receiving-address": "Supprimer l'adresse de réception", + "remove-receiving-address-msg": "Êtes-vous sûr de vouloir supprimer l'adresse de réception sélectionnée ?", + "locktime-notice-content": "Selon la hauteur de bloc réelle, il peut y avoir des variations temporelles dans le verrouillage temporel.", + "release-on": "Libération le", + "locktime-warning": "Veuillez vous assurer que le Wallet du destinataire peut prendre en charge le déverrouillage par expiration. (Remarque : 1. Les échanges ne prennent généralement pas en charge le déverrouillage par expiration {{extraNote}})", + "allow-use-sent-cell": "Les sorties non confirmées sont autorisées dans cette transaction.", + "submit-transaction": "Soumettre la transaction" + }, + "receive": { + "title": "Recevoir", + "address-book": "Carnet d'adresses", + "click-to-copy": "Cliquez pour copier l'adresse", + "copy-address": "Copier l'adresse", + "address-not-found": "Adresse non trouvée", + "prompt": "Neuron choisit une nouvelle adresse de réception pour une meilleure confidentialité. Veuillez consulter le carnet d'adresses si vous souhaitez utiliser une adresse de réception précédemment utilisée.", + "address-qrcode": "Code QR de l'adresse", + "address": "Adresse {{network}}", + "verify-address": "Vérifier l'adresse", + "turn-into-full-version-format": "Convertir en format version complète", + "turn-into-deprecated-format": "Convertir en format obsolète", + "save-qr-code": "Télécharger", + "copy-qr-code": "Copier" + }, + "transaction-status": { + "pending": "En attente", + "success": "Succès", + "failed": "Échec", + "confirming": "Confirmation en cours" + }, + "confirmationsCount_one": "{{count}} Confirmation", + "confirmationsCount_other": "{{count}} Confirmations", + "history": { + "title": "Historique", + "title-detail": "Détail de l'historique", + "meta": "Méta", + "type": "Type", + "date": "Date", + "timestamp": "Heure", + "amount": "Montant", + "transaction-hash": "Hash de transaction", + "send": "Envoyer", + "receive": "Recevoir", + "other": "Autre", + "more-actions": "Plus d'actions", + "detail": "Détail", + "explorer": "Explorateur", + "first": "Premier", + "previous": "Précédent", + "next": "Suivant", + "last": "Dernier", + "description": "Description", + "status": "Statut", + "blockNumber": "Numéro de bloc", + "basic-information": "Informations de base", + "search": { + "button": "Rechercher", + "placeholder": "Rechercher le hash de transaction, l'adresse ou la date (aaaa-mm-jj)" + }, + "table": { + "name": "Nom du Wallet", + "type": "Type", + "amount": "Montant", + "timestamp": "Heure", + "status": "Statut", + "operation": "Opération" + }, + "export-history": "Exporter l'historique des transactions", + "confirmationTimes": "Confirmations", + "confirming-with-count": "{{confirmations}} Confirmations", + "view-on-explorer": "Voir sur l'explorateur", + "no-txs": "Aucun historique de transaction", + "view-in-explorer": "Voir sur l'explorateur", + "view-in-explorer-button-title": "Voir sur l'explorateur", + "view-detail": "Détail", + "view-detail-button-title": "Voir les détails", + "opening": "Ouverture", + "copy-tx-hash": "Copier le hash de la transaction", + "copy-balance": "Copier le solde", + "copy-address": "Copier l'adresse", + "create": "Créer le compte d'actif {{name}}", + "destroy": "Détruire le compte d'actif {{name}}" + }, + "transaction": { + "window-title": "Transaction : {{hash}}", + "date": "Date", + "transaction-hash": "Hash de transaction", + "block-number": "Numéro de bloc", + "goBack": "Retour", + "index": "Index", + "address": "Adresse", + "income": "Revenu", + "amount": "Montant", + "inputs": "Entrées", + "outputs": "Sorties", + "cell-from-cellbase": "De cellbase", + "lock-script": "Script de verrouillage", + "lock-script-title": "Informations sur l'adresse", + "deprecated-address-format": "Adresse au format obsolète" + }, + "addresses": { + "title": "Carnet d'adresses", + "addresses": "Adresses", + "type": "Type", + "address": "Adresse", + "identifier": "Identifiant", + "description": "Description", + "balance": "Solde", + "transactions": "TXs", + "receiving-address": "Réception", + "change-address": "Changement", + "copy-address": "Copier l'adresse", + "request-payment": "Demander un paiement", + "view-on-explorer": "Voir sur l'explorateur", + "default-description": "Aucun", + "all-address": "Toutes" + }, + "settings":{ + "title": { + "normal": "Paramètres", + "mac": "Préférences" + }, + "go-to-overview": "Aller à l'aperçu", + "setting-tabs": { + "general": "Général", + "wallets": "Wallets", + "network": "Réseau", + "data": "Data" + }, + "general": { + "show": "Afficher", + "hide": "Masquer", + "version": "Version", + "language": "Langue", + "select-language": "Sélectionner la langue", + "apply": "Appliquer" + }, + "wallet-manager": { + "edit-wallet": { + "wallet-name": "Nom du Wallet", + "password": "Mot de passe", + "new-password": "Nouveau mot de passe", + "confirm-password": "Confirmer le mot de passe", + "edit-wallet": "Éditer le Wallet" + }, + "edit-success": "Informations du Wallet mises à jour avec succès", + "delete-wallet-title": "Veuillez entrer le mot de passe de {{name}}", + "password": "Mot de passe", + "wallet-detail": { + "balance": "Solde" + } + }, + "network": { + "online": "En ligne", + "offline": "Hors ligne", + "add-network": "Ajouter un réseau", + "remove-network": "Supprimer le réseau", + "remove-network-msg": "Êtes-vous sûr de vouloir supprimer le réseau sélectionné ?", + "edit-network": { + "title": "Modifier le réseau", + "rpc-url": "URL RPC", + "name": "Nom du réseau", + "input-rpc": "Veuillez entrer rpc", + "input-network": "Veuillez entrer le nom" + }, + "edit-success": "Modification réussie du réseau", + "mainnet": "Mainnet", + "testnet": "Testnet", + "lightTestnet": "Testnet léger", + "lightMainnet": "Mainnet léger", + "devnet": "Devnet", + "switch-network-type": "Basculer vers {{type}}" + }, + "locale": { + "en": "Anglais", + "en-US": "Anglais (États-Unis)", + "zh": "中文(简体)", + "zh-TW": "中文(繁體)", + "fr": "Français" + }, + "data": { + "ckb-node-data": "Configuration & stockage du noeud CKB", + "set": "Définir", + "cache": "Cache", + "clear-cache-description": "Effacez le cache si vous rencontrez des problèmes de synchronisation des données ou d'affichage du solde. Neuron effectuera une nouvelle analyse des données de bloc.", + "cache-cleared-on": "Cache actualisé le {{date}}", + "refresh": "Actualiser", + "clearing-cache": "Effacement du cache en cours, veuillez patienter", + "clear-success": "Cache effacé", + "set-path": "Définir le chemin", + "remove-ckb-data-tip": "Veuillez déplacer les données du noeud CKB de 【{{prevPath}}】 vers 【{{currentPath}}】 puis cliquer sur \"Les données ont été déplacées\" pour démarrer à partir du bloc synchronisé précédemment ; ou cliquez sur \"Synchroniser depuis le début\" pour démarrer une synchronisation complète.", + "resync-ckb-node-describe": "Attention : Synchroniser depuis le début ne supprimera pas les données du noeud CKB déjà téléchargées.", + "cancel": "Annuler", + "confirm-clear": "Confirmer", + "move-data-finish": "Les données ont été déplacées", + "re-sync": "Synchroniser depuis le début", + "disabled-set-path": "Lorsque le noeud CKB est démarré manuellement, la configuration du chemin du noeud CKB ne prend pas effet.", + "ckb-node-storage": "Stockage du noeud CKB" + } + }, + "password-request": { + "wallet-not-found": "Le Wallet n'a pas été trouvé", + "password": "Entrez le mot de passe", + "placeholder": "Veuillez entrer le mot de passe du Wallet", + "send": { + "title": "Soumettre la transaction" + }, + "send-nft": { + "title": "Envoyer NFT" + }, + "delete": { + "title": "Supprimer le Wallet" + }, + "backup": { + "title": "Sauvegarder le Wallet" + }, + "unlock": { + "title": "Veuillez entrer le mot de passe du Wallet pour réclamer l'actif" + }, + "create-sudt-account": { + "title": "Créer un compte d'actif" + }, + "send-sudt": { + "title": "Envoyer sUDT" + }, + "transfer-to-sudt": { + "title": "Envoyer vers le compte sUDT" + }, + "send-ckb-asset": { + "title": "Envoyer CKB" + }, + "send-acp": { + "title": "Envoyer CKB" + }, + "send-acp-sudt-to-new-cell": { + "title": "Envoyer sUDT" + }, + "send-acp-ckb-to-new-cell": { + "title": "Envoyer CKB" + }, + "migrate-acp": { + "title": "Mettre à niveau les comptes d'actif" + }, + "send-cheque": { + "title": "Envoyer l'actif chèque" + }, + "withdraw-cheque": { + "title": "Retirer l'actif chèque" + }, + "claim-cheque": { + "title": "Réclamer l'actif chèque" + }, + "create-account-to-claim-cheque": { + "title": "Réclamer l'actif chèque avec un nouveau compte" + }, + "destroy-asset-account": { + "title": "Détruire le compte d'actif" + }, + "send-from-multisig": { + "title": "Envoyer du CKB depuis une adresse multisig" + }, + "send-from-multisig-need-one": { + "title": "Envoyer du CKB depuis une adresse multisig" + }, + "xpub-notice": "Pour un Wallet en mode lecture seule, seules les transactions exportées sont prises en charge" + }, + "qrcode": { + "copy": "Copier l'image", + "save": "Enregistrer l'image" + }, + "common": { + "or": "ou", + "ok": "Ok", + "dismiss": "Rejeter", + "confirm": "Confirmer", + "open": "Ouvrir", + "cancel": "Annuler", + "save": "Enregistrer", + "toggle": { + "on": "Activé", + "off": "Désactivé" + }, + "copy-tx-hash": "Copier le hachage de transaction", + "copy-address": "Copier l'adresse", + "select": "Sélectionner", + "backup": "Sauvegarder", + "edit": "Éditer", + "delete": "Supprimer", + "click-to-edit": "Cliquez pour éditer", + "notice": "Avis", + "experimental": "Expérimental", + "close": "Fermer", + "copy": "Copier", + "copied": "Copié", + "verification-failure": "Échec de la vérification", + "back": "Retour", + "switch-to-light": "Passer en mode clair", + "switch-to-dark": "Passer en mode sombre" + }, + "notification-panel": { + "title": "Notifications" + }, + "message-types": { + "warning": "Avertissement", + "alert": "Alerte", + "success": "Succès" + }, + "messages": { + "error": "Erreur", + "unknown-error": "Erreur inconnue", + "update-wallet-successfully": "Le Wallet a été mis à jour avec succès", + "delete-wallet-successfully": "Le Wallet a été supprimé avec succès", + "create-network-successfully": "Le réseau a été créé avec succès", + "update-network-successfully": "Le réseau a été mis à jour avec succès", + "clear-cache-successfully": "Le cache a été effacé avec succès", + "addr-copied": "L'adresse a été copiée dans le presse-papiers", + "qrcode-copied": "Le code QR a été copié dans le presse-papiers", + "view-the-run-node-doc": "Consultez le guide dans le navigateur", + "remain-ckb-for-withdraw": "Il est conseillé de réserver quelques CKBytes pour l'opération de retrait", + "no-valid-addresses-found": "Aucune adresse valide trouvée", + "decimal-range": "La décimale doit être un entier de {{range}} (inclus)", + "experimental-message-hardware": "Il s'agit d'une fonctionnalité expérimentale. Veuillez prêter attention aux risques et l'utiliser avec précaution.", + "experimental-message": "Il s'agit d'une fonctionnalité expérimentale, elle peut changer à tout moment. Veuillez utiliser avec précaution.", + "rebuild-sync": "Pour une meilleure expérience utilisateur, Neuron a adopté un nouveau stockage, nécessitant une migration des données (estimée entre 20 et 60 minutes).\nDésolé pour le désagrément.", + "migrate-warning": "Avertissement : Le processus de migration peut échouer pour des raisons inconnues, entraînant une resynchronisation. Veuillez effectuer une sauvegarde manuelle et lancer la migration !", + "migrate-ckb-data": "Migrer", + "migrate": "Migrer", + "secp256k1/blake160-address-required": "L'adresse Secp256k1/Blake160 est requise", + "light-client-locktime-warning": "2. Le mode client léger ne prend pas en charge l'affichage des CKBytes avec un temps de verrouillage.", + "light-client-cheque-warning": "Avertissement : Le mode client léger ne prend pas en charge l'affichage des actifs Cheque.", + "fields": { + "wallet": "Wallet", + "name": "Nom", + "password": "Mot de passe", + "remote": "URL RPC", + "network": "Réseau", + "address": "Adresse", + "amount": "Montant", + "transaction": "Transaction", + "default-address": "Adresse par défaut", + "mnemonic": "Mnémonique", + "keystore-path": "Fichier de clés", + "keystore-name": "Nom du Wallet", + "keystore-password": "Mot de passe", + "deposit": "Dépôt", + "account-name": "Nom du compte", + "token-name": "Nom du token", + "token-id": "ID du token", + "symbol": "Symbole", + "decimal": "Décimale" + }, + "codes": { + "-3": "", + "100": "Le montant n'est pas suffisant.", + "101": "Le montant {{amount}} CKB est trop petit, veuillez saisir un montant d'au moins {{required}} CKB.", + "102": "$t(messages.fields.{{fieldName}}) est invalide.", + "103": "Le mot de passe $t(messages.fields.keystore-password) est incorrect", + "104": "Échec de la connexion au noeud, veuillez redémarrer Neuron.", + "105": "Vous avez besoin de plus de capacités pour le changement (plus de 61 CKBytes).", + "106": "Vous avez besoin de plus de capacités pour le changement (plus de 61 CKBytes), ou faites glisser jusqu'à la fin pour envoyer tout votre solde.", + "107": "Le montant {{amount}} CKB est trop petit, le transfert avec verrouillage nécessite un montant d'au moins 69 CKB.", + "108": "L'adresse fournie n'appartient pas au Wallet actuel. Veuillez vérifier votre Wallet ou attendre la fin de la synchronisation.", + "109": "Solde insuffisant.", + "110": "Solde disponible insuffisant, veuillez réessayer lorsque la dernière transaction a été confirmée.", + "111": "Le Wallet actuel n'est pas défini.", + "112": "Wallet {{id}} introuvable.", + "113": "La clé de stockage est invalide, veuillez vérifier l'intégrité de votre fichier.", + "114": "Le solde minimal de transfert est de {{bytes}} CKBytes.", + "115": "Vous avez besoin de plus de capacités pour le changement (plus de 61 CKBytes), ou cliquez sur le bouton 'Max' pour envoyer tout votre solde.", + "201": "$t(messages.fields.{{fieldName}}) est requis.", + "202": "$t(messages.fields.{{fieldName}}) est utilisé.", + "203": "$t(messages.fields.{{fieldName}}) doit être plus court ou égal à {{length}}", + "204": "$t(messages.fields.{{fieldName}}) est trop court, il doit être plus long ou égal à {{length}}.", + "205": "$t(messages.fields.{{fieldName}}) {{fieldValue}} est invalide.", + "206": "$t(messages.fields.{{fieldName}}) est invalide, veuillez entrer $t(messages.fields.{{fieldName}}) avec pas plus de {{length}} décimales.", + "207": "$t(messages.fields.{{fieldName}}) {{fieldValue}} est invalide, il ne peut pas être négatif.", + "208": "$t(messages.fields.{{fieldName}}) {{fieldValue}} est invalide, il doit commencer par http(s)://", + "209": "$t(messages.fields.{{fieldName}}) ne doit pas contenir d'espaces.", + "210": "\"{{value}}\" est réservé, veuillez en choisir un autre.", + "211": "Le montant ne peut pas être de 0", + "212": "$t(messages.fields.{{fieldName}}) est trop simple", + "301": "$t(messages.fields.{{fieldName}}) {{fieldValue}} ne peut pas être supprimé.", + "303": "$t(messages.fields.{{fieldName}}) est introuvable.", + "304": "La caméra n'est pas disponible ou désactivée.", + "305": "$t(messages.fields.address) ne peut pas être vide.", + "306": "Veuillez entrer une adresse principale", + "307": "Veuillez entrer une adresse de testnet", + "308": "Le montant n'est pas suffisant", + "309": "Le destinataire doit mettre à niveau son adresse de compte pour accepter davantage de transferts.", + "310": "Veuillez entrer une adresse {{tagName}}", + "402": "L'application CKB n'est pas ouverte. Veuillez ouvrir l'application CKB sur votre appareil.", + "403": "Aucun appareil détecté. Veuillez connecter votre appareil", + "404": "Plusieurs appareils détectés. Un seul appareil du même modèle peut être connecté.", + "600": "Veuillez vous assurer que la synchronisation est finalisée avant d'effectuer toute opération liée à une transaction." + } + }, + "sync": { + "synced": "Synchronisé à 100%", + "syncing": "Synchronisation {{ syncPercents }}", + "block-number": "numéro de bloc", + "syncing-balance": "La balance est en cours de mise à jour", + "slow": "La synchronisation est lente", + "sync-failed": "Échec de la synchronisation, veuillez vérifier le réseau", + "sync-not-start": "La synchronisation n'a pas encore commencé, veuillez essayer de redémarrer le Wallet Neuron", + "connecting": "Connexion en cours" + }, + "pagination": { + "previous-page": "Page précédente", + "next-page": "Page suivante", + "first-page": "Première page", + "last-page": "Dernière page", + "page": "Page", + "selected": "Page actuelle", + "range": "{{start}} - {{end}} de {{count}}", + "page-no": "Page {{pageNo}}" + }, + "nervos-dao": { + "free": "Disponible", + "locked": "Verrouillé", + "deposit": "Dépôt", + "deposit-rules": "Règles de dépôt", + "copy-balance": "Copier le solde", + "deposit-records": "Dépôts", + "completed-records": "Terminé", + "apc": "APC actuel", + "apc-tooltip": "Taux de Compensation Annuel Actuel", + "fee": "Frais de transaction : ", + "deposit-to-nervos-dao": "Déposer dans Nervos DAO", + "withdraw-from-nervos-dao": "Retirer de Nervos DAO", + "cancel": "Annuler", + "proceed": "Procéder", + "compensation": "Compensation", + "notice-wait-time": "Remarque : Vous initiez un retrait et le retrait final devra attendre {{epochs}} époques {{blocks}} blocs (environ {{days}} jours).", + "deposit-dialog-title": "Dépôt", + "deposit-terms": "Nervos DAO nécessite 102 CKBytes pour le stockage de la cellule de dépôt, qui n'est pas rémunéré.
Veuillez consulter le <0>Nervos DAO RFC pour plus d'informations sur Nervos DAO", + "minimal-fee-required": "La capacité minimale de dépôt est de {{minimal}} CKBytes", + "compensation-accumulated": "{{blockNumber}} blocs de compensation accumulée", + "withdraw-alert": "Astuce : il ne reste que {{epochs}} époques (~{{hours}} heures) jusqu'à la fin de votre période de verrouillage actuelle. Si vous souhaitez retirer pendant cette période de verrouillage, veuillez envoyer une demande de retrait à temps. Il reste {{nextLeftEpochs}} époques (~{{days}} jours) jusqu'à la fin de votre prochaine période de verrouillage.", + "balance-not-reserved": "Ne pas réserver de CKBytes pour les futures opérations DAO (non recommandé)", + "deposit-amount": "Montant du dépôt (CKB)", + "deposit-record": { + "deposited-at": "Déposé", + "completed-at": "Terminé", + "deposit-pending": "En attente de dépôt", + "withdraw-action-label": "Retirer", + "unlock-action-label": "Déverrouiller", + "record": "Enregistrement", + "deposited": "Déposé", + "withdrawn": "Retiré", + "unlocked": "Déverrouillé", + "view-tx-detail": "Voir les détails de la transaction", + "locked-period": "Période de verrouillage", + "compensated-period": "Période de compensation", + "days-hours": "{{days}} jours {{hours}} heures", + "no-deposit": "Aucun dépôt", + "no-completed": "Aucun terminé" + }, + "compensation-period": { + "tooltip": { + "compensated-period": "Période de compensation", + "days-hours": "{{days}} jours {{hours}} heures", + "immature-for-withdraw": "Il faut au moins environ 16 heures (4 époques) pour procéder à la prochaine action", + "normal": "Le montant de la compensation est faible, le retrait n'est pas recommandé", + "suggested": "Période de retrait suggérée pour une compensation maximisée", + "ending": "Le retrait demandé peut être reporté et verrouillé pour une période supplémentaire de 30 jours", + "withdrawn": "Retiré" + }, + "stage-messages": { + "pending": "En attente...", + "immature-for-withdraw": "Les CKB ne peuvent pas être retirés pendant les {{ hours }} prochaines heures", + "immature-for-unlock": "Les CKB ne peuvent pas être déverrouillés pendant les {{ hours }} prochaines heures", + "next-compensation-cycle": "Le prochain cycle de compensation commencera dans environ {{days}} jours", + "withdrawing": "Retrait en cours...", + "compensation-cycle-will-end": "La période de compensation se termine dans environ {{days}} jours", + "compensation-cycle-has-ended": "Le cycle est terminé, les CKB sont prêts à être déverrouillés", + "unlocking": "Déverrouillage en cours..." + } + } + }, + "nervos-dao-detail": { + "tx-detail": "Détail de la transaction", + "deposited": "Déposé", + "withdrawn": "Retiré", + "unlocked": "Déverrouillé", + "basic-information": "Informations de base", + "transaction-hash": "Hachage de la transaction", + "blockNumber": "Numéro de bloc", + "datetime": "Date et heure", + "income": "Revenu", + "index": "Index", + "address": "Adresse", + "amount": "Montant", + "cell-from-cellbase": "De cellbase", + "cancel": "Annuler", + "next": "Suivant" + }, + "lock-info-dialog": { + "address-info": "Informations sur l'adresse", + "deprecated-address": "Adresse obsolète" + }, + "updates": { + "check-updates": "Vérifier les mises à jour", + "checking-updates": "Vérification en cours...", + "update-available": "Des mises à jour sont actuellement disponibles.", + "downloading-update": "Téléchargement de la mise à jour...", + "update-not-available": "Aucune mise à jour n'est actuellement disponible.", + "updates-found-do-you-want-to-update": "Une mise à jour ({{version}}) est disponible", + "install-update": "Mise à jour immédiate", + "updates-downloaded-about-to-quit-and-install": "Mise à jour téléchargée. Prêt à installer et relancer.", + "quit-and-install": "Installer et relancer" + }, + "datetime": { + "mon": { + "full": "Lundi", + "short": "Lun.", + "tag": "L" + }, + "tue": { + "full": "Mardi", + "short": "Mar.", + "tag": "M" + }, + "wed": { + "full": "Mercredi", + "short": "Mer.", + "tag": "M" + }, + "thur": { + "full": "Jeudi", + "short": "Jeu.", + "tag": "J" + }, + "fri": { + "full": "Vendredi", + "short": "Ven.", + "tag": "V" + }, + "sat": { + "full": "Samedi", + "short": "Sam.", + "tag": "S" + }, + "sun": { + "full": "Dimanche", + "short": "Dim.", + "tag": "D" + }, + "jan": { + "full": "Janvier", + "short": "Jan.", + "tag": "J" + }, + "feb": { + "full": "Février", + "short": "Fév.", + "tag": "F" + }, + "mar": { + "full": "Mars", + "short": "Mar.", + "tag": "M" + }, + "apr": { + "full": "Avril", + "short": "Avr.", + "tag": "A" + }, + "may": { + "full": "Mai", + "short": "Mai.", + "tag": "M" + }, + "june": { + "full": "Juin", + "short": "Jui.", + "tag": "J" + }, + "july": { + "full": "Juillet", + "short": "Juil.", + "tag": "J" + }, + "aug": { + "full": "Août", + "short": "Aoû.", + "tag": "A" + }, + "sept": { + "full": "Septembre", + "short": "Sep.", + "tag": "S" + }, + "oct": { + "full": "Octobre", + "short": "Oct.", + "tag": "O" + }, + "nov": { + "full": "Novembre", + "short": "Nov.", + "tag": "N" + }, + "dec": { + "full": "Décembre", + "short": "Déc.", + "tag": "D" + }, + "timezone": "Fuseau horaire", + "previous-month": "mois précédent", + "next-month": "mois suivant", + "start-tomorrow": "L'heure sélectionnée devrait commencer à partir de demain." + }, + "sign-and-verify": { + "window-title": "Signer ou Vérifier le message", + "sign-or-verify-message": "Signer ou Vérifier le message", + "message": "Message", + "address": "Adresse", + "signature": "Signature", + "cancel": "Annuler", + "sign": "Signer", + "verify": "Vérifier", + "password": "Mot de passe", + "confirm": "Confirmer", + "verification-success": "Vérification réussie", + "verification-failure": "Échec de la vérification", + "address-not-found": "L'adresse fournie n'appartient pas au Wallet actuel. Veuillez vérifier votre Wallet ou attendre la fin de la synchronisation.", + "sign-with-magic-byte": "Le message sera signé avec les octets magiques 'Nervos Message:'", + "verify-tip": "Peut être vérifié par Neuron à partir de la version 0.33.1", + "verify-old-sign-success": "Le message a été signé par Neuron avant la version 0.33.1", + "input-message": "Veuillez saisir le message", + "input-choose-address": "Veuillez saisir ou choisir une adresse", + "input-password": "Veuillez saisir le mot de passe" + }, + "special-assets": { + "title": "Actifs personnalisés", + "date": "Date", + "assets": "Actifs", + "type-script-tooltip": "Type Script", + "data-tooltip": "Données", + "user-defined-asset": "Actif non répertorié", + "user-defined-asset-tooltip": "Il s'agit d'un actif non répertorié. Neuron ne peut pas le traiter pour le moment. Veuillez utiliser un logiciel tiers pour vérifier.", + "locked-asset": "Verrouillé", + "locked-asset-tooltip": "Le paramètre de verrouillage est de {{epochs}} époques, la date de libération estimée est le {{year}}-{{month}}-{{day}} (selon la hauteur de bloc réelle en cours d'exécution, il peut y avoir des variances de temps dans le verrouillage).", + "withdraw-asset-tooltip": "L'heure de libération estimée est le {{year}}-{{month}}-{{day}} à {{hour}}:{{minute}} (selon la hauteur de bloc réelle en cours d'exécution).", + "user-defined-token-tooltip": "Migrer l'actif sUDT vers un compte d'actif sUDT", + "claim-asset": "Réclamation", + "withdraw-asset": "Retrait", + "view-details": "Voir les détails", + "release-success": "L'actif a été libéré, consultez les détails dans l'historique.", + "no-special-assets": "Aucun actif personnalisé", + "experimental": "Expérimental", + "unknown-asset": "Actif inconnu", + "transfer-nft": "Envoyer", + "user-defined-token": "Migrer", + "transfer-nft-success": "Transfert d'actifs réussi", + "migrate-sudt-success": "Conversion en nouveau compte d'actif sUDT réussie", + "send-sudt-success": "Transfert vers le compte sUDT réussi", + "unlock-success": "Actif réclamé, consultez l'historique des transactions pour plus de détails", + "withdraw-cheque-success": "Actif retiré, consultez l'historique des transactions pour plus de détails", + "claim-cheque-success": "Actif réclamé, consultez l'historique des transactions pour plus de détails" + }, + "migrate-sudt": { + "title": "Migrer vers un compte d'actif sUDT", + "choose-title": "Mode de migration", + "next": "Suivant", + "back": "Retour", + "input-token": "Veuillez saisir le nom du token", + "input-symbol": "Veuillez saisir le symbole", + "input-decimal": "Veuillez saisir la décimale", + "turn-into-new-account": { + "title": "Transformez en un nouveau compte d'actif sUDT", + "sub-title": "Transformez l'actif sUDT en un nouveau compte sUDT, occupe au moins 142 CKBytes", + "cancel": "Annuler", + "confirm": "Confirmer" + }, + "transfer-to-exist-account": { + "title": "Transférer vers un compte d'actif sUDT", + "sub-title": "Transférez tout le solde sUDT vers un compte sUDT existant, assurez-vous que le compte cible est actif" + }, + "cancel": "Annuler", + "confirm": "Confirmer", + "balance": "Solde", + "amount": "Montant", + "address": "Adresse" + }, + "s-udt": { + "edit-account-success": "Modification des informations du compte réussie", + "create-account-success": "Création réussie du compte d'actif", + "account-list": { + "title": "Comptes d'actifs", + "no-asset-accounts": "Aucun actif sUDT n'a été détecté", + "search": "Nom du compte, Nom du token ou Symbole", + "syncing": "Synchronisation en cours, les données actuelles peuvent être inexactes", + "send": "Envoyer", + "receive": "Recevoir", + "set-account-info": "Définir les informations du compte" + }, + "create-dialog": { + "create-asset-account": "Créer un compte d'actif", + "input-account-name": "Veuillez saisir le nom du compte", + "select-account-type": "Sélectionner le type de compte", + "account-name": "Nom du compte", + "sudt-account": "Compte sUDT", + "delete-failed": "Échec de la suppression de la configuration multisig, raison de l'échec : {{reason}}", + "ckb-account": "Compte CKB", + "set-token-info": "Définir les informations du token", + "token-id": "Identifiant du token", + "token-name": "Nom du token", + "symbol": "Symbole", + "decimal": "Décimale", + "confirm": "Confirmer", + "cancel": "Annuler", + "next": "Suivant", + "back": "Retour", + "occupy-142-ckb": "Occupe au moins 142 CKBytes", + "occupy-61-ckb": "Occupe au moins 61 CKBytes", + "input": { + "account-name": "Veuillez saisir le nom du compte", + "token-id": "Veuillez saisir l'identifiant du token", + "token-name": "Veuillez saisir le nom du token", + "symbol": "Veuillez saisir le symbole", + "decimal": "Veuillez saisir la décimale" + }, + "placeholder": { + "token-id": "L'ID du token est l'argument du script de type sUDT, qui est identique au hachage de verrouillage de l'émetteur du token." + } + }, + "send": { + "title": "Envoyer", + "address": "Envoyer à", + "address-placeholder": "Veuillez entrer l'adresse", + "input-address": "Veuillez entrer l'adresse", + "input-description": "Veuillez entrer la description, facultatif", + "amount": "Montant", + "amount-placeholder": "Veuillez entrer le montant", + "description": "Description", + "description-placeholder": "Veuillez entrer la description, facultatif", + "submit": "Soumettre", + "click-to-edit": "Cliquez pour éditer", + "cheque-address-hint": { + "label": "Verrouiller temporairement 162 CKBytes", + "tooltip": "162 CKBytes seront temporairement verrouillés pour initialiser le transfert de token, et seront automatiquement déverrouillés après que le token ait été réclamé par le destinataire." + }, + "destroy": "Détruire", + "destroy-ckb-desc": "Retournera tous les CKB de ce compte CKB Asset à votre adresse de change.", + "destroy-sudt-desc": "Retournera les CKB occupés par le compte d'actif à votre adresse de change.", + "extra-ckb-send-to-secp256": { + "label": "Envoyer 142 CKBytes supplémentaires avec {{assetName}}", + "tooltip": "142 CKBytes supplémentaires seront envoyés avec {{assetName}} pour détenir l'actif." + }, + "extra-ckb-send-to-acp": { + "label": "Envoyer {{extraCKB}} CKBytes supplémentaires pour créer un compte d'actif pour le destinataire" + }, + "extra-ckb-send-to-unknow": { + "label": "L'adresse du destinataire a un script de verrouillage inconnu, assurez-vous que le destinataire sait comment le gérer, et {{extraCKB}} CKBytes supplémentaires seront envoyés avec {{assetName}} pour détenir l'actif" + }, + "select-option": "Veuillez confirmer l'option ci-dessus avant de soumettre" + }, + "receive": { + "notation": "Accepter uniquement {{symbol}}" + }, + "update-dialog": { + "update-asset-account": "Détails du compte", + "account-name": "Nom du compte", + "token-id": "ID du token", + "token-name": "Nom du token", + "symbol": "Symbole", + "decimal": "Décimal", + "confirm": "Confirmer", + "cancel": "Annuler" + } + }, + "multisig-address": { + "window-title": "Adresses Multisig", + "search": { + "placeholder": "recherche par adresse multisig, alias" + }, + "add": { + "label": "Créer" + }, + "import": { + "label": "Importer" + }, + "export": { + "label": "Exporter" + }, + "ok": "OK", + "no-data": "Pas d'adresse multisig", + "table": { + "address": "Adresse du signataire", + "alias": "Alias", + "type": "Type", + "balance": "Solde", + "copy-address": "Copier l'adresse", + "action": "Actions", + "more": "Plus", + "sync-block": "Bloc synchronisé", + "actions": { + "info": "Info", + "send": "Envoyer", + "approve": "Approuver", + "delete": "Supprimer" + } + }, + "import-dialog": { + "actions": { + "cancel": "Annuler", + "confirm": "Confirmer" + }, + "notice": "Une seule configuration peut être importée à la fois. Si le contenu du fichier est un tableau, le premier est importé par défaut." + }, + "delete-failed": "Échec de la suppression de la configuration multisig", + "remove-multisig-address": "Supprimer l'adresse multisig", + "remove-multisig-address-msg": "Êtes-vous sûr de vouloir supprimer l'adresse multisig sélectionnée?", + "send-ckb": { + "title": "Envoyer des CKB depuis une adresse multisig", + "detail": "Envoyer des CKB depuis une adresse multisig {{m}}-sur-{{n}} : <0>", + "balance": "Solde", + "address": "Adresse", + "amount": "Montant", + "send": "Envoyer", + "cancel": "Annuler", + "export": "Exporter Tx" + }, + "multi-details": "Détails de l'adresse multisig", + "create-dialog": { + "title": "Créer une adresse multisig", + "preview-title": "L'adresse multisig a été générée", + "index": "Index", + "required": "Requis", + "signer-address": "Adresse du signataire", + "copy-address": "Copier l'adresse", + "placeholder": "Veuillez entrer {{type}}(1-255)", + "m-n": { + "title": "Veuillez entrer la configuration d'adresse multisig en mode m-sur-n où m clés privées parmi un total possible de n sont nécessaires pour déplacer l'actif.", + "m-less-equal-n": "Pour une adresse multisig m-sur-n, m ne doit pas être supérieur à n", + "m-n-required": "Veuillez entrer m/n", + "m-n-between-0-256": "m/n doit être compris entre 0 et 256" + }, + "multi-address-info": { + "title": "Veuillez entrer les signataires pour l'adresse multisig {{m}}-sur-{{n}}", + "view-title": "L'adresse multisig {{m}}-sur-{{n}}", + "ckb-address-placeholder": "Veuillez entrer l'adresse CKB" + }, + "multi-list": "Liste des signataires", + "actions": { + "cancel": "Annuler", + "back": "Retour", + "next": "Suivant", + "generate-address": "Générer l'adresse", + "confirm": "Confirmer" + }, + "duplicate-address-forbid": "L'adresse ne peut pas être dupliquée" + }, + "approve-dialog": { + "title": "Approuver avec une adresse multisig", + "detail": "Approuver avec une adresse multisig {{m}}-sur-{{n}} <0>", + "transaction": "Transaction", + "cancel": "Annuler", + "signAndExport": "Signer & Exporter", + "export": "Exporter Tx", + "signAndBroadcast": "Signer & Diffuser", + "broadcast": "Diffuser", + "content": "Contenu :", + "status": "Statut", + "signerApprove": "Manque d'approbation de {{m}} signataire(s), dont {{r}} de signataire(s) spécifié(s)", + "noRSignerApprove": "Manque d'approbation de {{m}} signataire(s)", + "signed": "Signé", + "view-concise-data": "Données concises", + "view-raw-data": "Données" + } + }, + "dropdown": { + "placeholder": "Veuillez sélectionner..." + }, + "price-switch": { + "price": "Prix", + "customPrice": "Prix personnalisé", + "errorTip": "Le prix ne peut pas être inférieur à {{minPrice}} shannons/KB, veuillez le saisir à nouveau", + "hintTip": "Le prix suggéré est de {{suggestFeeRate}} shannons/KB", + "priceColumn": "Prix {{priceValue}} shannons/KB | Frais {{feeValue}} CKB", + "fast": "Rapide", + "standard": "Standard", + "slow": "Lent", + "countDownTip": "Le prix se rafraîchira après {{countDown}} secondes", + "switchToCustomPrice": "Basculer vers un prix personnalisé", + "switchToPrice": "Basculer vers le prix" + }, + "set-start-block-number": { + "title": "Définir le numéro de bloc de départ", + "tip": "Le numéro de bloc de départ est une configuration lorsque vous utilisez le noeud CKB en mode client léger. Vous pouvez le définir et accélérer la synchronisation.", + "input-place-holder": "Veuillez entrer le numéro de bloc de départ pour la synchronisation, par exemple : 10, 100, 101", + "locate-first-tx": "Localiser la première transaction", + "view-block": "Voir le bloc" + }, + "main": { + "external-node-detected-dialog": { + "title": "Détection d'un noeud externe", + "body-tips-without-network": "\"Noeud interne\" est réservé au noeud CKB intégré, veuillez ajouter une nouvelle option réseau pour le noeud externe.", + "body-tips-with-network": "\"Noeud interne\" est réservé au noeud CKB intégré, veuillez sélectionner parmi les options réseau suivantes ou en ajouter une nouvelle pour le noeud externe.", + "add-network": "Ajouter un réseau", + "ignore-external-node": "Ignorer le noeud externe" + } + }, + "cell-manage": { + "title": "Gestion des cellules", + "wallet-balance": "Solde du Wallet", + "table": { + "head": { + "date": "Date", + "type": "Type", + "balance": "Solde", + "status": "Statut", + "description": "Description", + "action": "Opération" + }, + "locked": "Verrouillé", + "unlocked": "Déverrouillé", + "default-description": "Aucune" + }, + "cell-detail-dialog": { + "title": "Détail de la cellule", + "capacity-used": "Capacité utilisée", + "data": "Données", + "total": "Total", + "used": "Utilisé" + }, + "locked-reason": { + "multi-locktime": "La cellule est verrouillée dans le temps et vous ne pouvez pas l'utiliser avant {{time}}.", + "multi-locktime-reached": "Le délai de verrouillage de cette cellule a expiré et vous devez la réclamer dans les actifs personnalisés avant de l'utiliser.", + "cheque-acp-multisig": "Il s'agit d'une cellule {{type}} et ne prend pas en charge le déverrouillage.", + "NFT-SUDT-DAO": "Il s'agit d'un actif {{type}} et ne prend pas en charge le déverrouillage.", + "Unknown": "Il s'agit d'un actif non répertorié, veuillez vérifier et confirmer avant de procéder." + }, + "cell-lock-dialog": { + "title": "Verrouiller la cellule", + "capacity": "Verrouiller la cellule sélectionnée {{capacity}} CKB", + "locked-cell-can-not-use": "Attention, les cellules verrouillées ne peuvent pas être utilisées" + }, + "cell-unlock-dialog": { + "title": "Déverrouiller la cellule", + "capacity": "Déverrouiller la cellule sélectionnée {{capacity}} CKB" + }, + "cell-consume-dialog": { + "title": "Consommer la cellule", + "warn-consume": "La consommation de la cellule ne conservera aucune donnée et utilisera tout le CKB. Veuillez vous assurer qu'aucune donnée importante ne doit être conservée." + }, + "enter-password": "Entrez le mot de passe", + "password-placeholder": "Veuillez entrer le mot de passe du Wallet", + "lock": "Verrouiller", + "unlock": "Déverrouiller", + "consume": "Consommer" + } + } +} diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 77fe4fc8a3..44b240120b 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -405,7 +405,8 @@ "en": "English", "en-US": "English(United States)", "zh": "中文(简体)", - "zh-TW": "中文(繁體)" + "zh-TW": "中文(繁體)", + "fr": "Français" }, "data": { "ckb-node-data": "節點配置和數據", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 8d2a559241..6a5b89f526 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -404,7 +404,8 @@ "en": "English", "en-US": "English(United States)", "zh": "中文(简体)", - "zh-TW": "中文(繁體)" + "zh-TW": "中文(繁體)", + "fr": "Français" }, "data": { "ckb-node-data": "节点配置和数据", diff --git a/packages/neuron-ui/src/tests/calendar/index.test.ts b/packages/neuron-ui/src/tests/calendar/index.test.ts index 27792b5cbc..cf7e24dea2 100644 --- a/packages/neuron-ui/src/tests/calendar/index.test.ts +++ b/packages/neuron-ui/src/tests/calendar/index.test.ts @@ -105,6 +105,11 @@ describe('Get Local Month Short Names', () => { const names = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'] expect(getLocalMonthShortNames('en')).toEqual(names) }) + + it('French', () => { + const names = ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'] + expect(getLocalMonthShortNames('fr')).toEqual(names) + }) }) describe('Get Local Month Names', () => { @@ -112,7 +117,6 @@ describe('Get Local Month Names', () => { const names = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'] expect(getLocalMonthNames('zh')).toEqual(names) }) - it('English', () => { const names = [ 'January', @@ -130,6 +134,24 @@ describe('Get Local Month Names', () => { ] expect(getLocalMonthNames('en')).toEqual(names) }) + + it('French', () => { + const names = [ + 'janvier', + 'février', + 'mars', + 'avril', + 'mai', + 'juin', + 'juillet', + 'août', + 'septembre', + 'octobre', + 'novembre', + 'décembre', + ] + expect(getLocalMonthNames('fr')).toEqual(names) + }) }) describe('Get Local Week Names', () => { @@ -143,6 +165,11 @@ describe('Get Local Week Names', () => { expect(getLocalWeekNames('en')).toEqual(names) }) + it('French', () => { + const names = ['D', 'L', 'M', 'M', 'J', 'V', 'S'] + expect(getLocalWeekNames('fr')).toEqual(names) + }) + it('Traditional Chinese', () => { const names = ['日', '一', '二', '三', '四', '五', '六'] expect(getLocalWeekNames('zh-TW')).toEqual(names) diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index 95db79fe67..624cf75931 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -49,7 +49,7 @@ export const DEFAULT_SUDT_FIELDS = { CKBSymbol: 'CKB', CKBDecimal: '8', } -export const LOCALES = ['zh', 'zh-TW', 'en', 'en-US'] as const +export const LOCALES = ['zh', 'zh-TW', 'en', 'en-US', 'fr'] as const // address property export const SHORT_ADDR_LENGTH = 46 diff --git a/packages/neuron-ui/src/utils/i18n.ts b/packages/neuron-ui/src/utils/i18n.ts index 28ebef475d..e422f4364d 100644 --- a/packages/neuron-ui/src/utils/i18n.ts +++ b/packages/neuron-ui/src/utils/i18n.ts @@ -5,10 +5,12 @@ import { getLocale } from 'services/remote' import zh from 'locales/zh.json' import en from 'locales/en.json' import zhTW from 'locales/zh-tw.json' +import fr from 'locales/fr.json' i18n.use(initReactI18next).init({ resources: { en, + fr, zh, 'zh-TW': zhTW, }, diff --git a/packages/neuron-wallet/src/locales/fr.ts b/packages/neuron-wallet/src/locales/fr.ts new file mode 100644 index 0000000000..892de7cb34 --- /dev/null +++ b/packages/neuron-wallet/src/locales/fr.ts @@ -0,0 +1,303 @@ +export default { + translation: { + keywords: { + wallet: 'Wallet', + password: 'Mot de passe', + 'wallet-name': 'Nom du Wallet', + }, + 'application-menu': { + neuron: { + about: 'À propos de {{app}}', + preferences: 'Préférences...', + 'check-updates': 'Vérifier les mises à jour...', + quit: 'Quitter {{app}}', + }, + wallet: { + label: 'Wallet', + select: 'Sélectionner un Wallet', + 'create-new': 'Créer un nouveau Wallet', + import: 'Importer un Wallet', + backup: 'Sauvegarder le Wallet actuel', + 'export-xpubkey': 'Exporter la clé publique étendue', + delete: 'Supprimer le Wallet actuel', + 'change-password': 'Changer de mot de passe', + 'import-mnemonic': 'Importer la graine du Wallet', + 'import-keystore': 'Importer depuis le fichier Keystore', + 'import-xpubkey': 'Importer la clé publique étendue', + 'import-hardware': 'Importer un Wallet matériel', + }, + edit: { + label: 'Édition', + cut: 'Couper', + copy: 'Copier', + paste: 'Coller', + selectall: 'Sélectionner tout', + }, + tools: { + label: 'Outils', + 'sign-and-verify': 'Signer/Vérifier le message', + 'multisig-address': 'Adresses multisig', + 'offline-sign': 'Signature hors ligne', + 'clear-sync-data': 'Effacer toutes les données synchronisées', + }, + window: { + label: 'Fenêtre', + minimize: 'Réduire', + close: 'Fermer la fenêtre', + }, + help: { + label: 'Aide', + 'nervos-website': 'Site Web de Nervos', + 'source-code': 'Code source', + 'report-issue': 'Signaler un problème', + 'contact-us': 'Contactez-nous', + 'contact-us-message': + '> Veuillez ajouter les informations de débogage exportées via "Menu" -> "Aide" -> "Exporter les informations de débogage".', + documentation: 'Documentation', + settings: 'Paramètres', + 'export-debug-info': 'Exporter les informations de débogage', + }, + develop: { + develop: 'Développer', + 'force-reload': 'Forcer le rechargement', + reload: 'Recharger', + 'toggle-dev-tools': 'Basculer les outils de développement', + }, + }, + services: { + transactions: 'Transactions', + wallets: 'Wallets', + }, + messages: { + 'failed-to-load-networks': 'Échec du chargement des réseaux.', + 'Networks-will-be-reset': 'Les réseaux seront réinitialisés.', + 'wallet-password-less-than-min-length': + 'Le mot de passe doit comporter au moins {{minPasswordLength}} caractères.', + 'wallet-password-more-than-max-length': + "Le mot de passe doit comporter jusqu'à {{maxPasswordLength}} caractères.", + 'wallet-password-letter-complexity': + 'Le mot de passe doit contenir une combinaison de lettres majuscules et minuscules, de chiffres et de caractères spéciaux.', + 'current-wallet-not-set': "Le Wallet actuel n'est pas défini.", + 'incorrect-password': 'Le mot de passe est incorrect', + 'invalid-address': "L'adresse {{address}} n'est pas valide.", + 'codehash-not-loaded': "Le codehash n'est pas chargé.", + 'wallet-not-found': 'Le Wallet {{id}} est introuvable.', + 'failed-to-create-mnemonic': 'Échec de la création de la mnémonique.', + 'network-not-found': "Le réseau de l'ID {{id}} n'a pas été trouvé.", + 'invalid-name': "Le nom {{field}} n'est pas valide.", + 'default-network-unremovable': 'Le réseau par défaut est irréparable.', + 'lack-of-default-network': 'Manque de réseau par défaut.', + 'current-network-not-set': "Le RPC du noeud CKB actuel n'a pas été défini.", + 'transaction-not-found': 'La transaction {{hash}} est introuvable.', + 'is-required': '{{field}} est requis.', + 'invalid-format': '{{field}} est dans un format invalide.', + 'used-name': 'Le nom {{field}} est utilisé, veuillez en choisir un autre.', + 'missing-required-argument': 'Argument requis manquant.', + 'save-keystore': 'Sauvegarder le fichier Keystore.', + 'save-extended-public-key': 'Sauvegarder la clé publique étendue.', + 'import-extended-public-key': 'Importer la clé publique étendue.', + 'invalid-mnemonic': "La graine du Wallet n'est pas valide, veuillez la vérifier à nouveau.", + 'unsupported-cipher': 'Chiffrement non pris en charge.', + 'capacity-not-enough': 'Solde insuffisant.', + 'capacity-not-enough-for-change': + 'Vous avez besoin de plus de capacités pour la monnaie de rendu (plus de 61 CKBytes).', + 'capacity-not-enough-for-change-by-transfer': + 'Vous avez besoin de plus de capacités pour la monnaie de rendu (plus de 61 CKBytes), ou cliquez sur le bouton "Max" pour envoyer tout votre solde.', + 'live-capacity-not-enough': + 'Solde disponible insuffisant, veuillez réessayer lorsque la dernière transaction a été confirmée.', + 'capacity-too-small': 'Le solde de transfert minimal est de {{bytes}} CKBytes.', + 'should-be-type-of': '{{field}} doit être de type {{type}}.', + 'invalid-keystore': "Le fichier Keystore n'est pas valide, veuillez vérifier l'intégrité de votre fichier.", + 'invalid-json': "Fichier JSON non valide, veuillez vérifier l'intégrité de votre fichier.", + 'cell-is-not-yet-live': 'Veuillez attendre que la dernière transaction soit confirmée par la chaîne.', + 'transaction-is-not-committed-yet': + 'Impossible de trouver les cellules requises sur la chaîne, veuillez vous assurer que les transactions liées ont été confirmées.', + 'mainnet-address-required': "{{address}} n'est pas une adresse du réseau principal.", + 'testnet-address-required': "{{address}} n'est pas une adresse du réseau de test.", + 'address-not-found': + "L'adresse donnée ne fait pas partie du Wallet actuel. Veuillez vérifier votre Wallet ou attendre la fin de la synchronisation.", + 'target-output-not-found': "Il n'y a pas de Wallet de compte associé à cette adresse.", + 'acp-same-account': 'Le compte de paiement et le compte de réception ne doivent pas être les mêmes.', + 'device-sign-canceled': + 'Vous avez annulé la demande de signature. Sinon, assurez-vous que l\'application Nervos sur votre appareil a la configuration "autoriser les données de contrat" activée', + 'connect-device-failed': "L'appareil ne peut pas être connecté, veuillez vérifier votre connexion.", + 'unsupported-manufacturer': 'Les appareils de {{manufacturer}} ne sont pas encore pris en charge.', + 'wallet-not-supported-function': 'Ce Wallet ne prend pas en charge la fonction {name}.', + 'invalid-transaction-file': 'Fichier de transaction non valide.', + 'offline-sign-failed': 'Échec de la signature, veuillez vérifier si vous signez avec le bon Wallet.', + 'multisig-script-prefix-error': 'La configuration multisig est erronée', + 'multisig-config-not-exist': "La configuration multisig n'existe pas", + 'multisig-config-exist': 'La configuration multisig existe déjà', + 'multisig-config-address-error': "Le paramètre d'adresse de la configuration multisig est incorrect", + 'multisig-config-need-error': 'La génération de transactions multisig nécessite une configuration multisig', + 'transaction-no-input-parameter': "Il manque un paramètre requis à l'entrée de la cellule de requête", + 'migrate-sudt-no-type': "La cellule de migration n'a pas de script de type", + 'multisig-not-signed': 'Des signatures partielles manquent pour les transactions multisig', + 'multisig-lock-hash-mismatch': "L'adresse multisig actuelle ne correspond pas à la transaction à approuver", + 'sudt-acp-have-data': 'Le compte acp sUDT à détruire contient une certaine quantité', + 'no-match-address-for-sign': 'Aucune adresse correspondante trouvée', + 'target-lock-error': "Le compte d'actifs CKB ne peut être transféré qu'à l'adresse secp256k1 ou acp", + 'no-exist-ckb-node-data': + "{{path}} n'a pas de configuration et de stockage de noeud CKB, appuyez sur Confirmer pour synchroniser à partir de zéro", + 'light-client-sudt-acp-error': + "Le mode client léger ne prend pas en charge l'envoi d'actifs vers le compte d'actifs d'autrui", + }, + messageBox: { + button: { + confirm: 'OK', + discard: 'Annuler', + }, + 'clear-sync-data': { + title: 'Effacer toutes les données synchronisées', + message: + 'Effacer toutes les données synchronisées supprimera toutes les données locales synchronisées et resynchronisera les données sur la chaîne. La synchronisation complète peut prendre beaucoup de temps.', + }, + 'send-capacity': { + title: 'Envoyer la transaction', + }, + 'remove-network': { + title: 'Supprimer le réseau', + message: 'Le réseau {{name}} (adresse : {{address}}) sera supprimé.', + alert: "C'est le réseau actuel. En le supprimant, la connexion passera au réseau par défaut", + }, + 'remove-wallet': { + title: 'Supprimer le Wallet', + password: 'Mot de passe', + }, + 'backup-keystore': { + title: 'Sauvegarder le fichier Keystore', + password: 'Mot de passe', + }, + transaction: { + title: 'Transaction : {{hash}}', + }, + 'sign-and-verify': { + title: 'Signer/Vérifier le message', + }, + 'multisig-address': { + title: 'Adresses multisig', + }, + 'ckb-dependency': { + title: 'Noeud CKB inclus', + message: 'Dépendance requise', + detail: `Les noeuds réseau dans Neuron dépendent de composants C++, veuillez donc installer la dernière version de Microsoft Visual C++ Redistributable pour x64 pour garantir le bon fonctionnement du logiciel.`, + buttons: { + 'install-and-exit': 'Installer et quitter', + }, + }, + 'acp-migration': { + title: "Mise à niveau du compte d'actif", + message: "Mise à niveau du compte d'actif", + detail: + "Récemment, notre équipe de sécurité a identifié une vulnérabilité potentielle dans le script expérimental du compte d'actif. Nous avons déployé un nouveau script de compte d'actif avec une correction sur le réseau principal, et tous les futurs comptes d'actif utiliseront la nouvelle version. Nous vous recommandons de les mettre à niveau pour utiliser le nouveau script.", + buttons: { + migrate: 'Mise à niveau sécurisée maintenant', + skip: 'Je connais les risques, je mettrai à niveau plus tard', + }, + }, + 'acp-migration-completed': { + title: 'Félicitations ! Vous avez terminé la mise à niveau sécurisée.', + message: 'Félicitations ! Vous avez terminé la mise à niveau sécurisée.', + buttons: { + ok: 'OK', + }, + }, + 'hard-fork-migrate': { + message: + "Afin de s'adapter à la dernière version de CKB, Neuron va resynchroniser les données sur la chaîne, et la synchronisation complète peut prendre un certain temps.", + }, + 'mail-us': { + message: + 'Veuillez nous envoyer un courriel avec les informations de débogage exportées par "Menu" -> "Aide" -> "Exporter les informations de débogage".', + 'open-client': 'Ouvrir le client de messagerie', + 'fail-message': + 'Impossible de lancer le client de messagerie. Veuillez copier l\'adresse e-mail, ajouter les informations de débogage exportées par "Menu" -> "Aide" -> "Exporter les informations de débogage" et nous les envoyer.', + 'copy-mail-addr': "Copier l'adresse e-mail", + }, + 'migrate-failed': { + title: 'Échec de la migration', + message: + "Échec de la migration. Appuyez sur OK pour supprimer les anciennes données et resynchroniser à partir de zéro, ou cliquez sur Annuler pour migrer ultérieurement en relançant Neuron. Raison de l'échec de la migration : {{ reason }}", + buttons: { + ok: 'OK', + cancel: 'Annuler', + }, + }, + }, + prompt: { + password: { + label: 'Entrez votre mot de passe', + submit: 'Soumettre', + cancel: 'Annuler', + }, + }, + updater: { + 'update-not-available': "Aucune mise à jour n'est actuellement disponible.", + }, + common: { + yes: 'Oui', + no: 'Non', + ok: 'OK', + cancel: 'Annuler', + error: 'Erreur', + }, + 'export-debug-info': { + 'export-debug-info': 'Exporter les informations de débogage', + 'debug-info-exported': 'Les informations de débogage ont été exportées vers {{ file }}', + }, + about: { + 'app-version': '{{name}} Version : {{version}}', + 'ckb-client-version': 'Version du client CKB : {{version}}', + 'ckb-light-client-version': 'Version légère du client CKB : {{version}}', + }, + settings: { + title: { + normal: 'Paramètres', + mac: 'Préférences', + }, + }, + 'export-transactions': { + 'export-transactions': "Exporter l'historique des transactions", + 'export-success': 'Les transactions ont été exportées', + 'transactions-exported': '{{total}} enregistrements de transactions ont été exportés vers {{file}}', + column: { + time: 'Heure', + 'block-number': 'Numéro de bloc', + 'tx-hash': 'hash de transaction', + 'tx-type': 'Type de transaction', + amount: 'Montant de CKB', + 'udt-amount': 'Montant UDT', + description: 'Description', + }, + 'tx-type': { + send: 'Envoyer', + receive: 'Recevoir', + 'create-asset-account': "Créer un compte d'actif {{name}}", + 'destroy-asset-account': "Détruire le compte d'actif {{name}}", + }, + }, + 'offline-signature': { + 'export-transaction': 'Exporter la transaction au format JSON', + 'transaction-exported': 'La transaction a été exportée vers {{filePath}}.', + 'load-transaction': 'Charger le fichier de transaction', + }, + 'multisig-config': { + 'import-config': 'Importer une configuration multisig', + 'export-config': 'Exporter une configuration multisig', + 'config-exported': 'Les configurations multisig ont été exportées vers {{filePath}}.', + 'import-duplicate': 'Veuillez vérifier les configurations en double', + 'import-result': 'Importations réussies {{success}}, échecs {{fail}}.{{failCheck}}', + 'confirm-delete': 'Confirmer la suppression de la configuration multisig ?', + 'approve-tx': 'Confirmer la transaction multisig', + 'delete-actions': { + ok: 'Confirmer', + cancel: 'Annuler', + }, + }, + 'open-in-explorer': { + title: "Voir dans l'explorateur CKB", + transaction: 'transaction', + message: "Voir {{type}} {{key}} dans l'explorateur CKB", + }, + }, +} diff --git a/packages/neuron-wallet/src/locales/i18n.ts b/packages/neuron-wallet/src/locales/i18n.ts index a7a9a69335..882ba31cfd 100644 --- a/packages/neuron-wallet/src/locales/i18n.ts +++ b/packages/neuron-wallet/src/locales/i18n.ts @@ -2,10 +2,12 @@ import i18n from 'i18next' import zh from './zh' import en from './en' import zhTW from './zh-tw' +import fr from './fr' i18n.init({ resources: { en, + fr, zh, 'zh-TW': zhTW, }, diff --git a/packages/neuron-wallet/src/services/settings.ts b/packages/neuron-wallet/src/services/settings.ts index 02629f021e..89f82f35d6 100644 --- a/packages/neuron-wallet/src/services/settings.ts +++ b/packages/neuron-wallet/src/services/settings.ts @@ -9,7 +9,7 @@ import { LIGHT_CLIENT_MAINNET, LIGHT_CLIENT_TESTNET } from '../utils/const' const { app } = env -export const locales = ['zh', 'zh-TW', 'en', 'en-US'] as const +export const locales = ['zh', 'zh-TW', 'en', 'en-US', 'fr'] as const export type Locale = (typeof locales)[number] const settingKeys = { ckbDataPath: 'ckbDataPath', diff --git a/packages/neuron-wallet/tests/services/setting.test.ts b/packages/neuron-wallet/tests/services/setting.test.ts index 7d8c55e117..96f760ddac 100644 --- a/packages/neuron-wallet/tests/services/setting.test.ts +++ b/packages/neuron-wallet/tests/services/setting.test.ts @@ -72,7 +72,9 @@ describe('SettingsService', () => { }) it('set', () => { SettingsService.getInstance().locale = 'zh' + SettingsService.getInstance().locale = 'fr' expect(writeSyncMock).toBeCalledWith('locale', 'zh') + expect(writeSyncMock).toBeCalledWith('locale', 'fr') expect(updateApplicationMenuMock).toHaveBeenCalled() }) it('set exception', () => { From 3a9160936ec4ffe815837d7d68f98e42518d99ab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:54:03 +0800 Subject: [PATCH 4/7] Update ckb client versions (#3018) feat: update ckb client versions Co-authored-by: Keith-CY --- .ckb-version | 2 +- compatible.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.ckb-version b/.ckb-version index bfe1211ecc..64a30f7e97 100644 --- a/.ckb-version +++ b/.ckb-version @@ -1 +1 @@ -v0.112.1 +v0.113.0 diff --git a/compatible.json b/compatible.json index 204a2ddda4..a28669ae51 100644 --- a/compatible.json +++ b/compatible.json @@ -1,5 +1,6 @@ { "fullVersions": [ + "0.113", "0.112", "0.111", "0.110", @@ -18,6 +19,7 @@ "compatible": { "0.111": { "full": [ + "0.113", "0.112", "0.111", "0.110", @@ -30,6 +32,7 @@ }, "0.110": { "full": [ + "0.113", "0.112", "0.111", "0.110", @@ -58,6 +61,7 @@ }, "0.112": { "full": [ + "0.113", "0.112", "0.111", "0.110", From d4488a19b50c97cdb577e0de15aab6a7e0b4fdba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:02:21 +0800 Subject: [PATCH 5/7] chore(deps): bump follow-redirects from 1.15.2 to 1.15.4 (#3017) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index dcadc9fc24..eaf1fa0edc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11059,9 +11059,9 @@ flow-parser@0.*: integrity sha512-M0SdOwD0wZHhk6K/AOaPReBnw2vB7p9KUFUFZHJRsU3ZMl/+WVrMpmb8AfEM6GXZ5mEssCx9vHugxxJg1ieoew== follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== for-each@^0.3.3: version "0.3.3" From b823770900e9617ed14be735eef47019a629b489 Mon Sep 17 00:00:00 2001 From: homura Date: Fri, 12 Jan 2024 10:12:52 +0800 Subject: [PATCH 6/7] refactor: upgrade spore sdk (#3007) --- packages/neuron-wallet/package.json | 2 +- packages/neuron-wallet/src/services/cells.ts | 7 +- .../src/services/transaction-sender.ts | 4 +- yarn.lock | 177 ++++-------------- 4 files changed, 39 insertions(+), 151 deletions(-) diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index ff7b802bd8..45828839f7 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -53,7 +53,7 @@ "@ckb-lumos/rpc": "0.21.1", "@iarna/toml": "2.2.5", "@ledgerhq/hw-transport-node-hid": "6.27.22", - "@spore-sdk/core": "0.1.0-beta.9", + "@spore-sdk/core": "0.1.0-beta.14", "archiver": "6.0.1", "async": "3.2.5", "bn.js": "4.12.0", diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 3959c4137a..6e41ac3d11 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -32,7 +32,7 @@ import MultisigConfigModel from '../models/multisig-config' import MultisigOutput from '../database/chain/entities/multisig-output' import { bytes } from '@ckb-lumos/codec' import { generateRPC } from '../utils/ckb-rpc' -import { getClusterCellById, SporeData, unpackToRawClusterData } from '@spore-sdk/core' +import { getClusterById, SporeData, unpackToRawClusterData } from '@spore-sdk/core' import NetworksService from './networks' import { LOCKTIME_ARGS_LENGTH, MIN_CELL_CAPACITY } from '../utils/const' import HdPublicKeyInfo from '../database/chain/entities/hd-public-key-info' @@ -383,10 +383,7 @@ export default class CellsService { return } - const clusterCell = await getClusterCellById( - clusterId, - assetAccountInfo.getSporeConfig(currentNetwork.remote) - ) + const clusterCell = await getClusterById(clusterId, assetAccountInfo.getSporeConfig(currentNetwork.remote)) const { name, description } = unpackToRawClusterData(clusterCell.data) clusterInfos[clusterId] = { name, description } } catch { diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts index 9820c9c0e7..7c0195b5fe 100644 --- a/packages/neuron-wallet/src/services/transaction-sender.ts +++ b/packages/neuron-wallet/src/services/transaction-sender.ts @@ -43,7 +43,7 @@ import NetworksService from './networks' import { generateRPC } from '../utils/ckb-rpc' import CellsService from './cells' import hd from '@ckb-lumos/hd' -import { getClusterCellByOutPoint } from '@spore-sdk/core' +import { getClusterByOutPoint } from '@spore-sdk/core' import CellDep, { DepType } from '../models/chain/cell-dep' import { dao } from '@ckb-lumos/common-scripts' @@ -606,7 +606,7 @@ export default class TransactionSender { // https://github.com/sporeprotocol/spore-sdk/blob/05f2cbe1c03d03e334ebd3b440b5b3b20ec67da7/packages/core/src/api/joints/spore.ts#L154-L158 const clusterDep = await (async () => { - const clusterCell = await getClusterCellByOutPoint(outPoint, assetAccountInfo.getSporeConfig(rpcUrl)).then( + const clusterCell = await getClusterByOutPoint(outPoint, assetAccountInfo.getSporeConfig(rpcUrl)).then( _ => _, () => undefined ) diff --git a/yarn.lock b/yarn.lock index eaf1fa0edc..365ad2a9db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2110,21 +2110,7 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@ckb-lumos/base@0.20.0", "@ckb-lumos/base@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/base/-/base-0.20.0.tgz#d18164ec60eae3f3a97a4bc48e1399e6e0c190d9" - integrity sha512-Zz4+iO7d7dJBMtn/7icD9uuld2fBUGI+RqoBumXW8mem3xDw41Vb4vL0knxVdOFF1EkuSX5yZ796f83h8Q/6zw== - dependencies: - "@ckb-lumos/bi" "0.20.0" - "@ckb-lumos/codec" "0.20.0" - "@ckb-lumos/toolkit" "0.20.0" - "@types/blake2b" "^2.1.0" - "@types/lodash.isequal" "^4.5.5" - blake2b "^2.1.3" - js-xxhash "^1.0.4" - lodash.isequal "^4.5.0" - -"@ckb-lumos/base@0.21.1": +"@ckb-lumos/base@0.21.1", "@ckb-lumos/base@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/base/-/base-0.21.1.tgz#1faf7909b5a68a124256d937cfb0fa03bb6b5457" integrity sha512-7O+jBl7pqMsRbYTMNnbpamgaQzvaLZq+ftMtnKZ3A+Zbs6hZcGbz/6nfbRZnyJitPXQHRPT5KAQz6g+TiYqJGg== @@ -2138,32 +2124,13 @@ js-xxhash "^1.0.4" lodash.isequal "^4.5.0" -"@ckb-lumos/bi@0.20.0", "@ckb-lumos/bi@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.20.0.tgz#4fa3beca641737b7b83a64d56668067e38cc288d" - integrity sha512-cJtv1NnvC11oQbDC8f3jIGTYdyEs2m8GvSrZgGL5KjBoaHTWghH8v6Z2XkwFgHmmCwfX5KrdXekrsOWrcIXyqw== - dependencies: - jsbi "^4.1.0" - -"@ckb-lumos/bi@0.21.1": +"@ckb-lumos/bi@0.21.1", "@ckb-lumos/bi@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.21.1.tgz#dfaa0968a9ffd990c1c4fcfc0711f20f09eb0485" integrity sha512-6q8uesvu3DAM7GReei9H5seino4tnakTeg8uXtZBPDC6rboMohLCPQvEwhl1iHmsybXvBYVQt4Te1BPPZtuaRw== dependencies: jsbi "^4.1.0" -"@ckb-lumos/ckb-indexer@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/ckb-indexer/-/ckb-indexer-0.20.0.tgz#c6792821e3b903f998a891b14892ff67229d3957" - integrity sha512-bw5HiuAleTa9D4l3xW+0+kYh49CH3bjRNPHxv/ySVy1TxTAYEJA6qjcIOE81T8U6qSnUxY1KKiAjk2bO+mo05w== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - "@ckb-lumos/rpc" "0.20.0" - "@ckb-lumos/toolkit" "0.20.0" - cross-fetch "^3.1.5" - events "^3.3.0" - "@ckb-lumos/ckb-indexer@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/ckb-indexer/-/ckb-indexer-0.21.1.tgz#a0e0ac3261c98181dc8c72e9e726030bb210779d" @@ -2177,35 +2144,14 @@ cross-fetch "^3.1.5" events "^3.3.0" -"@ckb-lumos/codec@0.20.0", "@ckb-lumos/codec@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.20.0.tgz#bb07707c5976b3b706b78f253e680cdc431ca898" - integrity sha512-NHBPIaiNYz5yX0RGSM0Qz1effLnVTt0wJHPfLVAkuzNRBg5/xRZySjYOemOsogbX1t4s3Yk7N0Q17tr02AUh3A== - dependencies: - "@ckb-lumos/bi" "0.20.0" - -"@ckb-lumos/codec@0.21.1": +"@ckb-lumos/codec@0.21.1", "@ckb-lumos/codec@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.21.1.tgz#160f21efa0cded6ea461eb6476f9af6010b682e0" integrity sha512-z6IUUxVZrx663iC7VM9CmaQZL8jsdM3ybgz0UCS24JgBXTNec+Uz0/Zrl7yeH6fBpVls44C2wObcHKigKaNVAA== dependencies: "@ckb-lumos/bi" "0.21.1" -"@ckb-lumos/common-scripts@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/common-scripts/-/common-scripts-0.20.0.tgz#7f95fb91d3c275ff557197269225a15a634811b6" - integrity sha512-GSCayhyt+G0k9fNGK3bjVif2PDWbo47ovUEM0CiRuG0YrhvBYbuq69mpQ3nKezI1IKI3UZyYOax7SqhYBCJ5VQ== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - "@ckb-lumos/codec" "0.20.0" - "@ckb-lumos/config-manager" "0.20.0" - "@ckb-lumos/helpers" "0.20.0" - "@ckb-lumos/rpc" "0.20.0" - "@ckb-lumos/toolkit" "0.20.0" - immutable "^4.0.0-rc.12" - -"@ckb-lumos/common-scripts@0.21.1": +"@ckb-lumos/common-scripts@0.21.1", "@ckb-lumos/common-scripts@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/common-scripts/-/common-scripts-0.21.1.tgz#64776b5ce55d8c66898a3ab592c7f77fe13d865e" integrity sha512-EfZQ9wdxPmEsxVVwtBjhpZVKbYCm1FJkMt59ABsIO1Ub7yi0qap7AQl0MMbuLwWIGKwS2w0U3wx/oJPm7z1RXg== @@ -2219,18 +2165,7 @@ "@ckb-lumos/toolkit" "0.21.1" immutable "^4.3.0" -"@ckb-lumos/config-manager@0.20.0", "@ckb-lumos/config-manager@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/config-manager/-/config-manager-0.20.0.tgz#f24946971005f62df62e22c3306947b5e820c181" - integrity sha512-YNhowInBGuOVMRBuhDWznhcB+83vLzYSL6HAlGCBbm2yT7lDmIrrVWh731hISmuaXeMt4IFupLfGXxpelB6zCA== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - "@ckb-lumos/codec" "0.20.0" - "@types/deep-freeze-strict" "^1.1.0" - deep-freeze-strict "^1.1.1" - -"@ckb-lumos/config-manager@0.21.1": +"@ckb-lumos/config-manager@0.21.1", "@ckb-lumos/config-manager@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/config-manager/-/config-manager-0.21.1.tgz#857050ebf2ca38096d8fae26b6a8927164798ca3" integrity sha512-BmrNqYyaksdCKHWagyC8+R8GUxhIO+sOM5S925jlkpjju2sUbH0Id2/zmdb7I9KxdKnbx3WsR+hqy7/bYqw1lA== @@ -2241,19 +2176,6 @@ "@types/deep-freeze-strict" "^1.1.0" deep-freeze-strict "^1.1.1" -"@ckb-lumos/hd@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/hd/-/hd-0.20.0.tgz#4b51fb881313ca193a876ae93257f2d5a13df46c" - integrity sha512-vPzMFZwYpuOVEiQ8WeY6+TVGy0Hcdau7okbMX6mks87Kt1f8qEyW5uAP8xFWA0UmsOGucdJKirjRfSvsAPzbIw== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - bn.js "^5.1.3" - elliptic "^6.5.4" - scrypt-js "^3.0.1" - sha3 "^2.1.3" - uuid "^8.3.0" - "@ckb-lumos/hd@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/hd/-/hd-0.21.1.tgz#406026ac2b570f72b4407149cb941d09bb8a0ab6" @@ -2267,18 +2189,6 @@ sha3 "^2.1.3" uuid "^8.3.0" -"@ckb-lumos/helpers@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/helpers/-/helpers-0.20.0.tgz#bc0555aeaba146ce90726ac87acad7e2200112c7" - integrity sha512-UWxiJSljZcN1zH6Blv5HyjNGgA9VSXyXFh8VSYD7W1xEeHYK05LP00rRWtEr8Z+KSfuuZ0ASg2sJofBDv/UZ9w== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - "@ckb-lumos/config-manager" "0.20.0" - "@ckb-lumos/toolkit" "0.20.0" - bech32 "^2.0.0" - immutable "^4.0.0-rc.12" - "@ckb-lumos/helpers@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/helpers/-/helpers-0.21.1.tgz#fffd455071f51556b2cd2539a255b3759893da08" @@ -2292,33 +2202,22 @@ bech32 "^2.0.0" immutable "^4.3.0" -"@ckb-lumos/lumos@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/lumos/-/lumos-0.20.0.tgz#47cd173affa81051f8a8fa60c91c366001d82d6c" - integrity sha512-LIKf1tDNk0HkK3DQ+BV4dmj/B7nfkbVXlPbzqxwLRaqHTbAArXs8g3JXGfmjZIgpoU6sfXdArO1jVpGRN1xnYg== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - "@ckb-lumos/ckb-indexer" "0.20.0" - "@ckb-lumos/common-scripts" "0.20.0" - "@ckb-lumos/config-manager" "0.20.0" - "@ckb-lumos/hd" "0.20.0" - "@ckb-lumos/helpers" "0.20.0" - "@ckb-lumos/rpc" "0.20.0" - "@ckb-lumos/toolkit" "0.20.0" - -"@ckb-lumos/rpc@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/rpc/-/rpc-0.20.0.tgz#7da342c6db0403ecf654c0281e004653c1220dc0" - integrity sha512-vAH/rXMnKYcyKVW8/vg5EvY9roW46FlDod/efLGN5FQyROn0O5Y9PTO/7bs4U1KyPeceHQpRW0ZAQ/KEHBWEzA== - dependencies: - "@ckb-lumos/base" "0.20.0" - "@ckb-lumos/bi" "0.20.0" - "@vespaiach/axios-fetch-adapter" "^0.3.1" - axios "0.27.2" - tslib "2.3.1" +"@ckb-lumos/lumos@^0.21.0-next.0": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/lumos/-/lumos-0.21.1.tgz#ff5a9a10859b305530026668bd14cdc12e5e7532" + integrity sha512-a5n8xaIUvmaPsw2fBki8Jamy6/6uQnLWDZM9SQX29cZ1YVoAk/slnBmEbCIGXmxDhcuAlLkTeJaiLDKEGrZ6pg== + dependencies: + "@ckb-lumos/base" "0.21.1" + "@ckb-lumos/bi" "0.21.1" + "@ckb-lumos/ckb-indexer" "0.21.1" + "@ckb-lumos/common-scripts" "0.21.1" + "@ckb-lumos/config-manager" "0.21.1" + "@ckb-lumos/hd" "0.21.1" + "@ckb-lumos/helpers" "0.21.1" + "@ckb-lumos/rpc" "0.21.1" + "@ckb-lumos/toolkit" "0.21.1" -"@ckb-lumos/rpc@0.21.1": +"@ckb-lumos/rpc@0.21.1", "@ckb-lumos/rpc@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/rpc/-/rpc-0.21.1.tgz#f6a6b3ed6d76adfb84c148e4b4acadb7fbaa7d7a" integrity sha512-gZWXYCyQ98s84Pb+buOiYL3HOIxQPLHQdCyo96GFerNw9lB1XsbaGWzfHPYpZvOQqYtnJ1GUfTkQkADrQ7hmew== @@ -2328,11 +2227,6 @@ abort-controller "^3.0.0" cross-fetch "^3.1.5" -"@ckb-lumos/toolkit@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ckb-lumos/toolkit/-/toolkit-0.20.0.tgz#8f71043324e50c8a3e94c65414325482d874cfca" - integrity sha512-nLNXRPG20NqUdXwHAnKwr0T7nZyDrf4fGwIrsxo+7ffqWeg9wNEhZoqTBJLgxEpoe0I+WHRLwYLiwUbkkqR1SQ== - "@ckb-lumos/toolkit@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/toolkit/-/toolkit-0.21.1.tgz#bcf4bf05615375087e3dd02fb087b797a8c67f37" @@ -4302,16 +4196,18 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@spore-sdk/core@0.1.0-beta.9": - version "0.1.0-beta.9" - resolved "https://registry.yarnpkg.com/@spore-sdk/core/-/core-0.1.0-beta.9.tgz#0ec68155367f72c5dcd40de7dcdbc12029a69e5d" - integrity sha512-Bku2Wrya1bFBAvBhWnzCnHMrZF1y4qWKES27dKYVOIZghBPK5WAYU2fDGWRCuU8YhueUOdqeUnkbVxc0mgyfuA== - dependencies: - "@ckb-lumos/base" "^0.20.0" - "@ckb-lumos/bi" "^0.20.0" - "@ckb-lumos/codec" "^0.20.0" - "@ckb-lumos/config-manager" "^0.20.0" - "@ckb-lumos/lumos" "^0.20.0" +"@spore-sdk/core@0.1.0-beta.14": + version "0.1.0-beta.14" + resolved "https://registry.yarnpkg.com/@spore-sdk/core/-/core-0.1.0-beta.14.tgz#1b7823e27180f7031fd12f641bbabcf52f78b3e3" + integrity sha512-pwD20Ft134aTUzJ3xZDEGtSNqJfuO5CoUGKqIbd/o+oQp1a7UpwdNKiqYxUKUBWEBF/JyBPPD2qvoBID/PycRw== + dependencies: + "@ckb-lumos/base" "^0.21.0-next.0" + "@ckb-lumos/bi" "^0.21.0-next.0" + "@ckb-lumos/codec" "^0.21.0-next.0" + "@ckb-lumos/common-scripts" "^0.21.0-next.0" + "@ckb-lumos/config-manager" "^0.21.0-next.0" + "@ckb-lumos/lumos" "^0.21.0-next.0" + "@ckb-lumos/rpc" "^0.21.0-next.0" lodash "^4.17.21" whatwg-mimetype "^3.0.0" @@ -6318,11 +6214,6 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vespaiach/axios-fetch-adapter@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@vespaiach/axios-fetch-adapter/-/axios-fetch-adapter-0.3.1.tgz#b0c08167bec9cc558f578a1b9ccff52ead1cf1cb" - integrity sha512-+1F52VWXmQHSRFSv4/H0wtnxfvjRMPK5531e880MIjypPdUSX6QZuoDgEVeCE1vjhzDdxCVX7rOqkub7StEUwQ== - "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" @@ -7268,7 +7159,7 @@ axios@0.21.4: dependencies: follow-redirects "^1.14.0" -axios@0.27.2, axios@^0.27.2: +axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== @@ -12243,7 +12134,7 @@ immutable@^4.0.0: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be" integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== -immutable@^4.0.0-rc.12, immutable@^4.3.0: +immutable@^4.3.0: version "4.3.4" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== From 4907867165926ada3240b390e27c808c1103324b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Fri, 12 Jan 2024 08:22:46 +0000 Subject: [PATCH 7/7] chore: Remove `address` field from indexer-tx-hash-cache table (#3011) --- .../sync/indexer-cache-service.ts | 4 +--- .../chain/entities/indexer-tx-hash-cache.ts | 10 +--------- ...1704357651876-RemoveAddressInIndexerCache.ts | 17 +++++++++++++++++ .../src/database/chain/ormconfig.ts | 2 ++ .../block-sync-renderer/synchronizer.test.ts | 2 -- 5 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 packages/neuron-wallet/src/database/chain/migrations/1704357651876-RemoveAddressInIndexerCache.ts diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts index 4775f70a67..b58c3d1555 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts @@ -219,13 +219,12 @@ export default class IndexerCacheService { continue } - for (const { lockHash, address } of mappings) { + for (const { lockHash } of mappings) { indexerCaches.push( IndexerTxHashCache.fromObject({ txHash: transaction.hash!, blockNumber: parseInt(transaction.blockNumber!), lockHash, - address, walletId: this.walletId, }) ) @@ -267,7 +266,6 @@ export default class IndexerCacheService { txHash: v.txHash, blockNumber: parseInt(v.blockNumber!), lockHash: v.lockHash, - address: v.address, walletId: v.walletId, }) ) diff --git a/packages/neuron-wallet/src/database/chain/entities/indexer-tx-hash-cache.ts b/packages/neuron-wallet/src/database/chain/entities/indexer-tx-hash-cache.ts index a2b3ce1530..57f998303b 100644 --- a/packages/neuron-wallet/src/database/chain/entities/indexer-tx-hash-cache.ts +++ b/packages/neuron-wallet/src/database/chain/entities/indexer-tx-hash-cache.ts @@ -19,13 +19,6 @@ export default class IndexerTxHashCache extends BaseEntity { @Index() lockHash!: string - @Column({ - type: 'character', - length: 32, - }) - @Index() - address!: string - @Column({ type: 'character', length: 32, @@ -54,12 +47,11 @@ export default class IndexerTxHashCache extends BaseEntity { }) updatedAt!: Date - static fromObject(obj: { txHash: string; blockNumber: number; lockHash: string; address: string; walletId: string }) { + static fromObject(obj: { txHash: string; blockNumber: number; lockHash: string; walletId: string }) { const result = new IndexerTxHashCache() result.txHash = obj.txHash result.blockNumber = obj.blockNumber result.lockHash = obj.lockHash - result.address = obj.address result.walletId = obj.walletId result.isProcessed = false return result diff --git a/packages/neuron-wallet/src/database/chain/migrations/1704357651876-RemoveAddressInIndexerCache.ts b/packages/neuron-wallet/src/database/chain/migrations/1704357651876-RemoveAddressInIndexerCache.ts new file mode 100644 index 0000000000..b6b0dba18c --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1704357651876-RemoveAddressInIndexerCache.ts @@ -0,0 +1,17 @@ +import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; + +export class RemoveAddressInIndexerCache1704357651876 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('indexer_tx_hash_cache', 'address') + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn('indexer_tx_hash_cache', new TableColumn({ + name: 'address', + type: 'character(32)', + isNullable: false, + })) + } + +} diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index f7667901a4..9a90ea6be6 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -59,6 +59,7 @@ import { TxLockAddArgs1694746034975 } from './migrations/1694746034975-TxLockAdd import { IndexerTxHashCacheRemoveField1701234043431 } from './migrations/1701234043431-IndexerTxHashCacheRemoveField' import { CreateCellLocalInfo1701234043432 } from './migrations/1701234043432-CreateCellLocalInfo' import { RenameSyncProgress1702781527414 } from './migrations/1702781527414-RenameSyncProgress' +import { RemoveAddressInIndexerCache1704357651876 } from './migrations/1704357651876-RemoveAddressInIndexerCache' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' @@ -134,6 +135,7 @@ const connectOptions = async (genesisBlockHash: string): Promise { txHash: 'hash1', blockNumber: 10, lockHash: script.computeHash(), - address, walletId, }), ] @@ -165,7 +164,6 @@ describe('unit tests for IndexerConnector', () => { txHash: 'hash2', blockNumber: 2, lockHash: script.computeHash(), - address, walletId, }), ]