From 078b5081db2430d899761ecebd7a74c30b369e55 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, 18 Oct 2023 01:41:43 +0000 Subject: [PATCH 01/12] fix: Format date when output log. Catch exception when export public key info. (#2877) --- .../src/controllers/export-debug.ts | 18 +++++++++++------- packages/neuron-wallet/src/utils/logger.ts | 7 ++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/neuron-wallet/src/controllers/export-debug.ts b/packages/neuron-wallet/src/controllers/export-debug.ts index 238207a0c0..c2c120eb44 100644 --- a/packages/neuron-wallet/src/controllers/export-debug.ts +++ b/packages/neuron-wallet/src/controllers/export-debug.ts @@ -137,14 +137,18 @@ export default class ExportDebugController { } private async addHdPublicKeyInfoCsv() { - const addressMetas = await AddressService.getAddressesByAllWallets() - let csv = 'walletId,addressType,addressIndex,publicKeyInBlake160\n' - for (const addressMeta of addressMetas) { - const row = `${addressMeta.walletId},${addressMeta.addressType},${addressMeta.addressIndex},${addressMeta.blake160}\n` - csv += row + try { + const addressMetas = await AddressService.getAddressesByAllWallets() + let csv = 'walletId,addressType,addressIndex,publicKeyInBlake160\n' + for (const addressMeta of addressMetas) { + const row = `${addressMeta.walletId},${addressMeta.addressType},${addressMeta.addressIndex},${addressMeta.blake160}\n` + csv += row + } + const csvFileName = 'hd_public_key_info.csv' + this.archive.append(csv, { name: csvFileName }) + } catch (error) { + logger.error(`Export Debug:\t export public key info error: ${error}`) } - const csvFileName = 'hd_public_key_info.csv' - this.archive.append(csv, { name: csvFileName }) } private addLogFiles = (files = ['main.log', 'renderer.log']) => { diff --git a/packages/neuron-wallet/src/utils/logger.ts b/packages/neuron-wallet/src/utils/logger.ts index 10a52498e9..2c3071d1c0 100644 --- a/packages/neuron-wallet/src/utils/logger.ts +++ b/packages/neuron-wallet/src/utils/logger.ts @@ -4,7 +4,12 @@ import env from '../env' if (!env.isDevMode) { logger.transports.file.level = 'info' } - +logger.transports.file.format = ({ date, level, data }) => { + return `[${date.toISOString()}] [${level}] ${data}` +} +logger.transports.console.format = ({ date, level, data }) => { + return `[${date.toISOString()}] [${level}] ${data}` +} // logger.catchErrors({ showDialog: false }) export default logger From 09fd7c8f553c51136e8af67e6e0dee63563cf05f Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Thu, 19 Oct 2023 13:15:21 +0900 Subject: [PATCH 02/12] chore: add a script to generate checksum table (#2886) --- scripts/generate-checksum-table.js | 110 +++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 scripts/generate-checksum-table.js diff --git a/scripts/generate-checksum-table.js b/scripts/generate-checksum-table.js new file mode 100644 index 0000000000..8eed204d61 --- /dev/null +++ b/scripts/generate-checksum-table.js @@ -0,0 +1,110 @@ +// use this script as 'node ./generate-checksum-table.js ./binaries' where Neuron binaries sit in ./binaries +const fs = require('node:fs') +const path = require('node:path') +const crypto = require('node:crypto') + +class Package { + _name + _directory + + get arch() { + const name = this._name + if (name.endsWith('.exe') || name.includes('-x64') || name.includes('x86_64')) { + return 'x64' + } + + if (name.includes('-arm64')) { + return 'arm64' + } + + throw new Error(`Unknown arch: ${name}`) + } + + get os() { + const name = this._name + if (name.endsWith('.exe')) { + return 'Windows' + } + + if (name.includes('-mac-') || name.endsWith('.dmg')) { + return 'macOS' + } + + if (name.endsWith('.AppImage')) { + return 'Linux' + } + + throw new Error(`Unknown OS: ${name}`) + } + + get package() { + const ext = path.extname(this._name).slice(1) + if (ext === 'dmg') return 'DMG' + return ext + } + + get url() { + const version = this._name.split('-')[1] + return `https://github.com/nervosnetwork/neuron/releases/download/${version}/${this._name}` + } + + get checksum() { + const binary = fs.readFileSync(path.join(this._directory, this._name)) + const hash = crypto.createHash('sha256') + hash.update(binary) + return hash.digest('hex') + } + + constructor(directory, name) { + this._directory = directory + this._name = name + } + + toEntry() { + return `${this.os} | ${this.arch} | [${this.package}](${this.url}) | ${this.checksum}\n` + } +} + +const getChecksumTable = (directory) => { + let table = `OS | Arch | Package | SHA256 Checksum\n-- | -- | -- | --\n` + + const files = fs.readdirSync(directory).filter((f) => ['.dmg', '.zip', '.exe', '.AppImage'].includes(path.extname(f))) + files + .map((f) => { + return new Package(directory, f) + }) + .sort((a, b) => { + if (a.os !== b.os) { + if (a.os === 'Windows') return -1 + if (a.os === 'Linux') return 1 + } + + if (a.package !== b.package) { + return a.package === 'zip' ? -1 : 1 + } + + if (a.arch !== b.arch) { + return a.arch === 'x64' ? -1 : 1 + } + }) + .forEach((p) => { + table += p.toEntry() + }) + + return table +} + +if (process.argv.length < 3) { + throw new Error(`Directory of binaries is required, use command as 'node ./generate-checksum-table.js ./binaries`) +} + +const directory = process.argv[2] + +const checksumTable = getChecksumTable(directory) + +console.info(checksumTable) + +const output = path.join(directory, './checksums.txt') +fs.writeFileSync(output, checksumTable, { flag: 'wx' }) + +console.info(`Checksum table has been generated at ${output}`) From a8bf38d78b672aef81c821b4bb4ec26492f95046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Thu, 19 Oct 2023 08:09:50 +0000 Subject: [PATCH 03/12] fix: use electron-log format styles. The function can not transform object correctly. (#2889) --- packages/neuron-wallet/src/utils/logger.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/neuron-wallet/src/utils/logger.ts b/packages/neuron-wallet/src/utils/logger.ts index 2c3071d1c0..9156ee3cfb 100644 --- a/packages/neuron-wallet/src/utils/logger.ts +++ b/packages/neuron-wallet/src/utils/logger.ts @@ -4,12 +4,10 @@ import env from '../env' if (!env.isDevMode) { logger.transports.file.level = 'info' } -logger.transports.file.format = ({ date, level, data }) => { - return `[${date.toISOString()}] [${level}] ${data}` -} -logger.transports.console.format = ({ date, level, data }) => { - return `[${date.toISOString()}] [${level}] ${data}` -} +logger.transports.file.format = '[{iso}] [{level}] {text}' + +logger.transports.console.format = '[{iso}] [{level}] {text}' + // logger.catchErrors({ showDialog: false }) export default logger From 03a08f039d04c20be554cb8be35485b0ebe6a375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Thu, 19 Oct 2023 08:45:10 +0000 Subject: [PATCH 04/12] fix: Update compatible.csv after update ckb and Neuron version (#2890) --- compatible.csv | 11 ++++++----- packages/neuron-wallet/src/services/node.ts | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/compatible.csv b/compatible.csv index 98e98ab39d..f39484153b 100644 --- a/compatible.csv +++ b/compatible.csv @@ -1,5 +1,6 @@ -CKB,0.110,0.109,0.108,0.107,0.106,0.105,0.104,0.103 -Neuron,,,,,,,, -0.110,yes,yes,no,no,no,no,no,no -0.106,no,no,yes,yes,yes,yes,no,no -0.103,no,no,no,no,no,no,yes,yes +CKB,0.111,0.110,0.109,0.108,0.107,0.106,0.105,0.104,0.103 +Neuron,,,,,,,,, +0.111,yes,yes,yes,no,no,no,no,no,no +0.110,yes,yes,yes,no,no,no,no,no,no +0.106,no,no,no,yes,yes,yes,yes,no,no +0.103,no,no,no,no,no,no,no,yes,yes diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts index b2b257add2..c672239aa3 100644 --- a/packages/neuron-wallet/src/services/node.ts +++ b/packages/neuron-wallet/src/services/node.ts @@ -155,6 +155,7 @@ class NodeService { } public async verifyExternalCkbNode() { + logger.info('CKB:\tstart verify external ckb node') const network = NetworksService.getInstance().getCurrent() if (this._isCkbNodeExternal && network.type !== NetworkType.Light) { const localNodeInfo = await new RpcService(network.remote).localNodeInfo() From f3cc18a45cbfcf5a77c2d46630e49c096c8ff5ca Mon Sep 17 00:00:00 2001 From: Shinya Date: Fri, 20 Oct 2023 16:05:43 +0800 Subject: [PATCH 05/12] refactor: remove ckb-sdk-core and replace with lumos (#2843) Co-authored-by: Yonghui Lin --- _typos.toml | 4 +- packages/neuron-wallet/package.json | 17 +- .../src/block-sync-renderer/sync/queue.ts | 2 +- .../src/controllers/offline-sign.ts | 3 +- .../neuron-wallet/src/models/chain/block.ts | 4 +- .../src/models/chain/transaction.ts | 6 +- .../neuron-wallet/src/services/sdk-core.ts | 12 +- .../src/services/transaction-sender.ts | 10 +- .../src/services/tx/transaction-service.ts | 8 +- .../src/types/ckbComponents.d.ts | 280 +++++++++++ packages/neuron-wallet/src/types/ckbRpc.d.ts | 3 + packages/neuron-wallet/src/types/rpc.d.ts | 459 +++++++++++++++++- packages/neuron-wallet/src/utils/ckb-rpc.ts | 19 +- .../neuron-wallet/src/utils/signWitnesses.ts | 46 ++ .../tests/services/tx-wallet.test.ts | 6 +- .../services/tx/transaction-sender.test.ts | 15 +- .../services/tx/transaction-service.test.ts | 24 +- yarn.lock | 149 +++--- 18 files changed, 938 insertions(+), 129 deletions(-) create mode 100644 packages/neuron-wallet/src/types/ckbComponents.d.ts create mode 100644 packages/neuron-wallet/src/types/ckbRpc.d.ts create mode 100644 packages/neuron-wallet/src/utils/signWitnesses.ts diff --git a/_typos.toml b/_typos.toml index 09deee0241..369e09719d 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,11 +1,9 @@ [default.extend-words] thur = "thur" +numer = "numer" # defined in database schema lastest = "lastest" [files] extend-exclude = ["CHANGELOG.md", "**/migrations/*.ts"] - - - diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index d25caff4cd..0038035ff3 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -42,17 +42,16 @@ ] }, "dependencies": { - "@ckb-lumos/base": "0.20.0-alpha.3", - "@ckb-lumos/bi": "0.20.0-alpha.3", - "@ckb-lumos/ckb-indexer": "0.20.0-alpha.3", - "@ckb-lumos/codec": "0.20.0-alpha.3", - "@ckb-lumos/config-manager": "0.20.0-alpha.3", - "@ckb-lumos/hd": "0.20.0-alpha.3", - "@ckb-lumos/helpers": "0.20.0-alpha.3", - "@ckb-lumos/rpc": "0.20.0-alpha.3", + "@ckb-lumos/base": "^0.21.0-next.1", + "@ckb-lumos/bi": "^0.21.0-next.1", + "@ckb-lumos/ckb-indexer": "^0.21.0-next.1", + "@ckb-lumos/codec": "^0.21.0-next.1", + "@ckb-lumos/config-manager": "^0.21.0-next.1", + "@ckb-lumos/hd": "^0.21.0-next.1", + "@ckb-lumos/helpers": "^0.21.0-next.1", + "@ckb-lumos/rpc": "^0.21.0-next.1", "@iarna/toml": "2.2.5", "@ledgerhq/hw-transport-node-hid": "6.27.16", - "@nervosnetwork/ckb-sdk-core": "0.109.0", "archiver": "5.3.0", "async": "3.2.4", "bn.js": "4.12.0", 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 506af6dc94..b8c29c11a9 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts @@ -146,7 +146,7 @@ export default class Queue { const headers = await rpc .createBatchRequest<'getHeader', string[], CKBComponents.BlockHeader[]>(blockHashes.map(v => ['getHeader', v])) .exec() - headers.forEach((blockHeader, idx) => { + headers.forEach((blockHeader: CKBComponents.BlockHeader, idx: number) => { if (blockHeader) { const header = BlockHeader.fromSDK(blockHeader) txs[idx].timestamp = header.timestamp diff --git a/packages/neuron-wallet/src/controllers/offline-sign.ts b/packages/neuron-wallet/src/controllers/offline-sign.ts index b6a0a0c0af..058c5d0e6f 100644 --- a/packages/neuron-wallet/src/controllers/offline-sign.ts +++ b/packages/neuron-wallet/src/controllers/offline-sign.ts @@ -40,7 +40,8 @@ export default class OfflineSignController { const rpc = generateRPC(NodeService.getInstance().nodeUrl) if (context === undefined) { - const rawTx = rpc.paramsFormatter.toRawTransaction(tx.toSDKRawTransaction()) + const rawSdkTx = tx.toSDKRawTransaction() + const rawTx = rpc.paramsFormatter.toRawTransaction(rawSdkTx) const txs = await Promise.all(rawTx.inputs.map(i => rpc.getTransaction(i.previous_output!.tx_hash))) context = txs.map(i => rpc.paramsFormatter.toRawTransaction(i.transaction)) } diff --git a/packages/neuron-wallet/src/models/chain/block.ts b/packages/neuron-wallet/src/models/chain/block.ts index bc6a4b5a2c..6d643742eb 100644 --- a/packages/neuron-wallet/src/models/chain/block.ts +++ b/packages/neuron-wallet/src/models/chain/block.ts @@ -14,7 +14,9 @@ export default class Block { const header = BlockHeader.fromSDK(block.header) return new Block( header, - block.transactions.map(tx => Transaction.fromSDK(tx, header)) + block.transactions.map((tx: CKBComponents.RawTransaction | CKBComponents.Transaction) => + Transaction.fromSDK(tx, header) + ) ) } } diff --git a/packages/neuron-wallet/src/models/chain/transaction.ts b/packages/neuron-wallet/src/models/chain/transaction.ts index 2af594c38c..885c3b182a 100644 --- a/packages/neuron-wallet/src/models/chain/transaction.ts +++ b/packages/neuron-wallet/src/models/chain/transaction.ts @@ -298,7 +298,7 @@ export default class Transaction { blockHeader?: BlockHeader ): Transaction { const txHash: string | undefined = (tx as CKBComponents.Transaction).hash - const outputs = tx.outputs.map((o, i) => { + const outputs = tx.outputs.map((o: CKBComponents.CellOutput, i: number) => { const output = Output.fromSDK(o) if (txHash) { output.setOutPoint(new OutPoint(txHash, i.toString())) @@ -307,9 +307,9 @@ export default class Transaction { }) return new Transaction( tx.version, - tx.cellDeps.map(cd => CellDep.fromSDK(cd)), + tx.cellDeps.map((cd: CKBComponents.CellDep) => CellDep.fromSDK(cd)), tx.headerDeps, - tx.inputs.map(i => Input.fromSDK(i)), + tx.inputs.map((i: CKBComponents.CellInput) => Input.fromSDK(i)), outputs, tx.outputsData, tx.witnesses, diff --git a/packages/neuron-wallet/src/services/sdk-core.ts b/packages/neuron-wallet/src/services/sdk-core.ts index ad93376d7f..a951c08ff6 100644 --- a/packages/neuron-wallet/src/services/sdk-core.ts +++ b/packages/neuron-wallet/src/services/sdk-core.ts @@ -1,4 +1,4 @@ -import CKB from '@nervosnetwork/ckb-sdk-core' +import { CKBRPC } from '@ckb-lumos/rpc' import https from 'https' import http from 'http' @@ -19,14 +19,14 @@ const getHttpAgent = () => { return httpAgent } -export const generateCKB = (url: string): CKB => { - const ckb = new CKB(url) +export const generateCKB = (url: string): CKBRPC => { + const rpc = new CKBRPC(url) if (url.startsWith('https')) { - ckb.rpc.setNode({ url, httpsAgent: getHttpsAgent() }) + rpc.setNode({ url, httpsAgent: getHttpsAgent() }) } else { - ckb.rpc.setNode({ url, httpAgent: getHttpAgent() }) + rpc.setNode({ url, httpAgent: getHttpAgent() }) } - return ckb + return rpc } export default { diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts index 893fb90a9a..672f25181f 100644 --- a/packages/neuron-wallet/src/services/transaction-sender.ts +++ b/packages/neuron-wallet/src/services/transaction-sender.ts @@ -1,4 +1,3 @@ -import signWitnesses from '@nervosnetwork/ckb-sdk-core/lib/signWitnesses' import NodeService from './node' import { serializeWitnessArgs } from '../utils/serialization' import { scriptToAddress } from '../utils/scriptAndAddress' @@ -21,6 +20,7 @@ import Script from '../models/chain/script' import Multisig from '../models/multisig' import Blake2b from '../models/blake2b' import logger from '../utils/logger' +import { signWitnesses } from '../utils/signWitnesses' import { bytes as byteUtils, bytes, number } from '@ckb-lumos/codec' import SystemScriptInfo from '../models/system-script-info' import AddressParser from '../models/address-parser' @@ -42,7 +42,7 @@ import { getMultisigStatus } from '../utils/multisig' import { SignStatus } from '../models/offline-sign' import NetworksService from './networks' import { generateRPC } from '../utils/ckb-rpc' -import CKB from '@nervosnetwork/ckb-sdk-core' +import { CKBRPC } from '@ckb-lumos/rpc' import CellsService from './cells' import hd from '@ckb-lumos/hd' @@ -216,7 +216,8 @@ export default class TransactionSender { wit.lock = serializedMultisig + wit.lock!.slice(2) signed[0] = serializeWitnessArgs(wit.toSDK()) } else { - signed = signWitnesses(privateKey)({ + signed = signWitnesses({ + privateKey, transactionHash: txHash, witnesses: serializedWitnesses.map(wit => { if (typeof wit === 'string') { @@ -791,9 +792,8 @@ export default class TransactionSender { depositOutPoint: OutPoint, withdrawBlockHash: string ): Promise => { - const ckb = new CKB(NodeService.getInstance().nodeUrl) + const ckb = new CKBRPC(NodeService.getInstance().nodeUrl) const result = await ckb.calculateDaoMaximumWithdraw(depositOutPoint.toSDK(), withdrawBlockHash) - return BigInt(result) } diff --git a/packages/neuron-wallet/src/services/tx/transaction-service.ts b/packages/neuron-wallet/src/services/tx/transaction-service.ts index c38d3a3e03..833b112b61 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-service.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-service.ts @@ -1,5 +1,5 @@ import { getConnection } from 'typeorm' -import CKB from '@nervosnetwork/ckb-sdk-core' +import { CKBRPC } from '@ckb-lumos/rpc' import TransactionEntity from '../../database/chain/entities/transaction' import OutputEntity from '../../database/chain/entities/output' import Transaction, { @@ -507,14 +507,14 @@ export class TransactionsService { const inputTxHashes = inputs.map(v => v.previousOutput?.txHash).filter((v): v is string => !!v) if (!inputTxHashes.length) return inputs const url: string = NetworksService.getInstance().getCurrent().remote - const ckb = new CKB(url) - const inputTxs = await ckb.rpc + const ckb = new CKBRPC(url) + const inputTxs = await ckb .createBatchRequest<'getTransaction', string[], CKBComponents.TransactionWithStatus[]>( inputTxHashes.map(v => ['getTransaction', v]) ) .exec() const inputTxMap = new Map() - inputTxs.forEach((v, idx) => { + inputTxs.forEach((v: { transaction: CKBComponents.Transaction }, idx: number) => { inputTxMap.set(inputTxHashes[idx], v.transaction) }) return inputs.map(v => { diff --git a/packages/neuron-wallet/src/types/ckbComponents.d.ts b/packages/neuron-wallet/src/types/ckbComponents.d.ts new file mode 100644 index 0000000000..56e2ff19b7 --- /dev/null +++ b/packages/neuron-wallet/src/types/ckbComponents.d.ts @@ -0,0 +1,280 @@ +import type { Agent as HttpsAgent } from 'node:https' +import type { Agent as HttpAgent } from 'node:http' + +declare global { + declare namespace CKBComponents { + type DAO = string + type Hash = string + type Number = string + type Hash256 = string + type UInt32 = string + type UInt64 = string + type U256 = string + type Index = string + type Version = string + type Count = string + type Difficulty = string + type BlockNumber = string + type EpochInHeader = string + type Capacity = string + type ProposalShortId = string + type Timestamp = string + type Nonce = string + type Cycles = string + type Size = string + type OutputsValidator = 'default' | 'passthrough' | undefined + type RationalU256 = Record<'denom' | 'numer', string> + type ProposalWindow = Record<'closest' | 'farthest', BlockNumber> + type EpochNumberWithFraction = string + type EpochNumber = string + enum TransactionStatus { + Pending = 'pending', + Proposed = 'proposed', + Committed = 'committed', + } + type ScriptHashType = api.HashType + type DepType = 'code' | 'depGroup' + type JsonBytes = string + /** + * @typedef Bytes, keep consistent with CKB + * @description Bytes will be serialized to string + * @see https://github.com/nervosnetwork/ckb/blob/develop/util/jsonrpc-types/src/blockchain.rs#L19 + */ + type Bytes = string + type Since = string + interface Node { + url: string + httpAgent?: HttpAgent + httpsAgent?: HttpsAgent + } + interface Method { + name: string + method: string + paramsFormatters: function[] + resultFormatters?: function + } + /** + * RPC Units + */ + type Witness = Bytes + type Script = api.Script + type CellInput = api.Input + type CellOutput = api.Output + type Cell = CellOutput + type OutPoint = api.OutPoint + type CellDep = api.CellDep + type RawTransaction = api.RawTransaction & { + witnesses: Witness[] + } + type Transaction = Required + type TransactionWithStatus = api.TransactionWithStatus + type BlockHeader = api.Header + type Block = api.Block + type UncleBlock = api.UncleBlock + type LiveCell = api.LiveCell + type AlertMessage = api.AlertMessage + type BlockchainInfo = api.ChainInfo + type LocalNodeInfo = api.LocalNode + type RemoteNodeInfo = api.RemoteNode + type TxPoolInfo = api.TxPoolInfo + type Epoch = api.Epoch + type RunDryResult = api.DryRunResult + type BannedAddress = api.BannedAddr + type WitnessArgs = api.WitnessArgs + type BlockEconomicState = api.BlockEconomicState + type SyncState = api.SyncState + type TransactionProof = api.TransactionProof + type TxVerbosity = api.TxVerbosity + type TxPoolVerbosity = api.TxPoolVerbosity + type RawTxPool = api.RawTxPool + type Consensus = api.Consensus + type HardForks = api.HardForks + type HardForkFeature = api.HardforkFeature + type SoftForkStatus = api.SoftForkStatus + type SoftFork = api.SoftFork + type Buried = api.Buried + type Rfc0043 = api.Rfc0043 + type Ratio = api.Ratio + type Deployment = api.Deployment + type QueryOptions = api.QueryOptions + interface TransactionPoint { + blockNumber: BlockNumber + index: Index + txHash: Hash256 + } + interface TransactionByLockHash { + consumedBy: undefined | TransactionPoint + createdBy: TransactionPoint + } + type TransactionsByLockHash = TransactionByLockHash[] + interface FeeRate { + feeRate: string + } + interface CellIncludingOutPoint { + blockHash: Hash256 + capacity: Capacity + lock: Script + outPoint: OutPoint + cellbase: boolean + outputDataLen: string + } + type TransactionTrace = { + action: string + info: string + time: Timestamp + }[] + enum CellStatus { + Live = 'live', + Unknown = 'unknown', + } + interface LiveCellByLockHash { + cellOutput: CellOutput + createdBy: TransactionPoint + cellbase: boolean + outputDataLen: string + } + type LiveCellsByLockHash = LiveCellByLockHash[] + interface PeersState { + lastUpdated: string + blocksInFlight: string + peer: string + } + interface LockHashIndexState { + blockHash: Hash256 + blockNumber: BlockNumber + lockHash: Hash256 + } + type LockHashIndexStates = LockHashIndexState[] + type BannedAddresses = BannedAddress[] + interface CellbaseOutputCapacityDetails { + primary: string + proposalReward: string + secondary: string + total: string + txFee: string + } + interface RawTransactionToSign extends Omit { + witnesses: (WitnessArgs | Witness)[] + } + interface CapacityByLockHash { + blockNumber: BlockNumber + capacity: Capacity + cellsCount: string + } + type TxPoolIds = Record<'pending' | 'proposed', Array> + interface Tip { + blockNumber: BlockNumber + blockHash: Hash256 + } + type ScriptType = 'type' | 'lock' + type Order = 'asc' | 'desc' + type IOType = 'input' | 'output' | 'both' + type ScriptSearchMode = 'prefix' | 'exact' + interface IndexerCell { + blockNumber: BlockNumber + outPoint: OutPoint + output: { + capacity: Capacity + lock: Script + type?: Script + } + outputData: string + txIndex: string + } + interface IndexerCellWithoutData extends Omit { + outputData: null + } + interface GetCellsResult { + lastCursor: string + objects: WithData extends true ? IndexerCell[] : IndexerCellWithoutData[] + } + type IndexerTransaction = Grouped extends true + ? GroupedIndexerTransaction + : UngroupedIndexerTransaction + type UngroupedIndexerTransaction = { + txHash: Hash256 + blockNumber: BlockNumber + ioIndex: number + ioType: IOType + txIndex: number + } + type GroupedIndexerTransaction = { + txHash: Hash256 + blockNumber: BlockNumber + txIndex: number + cells: Array<[IOType, number]> + } + interface GetTransactionsResult { + lastCursor: Hash256 + objects: IndexerTransaction[] + } + interface CKBIndexerQueryOptions extends QueryOptions { + outputDataLenRange?: HexadecimalRange + outputCapacityRange?: HexadecimalRange + scriptLenRange?: HexadecimalRange + bufferSize?: number + withData?: boolean + groupByTransaction?: boolean + } + type HexadecimalRange = [string, string] + interface SearchFilter { + script?: Script + scriptLenRange?: HexadecimalRange + outputDataLenRange?: HexadecimalRange + outputCapacityRange?: HexadecimalRange + blockRange?: HexadecimalRange + } + interface SearchKey { + script: Script + scriptType: ScriptType + filter?: SearchFilter + scriptSearchMode?: ScriptSearchMode + } + interface GetLiveCellsResult { + lastCursor: string + objects: WithData extends true ? IndexerCell[] : IndexerCellWithoutData[] + } + interface GetCellsSearchKey extends SearchKey { + withData?: WithData + } + interface GetTransactionsSearchKey extends SearchKey { + groupByTransaction?: Group + } + interface CellsCapacity { + capacity: Capacity + blockHash: Hash256 + blockNumber: BlockNumber + } + interface BlockFilter { + data: api.HexString + hash: api.Hash + } + interface TransactionAndWitnessProof { + blockHash: Hash256 + transactionsProof: api.MerkleProof + witnessesProof: api.MerkleProof + } + type TransactionView = api.Transaction & { + hash: api.Hash + } + interface BlockView { + header: BlockHeader + uncles: UncleBlock[] + transactions: TransactionView[] + proposals: ProposalShortId[] + } + type SerializedBlock = api.HexString + interface FeeRateStatistics { + mean: UInt64 + median: UInt64 + } + interface EstimateCycles { + cycles: UInt64 + } + type DeploymentPos = api.DeploymentPos + type DeploymentState = api.DeploymentState + type DeploymentInfo = api.DeploymentInfo + type DeploymentsInfo = api.DeploymentsInfo + } + //# sourceMappingURL=api.d.ts.map +} diff --git a/packages/neuron-wallet/src/types/ckbRpc.d.ts b/packages/neuron-wallet/src/types/ckbRpc.d.ts new file mode 100644 index 0000000000..0fe9dcad1e --- /dev/null +++ b/packages/neuron-wallet/src/types/ckbRpc.d.ts @@ -0,0 +1,3 @@ +declare namespace CKBRPC { + type ScriptType = 'lock' | 'type' +} diff --git a/packages/neuron-wallet/src/types/rpc.d.ts b/packages/neuron-wallet/src/types/rpc.d.ts index 0fe9dcad1e..10a7333fa2 100644 --- a/packages/neuron-wallet/src/types/rpc.d.ts +++ b/packages/neuron-wallet/src/types/rpc.d.ts @@ -1,3 +1,458 @@ -declare namespace CKBRPC { - type ScriptType = 'lock' | 'type' +declare namespace RPC { + export type ProposalShortId = CKBComponents.ProposalShortId + export type Number = CKBComponents.Number + export type UInt32 = CKBComponents.UInt32 + export type Uint64 = CKBComponents.UInt64 + export type U256 = CKBComponents.U256 + export type Count = CKBComponents.Count + export type DAO = CKBComponents.DAO + export type Hash = CKBComponents.Hash + export type Hash256 = CKBComponents.Hash256 + export type Version = CKBComponents.Version + export type Capacity = CKBComponents.Capacity + export type Witness = CKBComponents.Witness + export type Bytes = CKBComponents.Bytes + export type Index = CKBComponents.Index + export type Since = CKBComponents.Since + export type Timestamp = CKBComponents.Timestamp + export type BlockNumber = CKBComponents.BlockNumber + export type EpochInHeader = string + export type Difficulty = CKBComponents.Difficulty + export type Cycles = CKBComponents.Cycles + export type Size = CKBComponents.Size + export type RationalU256 = CKBComponents.RationalU256 + export type ProposalWindow = CKBComponents.ProposalWindow + export type EpochNumberWithFraction = CKBComponents.EpochNumberWithFraction + export type JsonBytes = CKBComponents.JsonBytes + export type IOType = CKBComponents.IOType + export type EpochNumber = CKBComponents.EpochNumber + type MapLike = { + [key in K]?: V + } + type Vec = T[] + export enum TransactionStatus { + Pending = 'pending', + Proposed = 'proposed', + Committed = 'committed', + } + export type DepType = 'code' | 'dep_group' + export type ScriptHashType = CKBComponents.ScriptHashType + export interface Script { + args: Bytes + code_hash: Hash256 + hash_type: ScriptHashType + } + export interface OutPoint { + tx_hash: Hash256 + index: Index + } + export interface CellInput { + previous_output: OutPoint + since: Since + } + export interface CellOutput { + capacity: Capacity + lock: Script + type?: Script | undefined + } + export type Cell = CellOutput + export interface LiveCell { + data: { + content: Hash + hash: Hash256 + } + output: CellOutput + } + export interface CellDep { + out_point: OutPoint + dep_type: DepType + } + export interface CellIncludingOutPoint { + block_hash: Hash256 + capacity: Capacity + lock: Script + out_point: OutPoint + cellbase: boolean + output_data_len: string + } + export interface RawTransaction { + version: Version + cell_deps: CellDep[] + header_deps: Hash256[] + inputs: CellInput[] + outputs: CellOutput[] + witnesses: Witness[] + outputs_data: Bytes[] + } + export interface Transaction extends RawTransaction { + hash: Hash256 + } + export interface TransactionWithStatus { + transaction: Transaction + tx_status: + | { + block_hash: Hash256 + status: TransactionStatus.Committed + } + | { + block_hash: undefined + status: TransactionStatus.Pending | TransactionStatus.Proposed + } + time_added_to_pool: Uint64 | null + cycles: Uint64 | null + } + export interface TransactionPoint { + block_number: BlockNumber + index: Index + tx_hash: Hash256 + } + export interface TransactionByLockHash { + consumed_by: undefined | TransactionPoint + created_by: TransactionPoint + } + export type TransactionsByLockHash = TransactionByLockHash[] + export interface LiveCellByLockHash { + cell_output: CellOutput + created_by: TransactionPoint + cellbase: boolean + output_data_len: string + } + export type LiveCellsByLockHash = LiveCellByLockHash[] + export interface Header { + compact_target: Hash + dao: DAO + epoch: EpochInHeader + hash: Hash256 + number: BlockNumber + parent_hash: Hash256 + proposals_hash: Hash256 + nonce: CKBComponents.Nonce + timestamp: Timestamp + transactions_root: Hash256 + extra_hash: Hash256 + version: Version + } + export interface UncleBlock { + header: Header + proposals: ProposalShortId[] + } + export interface Block { + header: Header + uncles: UncleBlock[] + transactions: Transaction[] + proposals: ProposalShortId[] + extension?: JsonBytes | undefined + } + export interface AlertMessage { + id: string + priority: string + notice_until: Timestamp + message: string + } + /** + * @deprecated please migrate to {@link ChainInfo} + */ + export type BlockchainInfo = ChainInfo + export interface LocalNodeInfo { + active: boolean + addresses: Record<'address' | 'score', string>[] + connections: string + node_id: string + protocols: { + id: string + name: string + support_versions: string[] + }[] + version: string + } + export interface RemoteNodeInfo { + addresses: Record<'address' | 'score', string>[] + connected_duration: string + is_outbound: boolean + last_ping_duration: string + node_id: string + protocols: Record<'id' | 'version', string>[] + sync_state: Record< + | 'best_known_header_hash' + | 'best_known_header_number' + | 'can_fetch_count' + | 'inflight_count' + | 'last_common_header_hash' + | 'last_common_header_number' + | 'unknown_header_list_size', + string | undefined + > + version: string + } + export interface PeersState { + last_updated: string + blocks_in_flight: string + peer: string + } + export interface TxPoolInfo { + last_txs_updated_at: Timestamp + min_fee_rate: string + orphan: Count + pending: Count + proposed: Count + tip_hash: Hash256 + tip_number: BlockNumber + total_tx_cycles: Cycles + total_tx_size: Size + } + export interface Epoch { + compact_target: Hash + length: string + number: string + start_number: string + } + export interface LockHashIndexState { + block_hash: Hash256 + block_number: BlockNumber + lock_hash: Hash256 + } + export type LockHashIndexStates = LockHashIndexState[] + export interface BannedAddress { + address: string + ban_reason: string + ban_until: Timestamp + created_at: Timestamp + } + export type BannedAddresses = BannedAddress[] + export interface CellbaseOutputCapacityDetails { + primary: string + proposal_reward: string + secondary: string + total: string + tx_fee: string + } + export interface FeeRate { + fee_rate: string + } + export interface CapacityByLockHash { + block_number: BlockNumber + capacity: Capacity + cells_count: string + } + export interface BlockEconomicState { + finalized_at: string + issuance: { + primary: string + secondary: string + } + miner_reward: { + committed: string + primary: string + proposal: string + secondary: string + } + txs_fee: string + } + export interface SyncState { + best_known_block_number: string + best_known_block_timestamp: string + fast_time: string + ibd: boolean + inflight_blocks_count: string + low_time: string + normal_time: string + orphan_blocks_count: string + } + export interface TransactionProof { + block_hash: Hash + proof: { + indices: string[] + lemmas: Hash[] + } + witnesses_root: Hash + } + export type TxPoolIds = Record<'pending' | 'proposed', Array> + export interface TxVerbosity { + cycles: Cycles + size: Size + fee: Capacity + ancestors_size: Size + ancestors_cycles: Cycles + ancestors_count: Count + } + export type TxPoolVerbosity = Record<'pending' | 'proposed', Record> + export type RawTxPool = TxPoolIds | TxPoolVerbosity + export interface Consensus { + id: string + genesis_hash: Hash256 + dao_type_hash: Hash256 | undefined + secp256k1_blake160_sighash_all_type_hash: Hash256 | undefined + secp256k1_blake160_multisig_all_type_hash: Hash256 | undefined + initial_primary_epoch_reward: Capacity + secondary_epoch_reward: Capacity + max_uncles_num: string + orphan_rate_target: RationalU256 + epoch_duration_target: string + tx_proposal_window: ProposalWindow + proposer_reward_ratio: RationalU256 + cellbase_maturity: EpochNumberWithFraction + median_time_block_count: Count + max_block_cycles: Cycles + max_block_bytes: string + block_version: Version + tx_version: Version + type_id_code_hash: Hash256 + max_block_proposals_limit: string + primary_epoch_reward_halving_interval: string + permanent_difficulty_in_dummy: boolean + hardfork_features: HardForks + softforks: { + [key in DeploymentPos]?: SoftFork + } + } + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface HardForks extends Array {} + export interface HardforkFeature { + rfc: string + epoch_number: EpochNumber | null + } + export type SoftFork = Buried | Rfc0043 + export interface Buried { + status: SoftForkStatus + active: boolean + epoch: EpochNumber + } + export interface Rfc0043 { + status: SoftForkStatus + rfc0043: Deployment + } + export type SoftForkStatus = 'buried' | 'rfc0043' + export interface Ratio { + numer: Uint64 + denom: Uint64 + } + export interface Deployment { + bit: number + start: EpochNumber + timeout: EpochNumber + min_activation_epoch: EpochNumber + period: EpochNumber + threshold: Ratio + } + export interface Tip { + block_hash: Hash256 + block_number: BlockNumber + } + export interface IndexerCell { + block_number: BlockNumber + out_point: OutPoint + output: CellOutput + output_data: string + tx_index: string + } + export type IndexerTransaction = Grouped extends true + ? GroupedIndexerTransaction + : UngroupedIndexerTransaction + export interface UngroupedIndexerTransaction { + tx_hash: Hash256 + block_number: BlockNumber + io_index: string + io_type: IOType + tx_index: string + } + export interface GroupedIndexerTransaction { + tx_hash: Hash256 + block_number: BlockNumber + tx_index: string + cells: Array<[IOType, string]> + } + export interface GetTransactionsResult { + last_cursor: Hash256 + objects: IndexerTransaction[] + } + export interface GetLiveCellsResult { + last_cursor: Hash256 + objects: IndexerCell[] + } + export interface CellsCapacity { + capacity: Capacity + block_hash: Hash256 + block_number: BlockNumber + } + export type HexadecimalRange = [string, string] + export type ScriptType = 'type' | 'lock' + export type ScriptSearchMode = 'prefix' | 'exact' + export interface SearchFilter { + script?: Script + output_data_len_range?: HexadecimalRange + output_capacity_range?: HexadecimalRange + block_range?: HexadecimalRange + script_len_range?: HexadecimalRange + } + export interface SearchKey { + script: Script + script_type: ScriptType + filter?: SearchFilter + script_search_mode?: ScriptSearchMode + } + export interface GetCellsSearchKey extends SearchKey { + with_data?: boolean + } + export interface GetTransactionsSearchKey extends SearchKey { + group_by_transaction?: boolean + } + export interface BlockFilter { + data: HexString + hash: Hash + } + export interface TransactionAndWitnessProof { + block_hash: Hash + transactions_proof: MerkleProof + witnesses_proof: MerkleProof + } + export interface FeeRateStatistics { + mean: HexNumber + median: HexNumber + } + export type HeaderView = Header & { + hash: Hash + } + export interface UncleBlockView { + header: HeaderView + proposals: ProposalShortId[] + } + export type TransactionView = Transaction & { + hash: Hash + } + export interface BlockView { + header: HeaderView + uncles: UncleBlockView[] + transactions: TransactionView[] + proposals: ProposalShortId[] + } + export type SerializedBlock = HexString + export interface EstimateCycles { + cycles: HexNumber + } + export type DeploymentPos = 'testdummy' | 'light_client' + export type DeploymentState = 'defined' | 'started' | 'locked_in' | 'active' | 'failed' + export interface DeploymentsInfo { + hash: Hash256 + epoch: EpochNumber + deployments: MapLike + } + export interface DeploymentInfo { + bit: number + start: EpochNumber + timeout: EpochNumber + min_activation_epoch: EpochNumber + period: EpochNumber + threshold: Ratio + since: EpochNumber + state: DeploymentState + } + export interface ChainInfo { + chain: string + median_time: Timestamp + epoch: EpochNumberWithFraction + difficulty: U256 + is_initial_block_download: boolean + alerts: Vec + } + export {} } diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts index e4eefea410..f23e2bebda 100644 --- a/packages/neuron-wallet/src/utils/ckb-rpc.ts +++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts @@ -1,14 +1,14 @@ import { HexString } from '@ckb-lumos/base' -import CKBRPC from '@nervosnetwork/ckb-sdk-rpc' -import Method from '@nervosnetwork/ckb-sdk-rpc/lib/method' -import resultFormatter from '@nervosnetwork/ckb-sdk-rpc/lib/resultFormatter' -import paramsFormatter from '@nervosnetwork/ckb-sdk-rpc/lib/paramsFormatter' -import Base from '@nervosnetwork/ckb-sdk-rpc/lib/Base' +import { CKBRPC } from '@ckb-lumos/rpc' +import { Method } from '@ckb-lumos/rpc/lib/method' +import * as resultFormatter from '@ckb-lumos/rpc/lib/resultFormatter' +import { formatter as paramsFormatter } from '@ckb-lumos/rpc/lib/paramsFormatter' +import { Base } from '@ckb-lumos/rpc/lib/Base' import { MethodInBatchNotFoundException, PayloadInBatchException, IdNotMatchedInBatchException, -} from '@nervosnetwork/ckb-sdk-rpc/lib/exceptions' +} from '@ckb-lumos/rpc/lib/exceptions' import https from 'https' import http from 'http' import { request } from 'undici' @@ -137,6 +137,8 @@ export type FetchTransactionReturnType = { export class LightRPC extends Base { setScripts: (params: LightScriptFilter[]) => Promise getScripts: () => Promise + // TODO: the type is not the same as full node here + // @ts-ignore getTransactions: ( searchKey: { script: CKBComponents.Script; scriptType: CKBRPC.ScriptType; blockRange: [HexString, HexString] }, order: 'asc' | 'desc', @@ -164,7 +166,10 @@ export class LightRPC extends Base { }) Object.keys(this.rpcProperties).forEach(name => { - this.addMethod({ name, ...this.rpcProperties[name] }) + // don't add default getTransactions method + if (name !== 'getTransactions') { + this.addMethod({ name, ...this.rpcProperties[name] }) + } }) this.setScripts = new Method(this.node, { name: 'setScripts', ...lightRPCProperties['setScripts'] }).call diff --git a/packages/neuron-wallet/src/utils/signWitnesses.ts b/packages/neuron-wallet/src/utils/signWitnesses.ts new file mode 100644 index 0000000000..0efade4466 --- /dev/null +++ b/packages/neuron-wallet/src/utils/signWitnesses.ts @@ -0,0 +1,46 @@ +import { bytes, number } from '@ckb-lumos/codec' +import { serializeWitnessArgs } from './serialization' +import { CKBHasher } from '@ckb-lumos/base/lib/utils' +import { key } from '@ckb-lumos/hd' + +type StructuredWitness = CKBComponents.WitnessArgs | CKBComponents.Witness + +// https://github.com/nervosnetwork/ckb-system-scripts/wiki/How-to-sign-transaction#signing +export const signWitnesses = ({ + witnesses, + transactionHash, + privateKey, +}: { + witnesses: StructuredWitness[] + transactionHash: string + privateKey: string +}): StructuredWitness[] => { + if (witnesses.length === 0) { + throw new Error('witnesses cannot be empty') + } + if (typeof witnesses[0] !== 'object') { + throw new Error('The first witness in the group should be type of WitnessArgs') + } + + const emptyWitness = { + ...witnesses[0], + lock: `0x${'00'.repeat(65)}`, + } + const serializedEmptyWitnessBytes = bytes.bytify(serializeWitnessArgs(emptyWitness)) + const serializedEmptyWitnessSize = serializedEmptyWitnessBytes.byteLength + + const hasher = new CKBHasher() + hasher.update(transactionHash) + hasher.update(number.Uint64LE.pack(serializedEmptyWitnessSize)) + hasher.update(serializedEmptyWitnessBytes) + + witnesses.slice(1).forEach(witness => { + const witnessBytes = bytes.bytify(typeof witness === 'string' ? witness : serializeWitnessArgs(witness)) + hasher.update(number.Uint64LE.pack(witnessBytes.byteLength)) + hasher.update(witnessBytes) + }) + const message = hasher.digestHex() + + emptyWitness.lock = key.signRecoverable(message, privateKey) + return [serializeWitnessArgs(emptyWitness), ...witnesses.slice(1)] +} diff --git a/packages/neuron-wallet/tests/services/tx-wallet.test.ts b/packages/neuron-wallet/tests/services/tx-wallet.test.ts index d9730351e1..95ebaaa9ad 100644 --- a/packages/neuron-wallet/tests/services/tx-wallet.test.ts +++ b/packages/neuron-wallet/tests/services/tx-wallet.test.ts @@ -3,8 +3,8 @@ import Keystore from '../../src/models/keys/keystore' import Keychain from '../../src/models/keys/keychain' import { mnemonicToSeedSync } from '../../src/models/keys/mnemonic' import { ExtendedPrivateKey, AccountExtendedPublicKey } from '../../src/models/keys/key' -import CKB from '@nervosnetwork/ckb-sdk-core' import TransactionSender from '../../src/services/transaction-sender' +import { signWitnesses } from '../../src/utils/signWitnesses' describe('sign witness', () => { const witness = { @@ -19,8 +19,8 @@ describe('sign witness', () => { ] it('success', () => { - const ckb = new CKB('') - const newWitness = ckb.signWitnesses(privateKey)({ + const newWitness = signWitnesses({ + privateKey, witnesses: [witness], transactionHash: txHash, }) diff --git a/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts index 38d05acf02..d49154203d 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts @@ -132,11 +132,16 @@ jest.doMock('services/hardware', () => ({ }), })) -jest.doMock('@nervosnetwork/ckb-sdk-core', () => { - return function () { - return { - calculateDaoMaximumWithdraw: stubbedCalculateDaoMaximumWithdraw, - } +jest.doMock('@ckb-lumos/rpc', () => { + return { + CKBRPC: class CKBRPC { + url: string + constructor(url: string) { + this.url = url + } + + calculateDaoMaximumWithdraw = stubbedCalculateDaoMaximumWithdraw + }, } }) diff --git a/packages/neuron-wallet/tests/services/tx/transaction-service.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-service.test.ts index f198f60abe..3d34af281d 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-service.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-service.test.ts @@ -33,18 +33,20 @@ jest.mock('../../../src/services/rpc-service', () => { }) const ckbRpcExecMock = jest.fn() +jest.mock('@ckb-lumos/rpc', () => { + return { + CKBRPC: class CKBRPC { + url: string + constructor(url: string) { + this.url = url + } -jest.mock('@nervosnetwork/ckb-sdk-core', () => { - return function () { - return { - rpc: { - createBatchRequest() { - return { - exec: ckbRpcExecMock, - } - }, - }, - } + createBatchRequest() { + return { + exec: ckbRpcExecMock, + } + } + }, } }) diff --git a/yarn.lock b/yarn.lock index 5acc5f4844..97b1acf864 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1373,7 +1373,7 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.22.5": +"@babel/runtime@^7.20.6": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== @@ -1449,97 +1449,101 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@ckb-lumos/base@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/base/-/base-0.20.0-alpha.3.tgz#5cc257b708e4fc894f11028ec46616fb4750fc49" - integrity sha512-O0fycJlBPZQdCAma2KrQdBAP2O0UxaTwczwPgw2lPRqSlE5wdXigJYcN8mdsnWd8YycaKr1FUb2j0hPpVwflog== +"@ckb-lumos/base@0.21.0-next.1", "@ckb-lumos/base@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/base/-/base-0.21.0-next.1.tgz#ad82d0147c479b84bc0e80c3e32ad6a39ee75285" + integrity sha512-dJL3pqa28oJcNfYLNOuWRIJCeWhUq9l7l2/SZItb9wIT4MX5pD0Z1tA8/s9qbvwmmsab+QGKaLCHXNNeOvS0hg== dependencies: - "@ckb-lumos/bi" "0.20.0-alpha.3" - "@ckb-lumos/codec" "0.20.0-alpha.3" - "@ckb-lumos/toolkit" "0.20.0-alpha.3" + "@ckb-lumos/bi" "0.21.0-next.1" + "@ckb-lumos/codec" "0.21.0-next.1" + "@ckb-lumos/toolkit" "0.21.0-next.1" "@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/bi@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.20.0-alpha.3.tgz#7c20776558d33b838e54550be22ff8eed909a3e4" - integrity sha512-/sGxpnkESboLQyT17UEni4Qw5DnpXw8hjuckAfiG8Hopv2fIaDdrDup613AGzmAkaWMr2Z4DK6tP6x0qR7feSw== +"@ckb-lumos/bi@0.21.0-next.1", "@ckb-lumos/bi@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.21.0-next.1.tgz#357b8da31882ab5e3e3f8aca54c30dd576e4544d" + integrity sha512-FaMKBbtr826upcEVYt9/K/hutxHySD21t7ut5yv8lfj6LYcg6hWjtrbCM2INVV6/8HatfAf8YV2KsNRaluyt3A== dependencies: jsbi "^4.1.0" -"@ckb-lumos/ckb-indexer@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/ckb-indexer/-/ckb-indexer-0.20.0-alpha.3.tgz#6d20f59b197d90ff83f8c73508416019a49a4865" - integrity sha512-kjcpEEq3cWytEcTg52/8WFn5LnXu41IL0ZevTdTk2Nl8ul3VPbuyJa1dwmJZb0uahye3kNb/dtqmE7xOKXbYuA== +"@ckb-lumos/ckb-indexer@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/ckb-indexer/-/ckb-indexer-0.21.0-next.1.tgz#bc2fd6fcd2c482040565c255d7a9f8fd9269b462" + integrity sha512-ftT8Rodv/oXIOZLfsXEOLQnHy05c8Wj3h6QasXPlZlRGMdtWrThGz+XVI6lz4VSIfVMPSbwnYT+Dv7bf8fDfdw== dependencies: - "@ckb-lumos/base" "0.20.0-alpha.3" - "@ckb-lumos/bi" "0.20.0-alpha.3" - "@ckb-lumos/rpc" "0.20.0-alpha.3" - "@ckb-lumos/toolkit" "0.20.0-alpha.3" + "@ckb-lumos/base" "0.21.0-next.1" + "@ckb-lumos/bi" "0.21.0-next.1" + "@ckb-lumos/codec" "0.21.0-next.1" + "@ckb-lumos/rpc" "0.21.0-next.1" + "@ckb-lumos/toolkit" "0.21.0-next.1" cross-fetch "^3.1.5" events "^3.3.0" -"@ckb-lumos/codec@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.20.0-alpha.3.tgz#e177efc177cc9200c793b52281ab5bdf350b40ef" - integrity sha512-d+Au9tPjV3SKHIVJ/YG0ScpMPLrt1fCUwm16QTm0qcZuBljLdsSow8PyxmLWhHEatiQV13TuR9ZcOHxdufX7+A== +"@ckb-lumos/codec@0.21.0-next.1", "@ckb-lumos/codec@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.21.0-next.1.tgz#a75f5331026039799afb4f3419bea388e220d26c" + integrity sha512-b1w4wbIAbuYngNTKtu6np93EYgnmM4tb6NGdaYN0vZ3kyunlODkLWyRHyMo+FGeBdWQjBwBbmxGNyXwDxtTEGQ== dependencies: - "@ckb-lumos/bi" "0.20.0-alpha.3" + "@ckb-lumos/bi" "0.21.0-next.1" -"@ckb-lumos/config-manager@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/config-manager/-/config-manager-0.20.0-alpha.3.tgz#160b02e46074b75bfd6065b5eeb9546739649e88" - integrity sha512-NUF6p313dbqUhoxt+J/nTF3fkLb5Tg7PPu8oknBdpi3GudeSJfEX4SuenTqLh5kaely39dSRjB0adHXnDvFzpQ== +"@ckb-lumos/config-manager@0.21.0-next.1", "@ckb-lumos/config-manager@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/config-manager/-/config-manager-0.21.0-next.1.tgz#927e21a5d9450155ebf766422a707fd3b0856a14" + integrity sha512-G8CO+q1RH/Gt8ou8p/N99AUh5hIdU+MZcTZHwABOa4CLbXk2xFenRfeGhHv4u4ddYZ3SLx1zND7pSnbImmrh2A== dependencies: - "@ckb-lumos/base" "0.20.0-alpha.3" - "@ckb-lumos/bi" "0.20.0-alpha.3" - "@ckb-lumos/codec" "0.20.0-alpha.3" + "@ckb-lumos/base" "0.21.0-next.1" + "@ckb-lumos/bi" "0.21.0-next.1" + "@ckb-lumos/codec" "0.21.0-next.1" "@types/deep-freeze-strict" "^1.1.0" deep-freeze-strict "^1.1.1" -"@ckb-lumos/hd@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/hd/-/hd-0.20.0-alpha.3.tgz#608bded07aed41f71bda40e4f66af365a9ab18db" - integrity sha512-ladOBv4CPdW3q6fZorMIBpavzKII12ClAs7jvjtub8/CPziJAdTW0o62UFBvP1zt51eG0nP/NObuHzAos3pJnw== +"@ckb-lumos/hd@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/hd/-/hd-0.21.0-next.1.tgz#ffa4b80b974187982bbfa280138ca1dab0b2df0e" + integrity sha512-gISrSs4OWoBVecRnYMfjYQc83aE0Khjjs1KmAkAg1J53PWGeU3kjbUQSCHjF6poFL5ylEARX9vOKixRfm6nktg== dependencies: - "@ckb-lumos/base" "0.20.0-alpha.3" - "@ckb-lumos/bi" "0.20.0-alpha.3" + "@ckb-lumos/base" "0.21.0-next.1" + "@ckb-lumos/bi" "0.21.0-next.1" bn.js "^5.1.3" elliptic "^6.5.4" scrypt-js "^3.0.1" sha3 "^2.1.3" uuid "^8.3.0" -"@ckb-lumos/helpers@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/helpers/-/helpers-0.20.0-alpha.3.tgz#17e715664b871fdb0b20cc9d146b0ccb3a7e288e" - integrity sha512-KGOwxWLP5ut+3RJ0qv974Fpy/KPWtJydlFaRPgkBlpJYfhrds809CO/27oAw8R8TZm+tg+ovu+9Qq75l2PNkwA== +"@ckb-lumos/helpers@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/helpers/-/helpers-0.21.0-next.1.tgz#f0f2d9e231f5a266d53df0c60e7b7610b87e10b5" + integrity sha512-lSvn2L97be7IlONFTdjjz+/jG6QlpEGyETyrcSfJxeOOtgjicPFaLXLnaTBIt/IElRZ2ZpclbTFvSNcbVOvKdQ== dependencies: - "@ckb-lumos/base" "0.20.0-alpha.3" - "@ckb-lumos/bi" "0.20.0-alpha.3" - "@ckb-lumos/config-manager" "0.20.0-alpha.3" - "@ckb-lumos/toolkit" "0.20.0-alpha.3" + "@ckb-lumos/base" "0.21.0-next.1" + "@ckb-lumos/bi" "0.21.0-next.1" + "@ckb-lumos/codec" "0.21.0-next.1" + "@ckb-lumos/config-manager" "0.21.0-next.1" + "@ckb-lumos/toolkit" "0.21.0-next.1" bech32 "^2.0.0" - immutable "^4.0.0-rc.12" + immutable "^4.3.0" -"@ckb-lumos/rpc@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/rpc/-/rpc-0.20.0-alpha.3.tgz#e1c7d1aaaaedfca1d7da75aac699ffd2fa060cc5" - integrity sha512-Y2tEFhPNNiPeYOknm4ymsoyz9vbXUxMeGnszIOzCckvMQ2/go/0qJCYqOLhUho+ETPC3DGqlurRRc6Y4NMk6zQ== +"@ckb-lumos/rpc@0.21.0-next.1", "@ckb-lumos/rpc@^0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/rpc/-/rpc-0.21.0-next.1.tgz#f31610fc1713ae63e25475d3653ee8cc3eeabc0f" + integrity sha512-6IjnME2wGg1rmVnajQ7CTBqbLnXkdNqRERRmnD1J9EnoHBc+onSYSfkC58ZCVCOe0xZIR2vNKmOUQ++dmlKKiQ== dependencies: - "@ckb-lumos/base" "0.20.0-alpha.3" - "@ckb-lumos/bi" "0.20.0-alpha.3" + "@ckb-lumos/base" "0.21.0-next.1" + "@ckb-lumos/bi" "0.21.0-next.1" "@vespaiach/axios-fetch-adapter" "^0.3.1" axios "0.27.2" tslib "2.3.1" -"@ckb-lumos/toolkit@0.20.0-alpha.3": - version "0.20.0-alpha.3" - resolved "https://registry.yarnpkg.com/@ckb-lumos/toolkit/-/toolkit-0.20.0-alpha.3.tgz#17ccd898abf00a601a58d4776031bc8de0df7a8e" - integrity sha512-mDwrYcu34o1rRd1YhWKW7ye0SK+9cQRyB/LnxXE7L/8iNIl9JgaIUGRjIoQr2+OPxzUxorvCEwgh1WzywNrZmQ== +"@ckb-lumos/toolkit@0.21.0-next.1": + version "0.21.0-next.1" + resolved "https://registry.yarnpkg.com/@ckb-lumos/toolkit/-/toolkit-0.21.0-next.1.tgz#f8ce7b5fa5b388f1870efd309a114f3df2e74c61" + integrity sha512-CBnRM8Y0P6TV0FjACmaAz2YcPiIlcwJlU1tXDtcpzp4fcGL4uSMTcrJwFqWB8OE2s12EfN4+d0Ie4bn8/uq1Fg== + dependencies: + "@ckb-lumos/bi" "0.21.0-next.1" "@cnakazawa/watch@^1.0.3": version "1.0.4" @@ -4563,7 +4567,7 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@17.0.62", "@types/react@>=16", "@types/react@^16", "@types/react@^17": +"@types/react@*", "@types/react@17.0.62", "@types/react@>=16", "@types/react@^17": version "17.0.62" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.62.tgz#2efe8ddf8533500ec44b1334dd1a97caa2f860e3" integrity sha512-eANCyz9DG8p/Vdhr0ZKST8JV12PhH2ACCDYlFw6DIO+D+ca+uP4jtEDEpVqXZrh/uZdXQGwk7whJa3ah5DtyLw== @@ -4572,6 +4576,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^16": + version "16.14.49" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.49.tgz#79347898927bf72b758237b2da1c11efce50894d" + integrity sha512-WHKMS4fIlDpeLVKCGDs5k1MTCyqh1tyFhGqouSFgpPsCsWNDTtiMpTYUcJnHg66kp03ubqb4BFjd5+7gS3MyHw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -10502,10 +10515,10 @@ 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: - version "4.3.2" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.2.tgz#f89d910f8dfb6e15c03b2cae2faaf8c1f66455fe" - integrity sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA== +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== import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -15262,12 +15275,12 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-i18next@12.1.5, react-i18next@>=11.16.4: - version "13.2.2" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.2.2.tgz#b1e78ed66a54f4bc819616f68b98221e1b1a1936" - integrity sha512-+nFUkbRByFwnrfDcYqvzBuaeZb+nACHx+fAWN/pZMddWOCJH5hoc21+Sa/N/Lqi6ne6/9wC/qRGOoQhJa6IkEQ== +react-i18next@12.1.5: + version "12.1.5" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.1.5.tgz#b65f5733dd2f96188a9359c009b7dbe27443f009" + integrity sha512-7PQAv6DA0TcStG96fle+8RfTwxVbHVlZZJPoEszwUNvDuWpGldJmNWa3ZPesEsZQZGF6GkzwvEh6p57qpFD2gQ== dependencies: - "@babel/runtime" "^7.22.5" + "@babel/runtime" "^7.20.6" html-parse-stringify "^3.0.1" react-inspector@^6.0.0, react-inspector@^6.0.1: @@ -17817,7 +17830,7 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -usb@1.8.8, usb@^1.7.0: +usb@^1.7.0: version "1.8.8" resolved "https://registry.yarnpkg.com/usb/-/usb-1.8.8.tgz#54de33f9e57dc4efc1b5b5f72b6624a275775e80" integrity sha512-xpRAoek268RE3ATqK8l6LjrF4ADHn/A3V3cXEFbYo3/D83ZCLSO0A5tFKO093F4w5IbDfBVlB9VsYzoGz6EJGw== From ba66f8d3f8256181fc107317e20d75bd563138b2 Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 30 Oct 2023 10:20:30 +0900 Subject: [PATCH 06/12] ci: use key locker to sign neuron for windows (#2913) --- .github/workflows/package.yml | 35 ++++++++++++++- .github/workflows/package_for_test.yml | 47 ++++++++++++++------ packages/neuron-wallet/electron-builder.yml | 3 ++ packages/neuron-wallet/scripts/customSign.js | 16 +++++++ 4 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 packages/neuron-wallet/scripts/customSign.js diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 04fdd175e4..b10ba13461 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -51,6 +51,39 @@ jobs: env: ACTIONS_ALLOW_UNSECURE_COMMANDS: "true" + - name: Setup Certificate + if: matrix.os == 'windows-2019' + run: | + echo "${{ secrets.SM_CLIENT_CERT_FILE_BASE64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 + shell: bash + + - name: Set variables + if: matrix.os == 'windows-2019' + run: | + echo "SM_KEYPAIR_NAME=${{ secrets.SM_KEYPAIR_ALIAS }}" >> "$GITHUB_ENV" + echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV" + echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV" + echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH + echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH + echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH + shell: bash + + - name: Setting up the client tools + if: ${{ matrix.os == 'windows-2019' && env.SM_API_KEY != '' }} + run: | + curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi + msiexec /i smtools-windows-x64.msi /quiet /qn + C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user + shell: cmd + + - name: Certificates Sync + if: ${{ matrix.os == 'windows-2019' && env.SM_API_KEY != '' }} + run: | + smctl windows certsync + shell: cmd + - name: Install libudev if: matrix.os == 'ubuntu-20.04' run: | @@ -88,8 +121,6 @@ jobs: bash ./scripts/release.sh win env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CSC_LINK: ${{ secrets.WIN_CERTIFICATE_BASE64 }} - CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }} - name: Package for Linux if: matrix.os == 'ubuntu-20.04' diff --git a/.github/workflows/package_for_test.yml b/.github/workflows/package_for_test.yml index 1a014d36c8..4b446bbf54 100644 --- a/.github/workflows/package_for_test.yml +++ b/.github/workflows/package_for_test.yml @@ -62,6 +62,39 @@ jobs: env: ACTIONS_ALLOW_UNSECURE_COMMANDS: "true" + - name: Setup Certificate + if: matrix.os == 'windows-2019' + run: | + echo "${{ secrets.SM_CLIENT_CERT_FILE_BASE64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 + shell: bash + + - name: Set variables + if: matrix.os == 'windows-2019' + run: | + echo "SM_KEYPAIR_NAME=${{ secrets.SM_KEYPAIR_ALIAS }}" >> "$GITHUB_ENV" + echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV" + echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV" + echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH + echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH + echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH + shell: bash + + - name: Setting up the client tools + if: ${{ matrix.os == 'windows-2019' && env.SM_API_KEY != '' }} + run: | + curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi + msiexec /i smtools-windows-x64.msi /quiet /qn + C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user + shell: cmd + + - name: Certificates Sync + if: ${{ matrix.os == 'windows-2019' && env.SM_API_KEY != '' }} + run: | + smctl windows certsync + shell: cmd + - name: Install libudev if: matrix.os == 'ubuntu-20.04' run: | @@ -101,19 +134,7 @@ jobs: SKIP_NOTARIZE: true - name: Package for Windows - if: ${{ matrix.os == 'windows-2019' && env.WIN_CERTIFICATE_BASE64 != '' }} - run: | - bash ./scripts/download-ckb.sh win - yarn build - bash ./scripts/copy-ui-files.sh - bash ./scripts/package-for-test.sh win - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CSC_LINK: ${{ secrets.WIN_CERTIFICATE_BASE64 }} - CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }} - - - name: Package for Windows for skip code sign - if: ${{ matrix.os == 'windows-2019' && env.WIN_CERTIFICATE_BASE64 == '' }} + if: matrix.os == 'windows-2019' run: | bash ./scripts/download-ckb.sh win yarn build diff --git a/packages/neuron-wallet/electron-builder.yml b/packages/neuron-wallet/electron-builder.yml index 6318166a0c..629d6abcb4 100644 --- a/packages/neuron-wallet/electron-builder.yml +++ b/packages/neuron-wallet/electron-builder.yml @@ -49,6 +49,9 @@ win: - target: nsis arch: - x64 + sign: scripts/customSign.js + signingHashAlgorithms: + - sha256 mac: artifactName: "${productName}-v${version}-${os}-${arch}.${ext}" diff --git a/packages/neuron-wallet/scripts/customSign.js b/packages/neuron-wallet/scripts/customSign.js new file mode 100644 index 0000000000..e2288b578e --- /dev/null +++ b/packages/neuron-wallet/scripts/customSign.js @@ -0,0 +1,16 @@ +const { execSync } = require('node:child_process') + +exports.default = async configuration => { + if (!process.env.SM_API_KEY) { + console.info(`Skip signing because SM_API_KEY and not configured`) + return + } + + if (!configuration.path) { + throw new Error(`Path of application is not found`) + } + + execSync(`smctl sign --keypair-alias="${process.env.SM_KEYPAIR_NAME}" --input "${String(configuration.path)}"`, { + stdio: 'inherit', + }) +} From a89b06f49e0997ffa820e99cc9cbae324d6af76e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:49:34 +0900 Subject: [PATCH 07/12] chore(deps): bump undici from 5.22.1 to 5.26.2 (#2879) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Keith --- packages/neuron-wallet/package.json | 2 +- packages/neuron-wallet/src/utils/ckb-rpc.ts | 4 ++ .../neuron-wallet/src/utils/rpc-request.ts | 12 +++-- yarn.lock | 52 +++++++------------ 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 0038035ff3..d6aa6252de 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -71,7 +71,7 @@ "sqlite3": "5.1.6", "subleveldown": "4.1.4", "typeorm": "0.2.45", - "undici": "5.22.1", + "undici": "5.26.2", "uuid": "8.3.2" }, "devDependencies": { diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts index f23e2bebda..a1abfb8eb8 100644 --- a/packages/neuron-wallet/src/utils/ckb-rpc.ts +++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts @@ -335,6 +335,10 @@ export class LightRPC extends Base { }) const batchRes = await res.body.json() + if (!Array.isArray(batchRes)) { + return [] + } + return batchRes.map((res: any, i: number) => { if (res.id !== payload[i].id) { return new IdNotMatchedInBatchException(i, payload[i].id, res.id) diff --git a/packages/neuron-wallet/src/utils/rpc-request.ts b/packages/neuron-wallet/src/utils/rpc-request.ts index 2ed650a0e5..56bbd7babc 100644 --- a/packages/neuron-wallet/src/utils/rpc-request.ts +++ b/packages/neuron-wallet/src/utils/rpc-request.ts @@ -23,7 +23,10 @@ export const rpcRequest = async ( throw new Error(`indexer request failed with HTTP code ${res.statusCode}`) } const body = await res.body.json() - return body?.result as T + if (body !== null && typeof body === 'object' && 'result' in body) { + return body?.result as T + } + return [] as T } export const rpcBatchRequest = async ( @@ -50,8 +53,11 @@ export const rpcBatchRequest = async ( if (res.statusCode !== 200) { throw new Error(`indexer request failed with HTTP code ${res.statusCode}`) } - const responseBody: { id: number; error?: any; result: any }[] = await res.body.json() - return responseBody.sort((a, b) => a.id - b.id) + const responseBody = await res.body.json() + if (Array.isArray(responseBody) && responseBody.every(i => 'id' in i)) { + return responseBody.sort((a, b) => a.id - b.id) + } + return [] } export default { diff --git a/yarn.lock b/yarn.lock index 97b1acf864..e2a6d28b10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1373,7 +1373,7 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.20.6": +"@babel/runtime@^7.22.5": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== @@ -1875,6 +1875,11 @@ resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== +"@fastify/busboy@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" + integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== + "@fluentui/date-time-utilities@^7.9.1": version "7.9.1" resolved "https://registry.yarnpkg.com/@fluentui/date-time-utilities/-/date-time-utilities-7.9.1.tgz#bb486dc0a0fff33ef5803adabbf95e2cbf4be7be" @@ -4567,7 +4572,7 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@17.0.62", "@types/react@>=16", "@types/react@^17": +"@types/react@*", "@types/react@17.0.62", "@types/react@>=16", "@types/react@^16", "@types/react@^17": version "17.0.62" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.62.tgz#2efe8ddf8533500ec44b1334dd1a97caa2f860e3" integrity sha512-eANCyz9DG8p/Vdhr0ZKST8JV12PhH2ACCDYlFw6DIO+D+ca+uP4jtEDEpVqXZrh/uZdXQGwk7whJa3ah5DtyLw== @@ -4576,15 +4581,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^16": - version "16.14.49" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.49.tgz#79347898927bf72b758237b2da1c11efce50894d" - integrity sha512-WHKMS4fIlDpeLVKCGDs5k1MTCyqh1tyFhGqouSFgpPsCsWNDTtiMpTYUcJnHg66kp03ubqb4BFjd5+7gS3MyHw== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -6383,13 +6379,6 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" -busboy@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" - integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== - dependencies: - streamsearch "^1.1.0" - byte-size@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.1.tgz#3424608c62d59de5bfda05d31e0313c6174842ae" @@ -15275,12 +15264,12 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-i18next@12.1.5: - version "12.1.5" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.1.5.tgz#b65f5733dd2f96188a9359c009b7dbe27443f009" - integrity sha512-7PQAv6DA0TcStG96fle+8RfTwxVbHVlZZJPoEszwUNvDuWpGldJmNWa3ZPesEsZQZGF6GkzwvEh6p57qpFD2gQ== +react-i18next@12.1.5, react-i18next@>=11.16.4: + version "13.3.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.3.1.tgz#9b072bf4dd4cafb028e92315a8a1415f8034bdca" + integrity sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og== dependencies: - "@babel/runtime" "^7.20.6" + "@babel/runtime" "^7.22.5" html-parse-stringify "^3.0.1" react-inspector@^6.0.0, react-inspector@^6.0.1: @@ -16751,11 +16740,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - string-argv@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" @@ -17628,12 +17612,12 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -undici@5.22.1: - version "5.22.1" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" - integrity sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw== +undici@5.26.2: + version "5.26.2" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.2.tgz#fa61bfe40f732540d15e58b0c1271872d8e3c995" + integrity sha512-a4PDLQgLTPHVzOK+x3F79/M4GtyYPl+aX9AAK7aQxpwxDwCqkeZCScy7Gk5kWT3JtdFq1uhO3uZJdLtHI4dK9A== dependencies: - busboy "^1.6.0" + "@fastify/busboy" "^2.0.0" unfetch@^4.2.0: version "4.2.0" @@ -17830,7 +17814,7 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -usb@^1.7.0: +usb@1.8.8, usb@^1.7.0: version "1.8.8" resolved "https://registry.yarnpkg.com/usb/-/usb-1.8.8.tgz#54de33f9e57dc4efc1b5b5f72b6624a275775e80" integrity sha512-xpRAoek268RE3ATqK8l6LjrF4ADHn/A3V3cXEFbYo3/D83ZCLSO0A5tFKO093F4w5IbDfBVlB9VsYzoGz6EJGw== From f31c4057b053c84168bbf5515fdd7a7dcf954db4 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Mon, 30 Oct 2023 13:49:51 +0800 Subject: [PATCH 08/12] feat: upgrade "Compare checksums" action (#2917) --- .github/workflows/check_checksums.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_checksums.yml b/.github/workflows/check_checksums.yml index 3a8558ffd7..a0ab07480c 100644 --- a/.github/workflows/check_checksums.yml +++ b/.github/workflows/check_checksums.yml @@ -31,7 +31,7 @@ jobs: body="${body//'%'/'%25'}" body="${body//$'\n'/'%0A'}" body="${body//$'\r'/'%0D'}" - echo ::set-output name=body::$body + echo "body=$body" >> $GITHUB_OUTPUT - uses: peter-evans/commit-comment@v2 with: From e94fe9d33bc9bc177f9d2c3f7620df43f8bcc2aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:43:53 +0800 Subject: [PATCH 09/12] Update ckb client versions (#2919) feat: update ckb client versions Co-authored-by: Keith-CY --- .ckb-light-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ckb-light-version b/.ckb-light-version index f82e0685d9..268b0334e6 100644 --- a/.ckb-light-version +++ b/.ckb-light-version @@ -1 +1 @@ -v0.2.4 +v0.3.0 From 743fbd43966e324578a927d48e8970e26be6cf56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Tue, 7 Nov 2023 22:00:57 +0800 Subject: [PATCH 10/12] feat: Adapt switch internal network type, and notify the user network type is not running external. (#2921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 严国宇 <841185308@qq.com> Co-authored-by: Keith --- .../src/components/DataSetting/hooks.ts | 10 +- .../src/components/DataSetting/index.tsx | 42 ++---- .../src/components/HDWalletSign/index.tsx | 4 +- .../components/HistoryDetailPage/index.tsx | 7 +- .../src/components/MultisigAddress/index.tsx | 4 +- .../NervosDAODetail/CellsCard/index.tsx | 7 +- .../components/NetworkEditorDialog/index.tsx | 5 +- .../src/components/NetworkSetting/index.tsx | 59 ++++++-- .../NetworkSetting/networkSetting.module.scss | 12 ++ .../src/components/PageContainer/index.tsx | 4 +- .../neuron-ui/src/components/Receive/hooks.ts | 2 +- .../components/SUDTReceiveDialog/index.tsx | 2 +- .../src/components/SpecialAssetList/index.tsx | 4 +- .../neuron-ui/src/containers/Main/hooks.ts | 73 ++++++++- .../neuron-ui/src/containers/Main/index.tsx | 85 +++++++++-- .../src/containers/Main/main.module.scss | 27 ++++ .../neuron-ui/src/containers/Navbar/index.tsx | 20 ++- packages/neuron-ui/src/locales/en.json | 13 +- packages/neuron-ui/src/locales/zh-tw.json | 13 +- packages/neuron-ui/src/locales/zh.json | 13 +- packages/neuron-ui/src/services/localCache.ts | 13 +- packages/neuron-ui/src/services/remote/app.ts | 2 +- .../src/services/remote/remoteApiWrapper.ts | 2 +- .../stateProvider/actionCreators/app.ts | 5 + packages/neuron-ui/src/types/App/index.d.ts | 2 +- packages/neuron-ui/src/utils/const.ts | 13 +- .../neuron-ui/src/utils/getNetworkLabel.ts | 7 +- packages/neuron-ui/src/utils/is.ts | 4 +- .../neuron-ui/src/widgets/Icons/Switch.svg | 3 + packages/neuron-ui/src/widgets/Icons/icon.tsx | 4 + .../src/widgets/RadioGroup/index.tsx | 30 ++-- packages/neuron-wallet/electron-builder.yml | 18 ++- .../src/block-sync-renderer/index.ts | 5 +- .../sync/indexer-connector.ts | 5 +- .../sync/light-connector.ts | 17 +-- .../src/block-sync-renderer/sync/queue.ts | 11 +- .../src/block-sync-renderer/task.ts | 3 +- .../block-sync-renderer/tx-status-listener.ts | 8 +- packages/neuron-wallet/src/controllers/api.ts | 14 +- .../src/controllers/export-debug.ts | 8 +- .../neuron-wallet/src/controllers/mercury.ts | 2 +- .../src/controllers/networks/chain-info.ts | 2 +- .../src/controllers/networks/index.ts | 12 +- .../src/controllers/offline-sign.ts | 5 +- .../neuron-wallet/src/controllers/sync-api.ts | 24 +-- .../neuron-wallet/src/controllers/wallets.ts | 4 +- packages/neuron-wallet/src/models/network.ts | 6 +- .../src/models/system-script-info.ts | 17 ++- .../neuron-wallet/src/services/ckb-runner.ts | 32 +++- .../src/services/hardware/ledger.ts | 4 +- .../neuron-wallet/src/services/indexer.ts | 6 +- .../src/services/light-runner.ts | 63 ++++---- .../neuron-wallet/src/services/networks.ts | 98 ++++++++++--- packages/neuron-wallet/src/services/node.ts | 67 ++++----- .../neuron-wallet/src/services/rpc-service.ts | 5 +- .../neuron-wallet/src/services/settings.ts | 48 +++--- .../src/services/transaction-sender.ts | 19 +-- .../src/services/tx/transaction-generator.ts | 5 +- .../src/services/tx/transaction-service.ts | 4 +- packages/neuron-wallet/src/utils/ckb-rpc.ts | 11 +- packages/neuron-wallet/src/utils/const.ts | 6 + .../src/utils/get-usable-port.ts | 41 ++++++ packages/neuron-wallet/src/utils/toml.ts | 33 +++++ .../tests/block-sync-renderer/task.test.ts | 4 +- .../tests/controllers/offline-sign.test.ts | 12 ++ .../tests/controllers/sync-api.test.ts | 29 +--- .../models/keys/hd-public-key-info.test.ts | 2 +- .../models/synced-block-number.intg.test.ts | 14 +- .../tests/services/ckb-runner.test.ts | 53 ++++++- .../tests/services/indexer.test.ts | 24 ++- .../tests/services/light-runner.test.ts | 92 ++++++++++-- .../tests/services/networks.test.ts | 138 ++++++++++++++++-- .../neuron-wallet/tests/services/node.test.ts | 72 ++++----- .../tests/services/setting.test.ts | 32 +++- .../services/tx/transaction-generator.test.ts | 6 +- .../services/tx/transaction-sender.test.ts | 13 ++ .../tests/services/wallets.test.ts | 15 +- .../neuron-wallet/tests/utils/ckb-rpc.test.ts | 7 +- .../tests/utils/get-usable-port.test.ts | 58 ++++++++ .../neuron-wallet/tests/utils/toml/test.toml | 28 ++++ .../tests/utils/toml/toml.test.ts | 68 +++++++++ scripts/download-ckb.sh | 9 +- 82 files changed, 1309 insertions(+), 461 deletions(-) create mode 100644 packages/neuron-ui/src/containers/Main/main.module.scss create mode 100644 packages/neuron-ui/src/widgets/Icons/Switch.svg create mode 100644 packages/neuron-wallet/src/utils/get-usable-port.ts create mode 100644 packages/neuron-wallet/src/utils/toml.ts create mode 100644 packages/neuron-wallet/tests/utils/get-usable-port.test.ts create mode 100644 packages/neuron-wallet/tests/utils/toml/test.toml create mode 100644 packages/neuron-wallet/tests/utils/toml/toml.test.ts diff --git a/packages/neuron-ui/src/components/DataSetting/hooks.ts b/packages/neuron-ui/src/components/DataSetting/hooks.ts index 57b0ff0680..586ea9858f 100644 --- a/packages/neuron-ui/src/components/DataSetting/hooks.ts +++ b/packages/neuron-ui/src/components/DataSetting/hooks.ts @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { getCkbNodeDataPath, @@ -7,11 +7,11 @@ import { stopProcessMonitor, setCkbNodeDataPath, } from 'services/remote' -import { isSuccessResponse, useDidMount } from 'utils' +import { isSuccessResponse } from 'utils' const type = 'ckb' -export const useDataPath = () => { +export const useDataPath = (network?: State.Network) => { const [t] = useTranslation() const [isSaving, setIsSaving] = useState(false) const [savingType, setSavingType] = useState() @@ -20,13 +20,13 @@ export const useDataPath = () => { const [isDialogOpen, setIsDialogOpen] = useState(false) const [faidMessage, setFaidMessage] = useState('') - useDidMount(() => { + useEffect(() => { getCkbNodeDataPath().then(res => { if (isSuccessResponse(res)) { setPrevPath(res.result!) } }) - }) + }, [network?.id]) const onSetting = useCallback(() => { invokeShowOpenDialog({ buttonLabel: t('settings.data.set', { lng: navigator.language }), diff --git a/packages/neuron-ui/src/components/DataSetting/index.tsx b/packages/neuron-ui/src/components/DataSetting/index.tsx index 7e83d99684..4b3d9709c6 100644 --- a/packages/neuron-ui/src/components/DataSetting/index.tsx +++ b/packages/neuron-ui/src/components/DataSetting/index.tsx @@ -1,15 +1,13 @@ -import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react' +import React, { useCallback, useRef, useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from 'widgets/Button' import ClearCache from 'components/ClearCache' import { useDispatch, useState as useGlobalState } from 'states' import { shell } from 'electron' -import { getIsCkbRunExternal } from 'services/remote' -import { isSuccessResponse } from 'utils' import Tooltip from 'widgets/Tooltip' import Dialog from 'widgets/Dialog' import AlertDialog from 'widgets/AlertDialog' -import { LIGHT_NETWORK_TYPE } from 'utils/const' +import { NetworkType } from 'utils/const' import { Attention } from 'widgets/Icons/icon' import { useDataPath } from './hooks' @@ -42,6 +40,12 @@ const PathItem = ({ const DataSetting = () => { const dispatch = useDispatch() const [t] = useTranslation() + const resyncRef = useRef(null) + const { + chain: { networkID }, + settings: { networks = [] }, + } = useGlobalState() + const network = useMemo(() => networks.find(n => n.id === networkID), [networkID, networks]) const { onSetting, prevPath, @@ -53,38 +57,20 @@ const DataSetting = () => { savingType, faidMessage, setFaidMessage, - } = useDataPath() - - const resyncRef = useRef(null) + } = useDataPath(network) const openPath = useCallback(() => { if (prevPath) { shell.openPath(prevPath!) } }, [prevPath]) - const [isCkbRunExternal, setIsCkbRunExternal] = useState() - useEffect(() => { - getIsCkbRunExternal().then(res => { - if (isSuccessResponse(res)) { - setIsCkbRunExternal(res.result ?? false) - } else { - // ignore - } - }) - }, []) - const { - chain: { networkID }, - settings: { networks = [] }, - } = useGlobalState() - const isLightClient = useMemo( - () => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE, - [networkID, networks] - ) + const isLightClient = network?.type === NetworkType.Light + const hiddenDataPath = isLightClient || !network?.readonly return ( <>
- {isLightClient ? null : ( + {hiddenDataPath ? null : (
{t('settings.data.ckb-node-data')}
{
- {isLightClient ? null : ( - - )} + {hiddenDataPath ? null : } { const [t] = useTranslation() @@ -33,7 +33,7 @@ const HDWalletSign = ({ tx }: { tx: State.DetailedTransaction }) => { throw new Error('Cannot find current network in the network list') } - setIsMainnet(network.chain === MAINNET_TAG) + setIsMainnet(MAINNET_CLIENT_LIST.includes(network.chain)) } }) .catch(err => console.warn(err)) diff --git a/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx b/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx index dd7da563f3..8e7297a42f 100644 --- a/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx +++ b/packages/neuron-ui/src/components/HistoryDetailPage/index.tsx @@ -16,18 +16,16 @@ import Tooltip from 'widgets/Tooltip' import { ErrorCode, - CONSTANTS, localNumberFormatter, uniformTimeFormatter, shannonToCKBFormatter, isSuccessResponse, + isMainnet as isMainnetUtil, } from 'utils' import { HIDE_BALANCE } from 'utils/const' import styles from './historyDetailPage.module.scss' -const { MAINNET_TAG } = CONSTANTS - type InputOrOutputType = (State.DetailedInput | State.DetailedOutput) & { idx: number } const InfoItem = ({ label, value, className }: { label: string; value: React.ReactNode; className?: string }) => ( @@ -46,8 +44,7 @@ const HistoryDetailPage = () => { settings: { networks }, wallet: currentWallet, } = useGlobalState() - const network = networks.find(n => n.id === networkID) - const isMainnet = network != null && network.chain === MAINNET_TAG + const isMainnet = isMainnetUtil(networks, networkID) const [t] = useTranslation() const [transaction, setTransaction] = useState(transactionState) const [error, setError] = useState({ code: '', message: '' }) diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index 01119e2767..e8f61a250c 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -26,7 +26,7 @@ import { ReactComponent as Transfer } from 'widgets/Icons/Transfer.svg' import { ReactComponent as Upload } from 'widgets/Icons/Upload.svg' import { ReactComponent as Edit } from 'widgets/Icons/Edit.svg' import { Download, Search } from 'widgets/Icons/icon' -import { HIDE_BALANCE, LIGHT_NETWORK_TYPE } from 'utils/const' +import { HIDE_BALANCE, NetworkType } from 'utils/const' import { onEnter } from 'utils/inputDevice' import { useSearch, useConfigManage, useExportConfig, useActions, useSubscription } from './hooks' @@ -62,7 +62,7 @@ const MultisigAddress = () => { } = useGlobalState() const isMainnet = isMainnetUtil(networks, networkID) const isLightClient = useMemo( - () => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE, + () => networks.find(n => n.id === networkID)?.type === NetworkType.Light, [networks, networkID] ) const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) diff --git a/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx b/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx index 3ab5e8df22..d6f8041f8c 100644 --- a/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAODetail/CellsCard/index.tsx @@ -1,11 +1,11 @@ import React, { FC, useMemo, useState } from 'react' import Tabs, { VariantProps } from 'widgets/Tabs' -import { clsx, localNumberFormatter, shannonToCKBFormatter } from 'utils' +import { clsx, localNumberFormatter, shannonToCKBFormatter, isMainnet as isMainnetUtils } from 'utils' import { useTranslation } from 'react-i18next' import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils' import { onEnter } from 'utils/inputDevice' import { EyesClose, EyesOpen } from 'widgets/Icons/icon' -import { HIDE_BALANCE, MAINNET_TAG } from 'utils/const' +import { HIDE_BALANCE } from 'utils/const' import ScriptTag from 'components/ScriptTag' import LockInfoDialog from 'components/LockInfoDialog' import { useState as useGlobalState } from 'states' @@ -24,8 +24,7 @@ const TabsVariantWithCellsCard: FC< chain: { networkID }, settings: { networks }, } = useGlobalState() - const network = networks.find(n => n.id === networkID) - const isMainnet = network != null && network.chain === MAINNET_TAG + const isMainnet = isMainnetUtils(networks, networkID) const [isPrivacyMode, setIsPrivacyMode] = useState(false) const [showingLockInfo, setShowingLockInfo] = useState(null) diff --git a/packages/neuron-ui/src/components/NetworkEditorDialog/index.tsx b/packages/neuron-ui/src/components/NetworkEditorDialog/index.tsx index 83dd80c5f9..a636923b7e 100644 --- a/packages/neuron-ui/src/components/NetworkEditorDialog/index.tsx +++ b/packages/neuron-ui/src/components/NetworkEditorDialog/index.tsx @@ -12,10 +12,12 @@ const NetworkEditorDialog = ({ onCancel, id, onSuccess, + url, }: { onCancel: () => void id: 'new' | string onSuccess: () => void + url?: string }) => { const { settings: { networks = [] }, @@ -31,7 +33,7 @@ const NetworkEditorDialog = ({ const [editor, setEditor] = useState({ name: '', nameError: '', - url: '', + url: url ?? '', urlError: '', }) const [isUpdating, setIsUpdating] = useState(false) @@ -115,6 +117,7 @@ const NetworkEditorDialog = ({ error={editor.urlError} placeholder={t('settings.network.edit-network.input-rpc')} autoFocus + disabled={!!url} /> { @@ -43,7 +46,6 @@ const NetworkSetting = ({ chain = chainState, settings: { networks = [] } }: Sta break } default: - // @ts-ignore } }, [setNetId, setShowEditorDialog, setShowDeleteDialog] @@ -75,20 +77,55 @@ const NetworkSetting = ({ chain = chainState, settings: { networks = [] } }: Sta } }, [setShowEditorDialog, setNotice, netId]) + const onSwitchNetworkType = useCallback>(() => { + const selectedNetwork = networks.find(v => v.id === currentId) + const switchNetwork = networks.find(v => v.type === selectedNetwork?.type && v.id !== currentId) + if (switchNetwork) { + setCurrentNetwork(switchNetwork.id) + lastShowInternalNodeIds.save(switchNetwork.type, switchNetwork.id) + } + }, [currentId, networks]) + + const showNetworks = useMemo(() => { + const internalLightNodeId = lastShowInternalNodeIds.get(NetworkType.Light) + return networks.filter(v => v.type !== NetworkType.Light || v.id === internalLightNodeId) + }, [currentId, networks]) + return (
({ + options={showNetworks.map(network => ({ value: network.id, - label: ( -
-

{`${network.name} (${network.remote})`}

-
{t(getNetworkLabelI18nkey(network.chain))}
-
- ), + label: + currentId === network.id && network.type === NetworkType.Light ? ( +
+

{`${network.name} (${network.remote})`}

+ + {t('settings.network.switch-network-type', { + type: network.chain === LIGHT_CLIENT_MAINNET ? 'testnet' : 'mainnet', + })} + + } + placement="top" + showTriangle + > +
+ {t(getNetworkLabelI18nkey(network.chain))} + +
+
+
+ ) : ( +
+

{`${network.name} (${network.remote})`}

+
{t(getNetworkLabelI18nkey(network.chain))}
+
+ ), suffix: (
{network.readonly ? null : ( diff --git a/packages/neuron-ui/src/components/NetworkSetting/networkSetting.module.scss b/packages/neuron-ui/src/components/NetworkSetting/networkSetting.module.scss index 3fbdf69edd..25024fd5ec 100644 --- a/packages/neuron-ui/src/components/NetworkSetting/networkSetting.module.scss +++ b/packages/neuron-ui/src/components/NetworkSetting/networkSetting.module.scss @@ -25,6 +25,18 @@ background: var(--table-head-background-color); border: 1px solid var(--divide-line-color); border-radius: 40px; + display: flex; + align-items: center; + + & > svg { + margin-left: 4px; + } + } + .switchBtn { + border: none; + background-color: transparent; + cursor: pointer; + color: var(--main-text-color); } } diff --git a/packages/neuron-ui/src/components/PageContainer/index.tsx b/packages/neuron-ui/src/components/PageContainer/index.tsx index 6237ba4ad4..b90cbc6575 100755 --- a/packages/neuron-ui/src/components/PageContainer/index.tsx +++ b/packages/neuron-ui/src/components/PageContainer/index.tsx @@ -15,7 +15,7 @@ import SyncStatusComponent from 'components/SyncStatus' import { AppActions, useDispatch, useState as useGlobalState } from 'states' import { openExternal } from 'services/remote' import Tooltip from 'widgets/Tooltip' -import { LIGHT_NETWORK_TYPE } from 'utils/const' +import { NetworkType } from 'utils/const' import Dialog from 'widgets/Dialog' import TextField from 'widgets/TextField' @@ -59,7 +59,7 @@ const PageContainer: React.FC = props => { const { theme, onSetTheme } = useTheme() const network = useMemo(() => networks.find(n => n.id === networkID), [networks, networkID]) const isLightClient = useMemo( - () => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE, + () => networks.find(n => n.id === networkID)?.type === NetworkType.Light, [networkID, networks] ) const netWorkTypeLabel = useMemo(() => (network ? getNetworkLabelI18nkey(network.chain) : ''), [network]) diff --git a/packages/neuron-ui/src/components/Receive/hooks.ts b/packages/neuron-ui/src/components/Receive/hooks.ts index 5b2ddcbde1..416b695b4b 100644 --- a/packages/neuron-ui/src/components/Receive/hooks.ts +++ b/packages/neuron-ui/src/components/Receive/hooks.ts @@ -32,7 +32,7 @@ export const useCopyAndDownloadQrCode = () => { const toShortAddr = (addr: string) => { try { const script = addressToScript(addr) - const isMainnet = addr.startsWith('ckb') + const isMainnet = addr.startsWith(AddressPrefix.Mainnet) return bech32Address(script.args, { prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet }) } catch { return '' diff --git a/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx b/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx index ac2dddda05..371e2db970 100644 --- a/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTReceiveDialog/index.tsx @@ -16,7 +16,7 @@ const { DEFAULT_SUDT_FIELDS } = CONSTANTS const toShortAddr = (addr: string) => { try { const script = addressToScript(addr) - const isMainnet = addr.startsWith('ckb') + const isMainnet = addr.startsWith(AddressPrefix.Mainnet) return bech32Address(script.args, { prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, codeHashOrCodeHashIndex: '0x02', diff --git a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx index 9b4c804427..136c5e43cb 100644 --- a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx +++ b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx @@ -26,7 +26,7 @@ import { getExplorerUrl, ConnectionStatus, } from 'utils' -import { LIGHT_NETWORK_TYPE, HIDE_BALANCE } from 'utils/const' +import { NetworkType, HIDE_BALANCE } from 'utils/const' import useGetCountDownAndFeeRateStats from 'utils/hooks/useGetCountDownAndFeeRateStats' import { ControllerResponse } from 'services/remote/remoteApiWrapper' import SUDTUpdateDialog, { SUDTUpdateDialogProps } from 'components/SUDTUpdateDialog' @@ -146,7 +146,7 @@ const SpecialAssetList = () => { const { suggestFeeRate } = useGetCountDownAndFeeRateStats() const isMainnet = isMainnetUtil(networks, networkID) const isLightClient = useMemo( - () => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE, + () => networks.find(n => n.id === networkID)?.type === NetworkType.Light, [networkID, networks] ) const foundTokenInfo = tokenInfoList.find(token => token.tokenID === accountToClaim?.account.tokenID) diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 2049222fdd..b332e2ca9d 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -1,5 +1,5 @@ -import { useEffect, useCallback } from 'react' -import { useLocation, NavigateFunction } from 'react-router-dom' +import { useEffect, useCallback, useState } from 'react' +import { useLocation, NavigateFunction, useNavigate } from 'react-router-dom' import { NeuronWalletActions, StateDispatch, AppActions } from 'states/stateProvider/reducer' import { updateTransactionList, @@ -10,7 +10,7 @@ import { showGlobalAlertDialog, } from 'states/stateProvider/actionCreators' -import { getCurrentWallet, getWinID } from 'services/remote' +import { getCurrentWallet, getWinID, setCurrentNetwork, startNodeIgnoreExternal } from 'services/remote' import { DataUpdate as DataUpdateSubject, NetworkList as NetworkListSubject, @@ -105,6 +105,7 @@ export const useSubscription = ({ navigate, dispatch, location, + showSwitchNetwork, }: { walletID: string chain: State.Chain @@ -112,6 +113,7 @@ export const useSubscription = ({ navigate: NavigateFunction location: ReturnType dispatch: StateDispatch + showSwitchNetwork: () => void }) => { const { pageNo, pageSize, keywords } = chain.transactions @@ -203,6 +205,9 @@ export const useSubscription = ({ type: NeuronWalletActions.UpdateConnectionStatus, payload: getConnectionStatus({ ...status, isTimeout: Date.now() > CONNECTING_DEADLINE }), }) + if (status.connected && status.isBundledNode && !status.startedBundledNode) { + showSwitchNetwork() + } } }) @@ -308,7 +313,67 @@ export const useSubscription = ({ commandSubscription.unsubscribe() showGlobalDialogSubject.unsubscribe() } - }, [walletID, pageNo, pageSize, keywords, isAllowedToFetchList, navigate, dispatch, location.pathname]) + }, [ + walletID, + pageNo, + pageSize, + keywords, + isAllowedToFetchList, + navigate, + dispatch, + location.pathname, + showSwitchNetwork, + ]) +} + +export const useCheckNode = (networks: State.Network[], networkID: string) => { + const [isSwitchNetworkShow, setIsSwitchNetworkShow] = useState(false) + const [selectNetwork, setSelectNetwork] = useState(networks[0]?.id) + useEffect(() => { + if (isSwitchNetworkShow) { + setSelectNetwork(networks[0]?.id) + } + }, [networks, isSwitchNetworkShow]) + const [showEditorDialog, setShowEditorDialog] = useState(false) + const onConfirm = useCallback(() => { + if (selectNetwork) { + setCurrentNetwork(selectNetwork) + setIsSwitchNetworkShow(false) + } + }, [selectNetwork]) + const onCloseEditorDialog = useCallback(() => { + setShowEditorDialog(false) + }, []) + const onOpenEditorDialog = useCallback(() => { + setShowEditorDialog(true) + }, []) + const navigate = useNavigate() + const [networkIdWhenDialogShow, setNetworkIdWhenDialogShow] = useState() + useEffect(() => { + setNetworkIdWhenDialogShow(undefined) + }, [networkID]) + const showSwitchNetwork = useCallback(() => { + // if the use has not change network id, the dialog will only show once + if (networkIdWhenDialogShow !== networkID) { + setNetworkIdWhenDialogShow(networkID) + navigate(RoutePath.Settings) + setIsSwitchNetworkShow(true) + } + }, [networkID, networkIdWhenDialogShow]) + return { + selectNetwork, + onChangeSelected: setSelectNetwork, + isSwitchNetworkShow, + showSwitchNetwork, + onCancel: useCallback(() => { + setIsSwitchNetworkShow(false) + startNodeIgnoreExternal() + }, []), + onConfirm, + showEditorDialog, + onCloseEditorDialog, + onOpenEditorDialog, + } } export default { diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx index 7d77ae8242..9d60f521aa 100644 --- a/packages/neuron-ui/src/containers/Main/index.tsx +++ b/packages/neuron-ui/src/containers/Main/index.tsx @@ -6,7 +6,11 @@ import { useMigrate, useOnDefaultContextMenu, useOnLocaleChange } from 'utils' import AlertDialog from 'widgets/AlertDialog' import Dialog from 'widgets/Dialog' import Button from 'widgets/Button' -import { useSubscription, useSyncChainData, useOnCurrentWalletChange } from './hooks' +import RadioGroup from 'widgets/RadioGroup' +import NetworkEditorDialog from 'components/NetworkEditorDialog' +import { AddSimple } from 'widgets/Icons/icon' +import styles from './main.module.scss' +import { useSubscription, useSyncChainData, useOnCurrentWalletChange, useCheckNode } from './hooks' const MainContent = () => { const navigate = useNavigate() @@ -21,6 +25,29 @@ const MainContent = () => { const { networkID } = chain const [t, i18n] = useTranslation() + const network = useMemo(() => networks.find(n => n.id === networkID), [networks, networkID]) + + const sameUrlNetworks = useMemo( + () => networks.filter(v => v.remote === network?.remote && !v.readonly), + [network, networks] + ) + + useSyncChainData({ + chainURL: network?.remote ?? '', + dispatch, + }) + + const { + isSwitchNetworkShow, + showSwitchNetwork, + onCancel: onCloseSwitchNetwork, + onConfirm: onSwitchNetwork, + onChangeSelected, + showEditorDialog, + onCloseEditorDialog, + onOpenEditorDialog, + } = useCheckNode(sameUrlNetworks, networkID) + useSubscription({ walletID, chain, @@ -28,16 +55,7 @@ const MainContent = () => { navigate, dispatch, location, - }) - - const chainURL = useMemo(() => { - const network = networks.find(n => n.id === networkID) - return network ? network.remote : '' - }, [networks, networkID]) - - useSyncChainData({ - chainURL, - dispatch, + showSwitchNetwork, }) useOnCurrentWalletChange({ @@ -76,6 +94,51 @@ const MainContent = () => {
+ + {sameUrlNetworks.length ? ( + + {t('main.external-node-detected-dialog.body-tips-with-network')} + + ) : ( + t('main.external-node-detected-dialog.body-tips-without-network') + )} + {sameUrlNetworks.length ? ( + <> +
+ ({ + value: v.id, + label: `${v.name} (${v.remote})`, + }))} + inputIdPrefix="main-switch" + /> +
+
+ +
+ + ) : null} +
+ {showEditorDialog ? ( + + ) : null}
) } diff --git a/packages/neuron-ui/src/containers/Main/main.module.scss b/packages/neuron-ui/src/containers/Main/main.module.scss new file mode 100644 index 0000000000..0e01fceaa4 --- /dev/null +++ b/packages/neuron-ui/src/containers/Main/main.module.scss @@ -0,0 +1,27 @@ +.networkDialog { + width: 680px; + + .chooseNetworkTip { + color: var(--secondary-text-color); + } + + .networks { + max-height: 124px; + border-radius: 8px; + margin-top: 16px; + border: 1px solid var(--divide-line-color); + overflow-y: auto; + } + + .addNetwork { + text-align: center; + + & > button { + font-weight: 400; + } + + svg { + margin-right: 4px; + } + } +} diff --git a/packages/neuron-ui/src/containers/Navbar/index.tsx b/packages/neuron-ui/src/containers/Navbar/index.tsx index 8020a12b53..6c38bdbfe7 100644 --- a/packages/neuron-ui/src/containers/Navbar/index.tsx +++ b/packages/neuron-ui/src/containers/Navbar/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { useLocation, NavLink, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' @@ -63,8 +63,9 @@ const Navbar = () => { const dispatch = useDispatch() const neuronWallet = useGlobalState() const { + chain: { networkID }, wallet: { name }, - settings: { wallets = [] }, + settings: { wallets = [], networks = [] }, updater: { version, isUpdated }, } = neuronWallet const [t, i18n] = useTranslation() @@ -95,13 +96,16 @@ const Navbar = () => { const [verifyCkbResult, setVerifyCkbResult] = useState() + const network = useMemo(() => networks.find(n => n.id === networkID), [networkID, networks]) useEffect(() => { - verifyExternalCkbNode().then(res => { - if (isSuccessResponse(res) && res.result) { - setVerifyCkbResult(res.result) - } - }) - }, []) + if (!network?.readonly) { + verifyExternalCkbNode().then(res => { + if (isSuccessResponse(res) && res.result) { + setVerifyCkbResult(res.result) + } + }) + } + }, [network?.readonly]) useEffect(() => { // isUpdated is true or version is not empty means check update has return diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 4878419ed2..a60c6dee05 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -400,7 +400,9 @@ "mainnet": "Mainnet", "testnet": "Testnet", "lightTestnet": "Light Testnet", - "devnet": "Devnet" + "lightMainnet": "Light Mainnet", + "devnet": "Devnet", + "switch-network-type": "Switch to {{type}}" }, "locale": { "en": "English", @@ -1137,6 +1139,15 @@ "input-place-holder": "Please enter the start sync block number, eg: 10,100,101", "locate-first-tx": "Locate the first transaction", "view-block": "View the block" + }, + "main": { + "external-node-detected-dialog": { + "title": "External node detected", + "body-tips-without-network": "\"Internal Node\" is reserved for the built-in CKB node, please add a new network option for the external node.", + "body-tips-with-network": "\"Internal Node\" is reserved for the built-in CKB node, please select from the following network options or add a new one for the external node.", + "add-network": "Add Network", + "ignore-external-node": "Ignore external node" + } } } } diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index a1f2bde2c7..b8dc8731cf 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -394,7 +394,9 @@ "edit-success": "編輯網絡成功", "mainnet": "主網", "testnet": "測試網", - "devnet": "開發網" + "lightMainnet": "輕節點主網", + "devnet": "開發網", + "switch-network-type": "切換到 {{type}}" }, "locale": { "en": "English", @@ -1108,6 +1110,15 @@ "input-place-holder": "輸入同步起始區塊高度,例如:10,100,101", "locate-first-tx": "定位第一筆交易", "view-block": "查看區塊" + }, + "main": { + "external-node-detected-dialog": { + "title": "檢測到外部節點", + "body-tips-without-network": "\"Internal Node\" 僅用於連接內置節點,請添加新的網絡選項以連接外部節點。", + "body-tips-with-network": "\"Internal Node\" 僅用於連接內置節點,請選擇以下網絡選項或添加新的網絡選項以連接外部節點。", + "add-network": "添加網絡", + "ignore-external-node": "忽略外部節點" + } } } } diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 67d31df813..766a89bd10 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -393,7 +393,9 @@ "mainnet": "主网", "testnet": "测试网", "lightTestnet": "轻节点测试网", - "devnet": "开发网" + "lightMainnet": "轻节点主网", + "devnet": "开发网", + "switch-network-type": "切换到 {{type}}" }, "locale": { "en": "English", @@ -1129,6 +1131,15 @@ "input-place-holder": "输入同步起始区块高度,例如:10,100,101", "locate-first-tx": "定位第一笔交易", "view-block": "查看区块" + }, + "main": { + "external-node-detected-dialog": { + "title": "检测到外部节点", + "body-tips-without-network": "\"Internal Node\" 仅用于连接内置节点,请添加新的网络选项以连接外部节点。", + "body-tips-with-network": "\"Internal Node\" 仅用于连接内置节点,请选择以下网络选项或添加新的网络选项以连接外部节点。", + "add-network": "添加网络", + "ignore-external-node": "忽略外部节点" + } } } } diff --git a/packages/neuron-ui/src/services/localCache.ts b/packages/neuron-ui/src/services/localCache.ts index d134ce4934..02ae5b823e 100644 --- a/packages/neuron-ui/src/services/localCache.ts +++ b/packages/neuron-ui/src/services/localCache.ts @@ -1,4 +1,4 @@ -import { SYNC_REBUILD_SINCE_VERSION } from 'utils/const' +import { NetworkType, SYNC_REBUILD_SINCE_VERSION } from 'utils/const' export enum LocalCacheKey { Addresses = 'addresses', @@ -10,6 +10,7 @@ export enum LocalCacheKey { SyncRebuildNotification = 'syncRebuildNotification', LoadedWalletIDs = 'loadedWalletIDs', ImportedWallet = 'ImportedWallet', + ShownNodeId = 'ShownNodeId', } export const addresses = { @@ -154,3 +155,13 @@ export const importedWalletDialogShown = { } }, } + +export const lastShowInternalNodeIds = { + get: (type: NetworkType) => { + const savedNodeId = window.localStorage.getItem(`${type}_${LocalCacheKey.ShownNodeId}`) + return savedNodeId ?? networks.load().find(v => v.type === type)?.id + }, + save: (type: NetworkType, id: string) => { + window.localStorage.setItem(`${type}_${LocalCacheKey.ShownNodeId}`, id) + }, +} diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts index 65e69fa9ca..5a0c65ae4a 100644 --- a/packages/neuron-ui/src/services/remote/app.ts +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -22,7 +22,7 @@ export const setCkbNodeDataPath = remoteApi<{ dataPath: string; clearCache?: boo ) export const stopProcessMonitor = remoteApi<'ckb'>('stop-process-monitor') export const startProcessMonitor = remoteApi<'ckb'>('start-process-monitor') -export const getIsCkbRunExternal = remoteApi('is-ckb-run-external') +export const startNodeIgnoreExternal = remoteApi('start-node-ignore-external') export type VerifyExternalCkbNodeRes = | { diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index d8f798b382..2bb198db01 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -48,8 +48,8 @@ type Action = | 'start-process-monitor' | 'is-dark' | 'set-theme' - | 'is-ckb-run-external' | 'verify-external-ckb-node' + | 'start-node-ignore-external' // Wallets | 'get-all-wallets' | 'get-current-wallet' diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index bca989ac43..dd63438b57 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -9,6 +9,7 @@ import { currentWallet as currentWalletCache, currentNetworkID as currentNetworkIDCache, networks as networksCache, + lastShowInternalNodeIds, } from 'services/localCache' export const initAppState = () => (dispatch: StateDispatch, navigate: any) => { @@ -48,6 +49,10 @@ export const initAppState = () => (dispatch: StateDispatch, navigate: any) => { addressesCache.save(addresses) networksCache.save(networks) currentNetworkIDCache.save(currentNetworkID) + const currentNetwork = (networks as State.Network[]).find(v => v.id === currentNetworkID) + if (currentNetwork) { + lastShowInternalNodeIds.save(currentNetwork.type, currentNetworkID) + } } else { navigate(`${RoutePath.WalletWizard}${WalletWizardPath.Welcome}`) } diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index f9473e036b..043f51e3d1 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -175,7 +175,7 @@ declare namespace State { name: string remote: string chain: 'ckb' | 'ckb_testnet' | 'ckb_dev' | string - type: 0 | 1 | 2 + type: 0 | 1 | 2 // 0 for default node, 1 for full node, 2 for light client, ref: NetworkType in utils/const.ts genesisHash: string readonly: boolean } diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index abd90e982b..238c4be8b8 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -13,8 +13,10 @@ export const MAX_TIP_BLOCK_DELAY = 180000 export const BUFFER_BLOCK_NUMBER = 10 export const MAX_DECIMAL_DIGITS = 8 -export const MAINNET_TAG = 'ckb' -export const LIGHT_MAINNET_TAG = 'light_client_mainnet' +export const FULL_NODE_MAINNET = 'ckb' +export const LIGHT_CLIENT_MAINNET = 'light_client_mainnet' +export const LIGHT_CLIENT_TESTNET = 'light_client_testnet' +export const MAINNET_CLIENT_LIST = [FULL_NODE_MAINNET, LIGHT_CLIENT_MAINNET] export const MIN_DEPOSIT_AMOUNT = 102 export const TOKEN_ID_LENGTH = 66 @@ -67,8 +69,11 @@ export const DEPRECATED_CODE_HASH: Record = { } export const HIDE_BALANCE = '******' -export const LIGHT_CLIENT_TESTNET = 'light_client_testnet' -export const LIGHT_NETWORK_TYPE = 2 +export enum NetworkType { + Default, // internal full node + Normal, + Light, // internal Light node +} export const METHOD_NOT_FOUND = -32601 export const MAX_M_N_NUMBER = 255 diff --git a/packages/neuron-ui/src/utils/getNetworkLabel.ts b/packages/neuron-ui/src/utils/getNetworkLabel.ts index 29ac0d5e29..461adcb7fd 100644 --- a/packages/neuron-ui/src/utils/getNetworkLabel.ts +++ b/packages/neuron-ui/src/utils/getNetworkLabel.ts @@ -1,6 +1,6 @@ -import { LIGHT_CLIENT_TESTNET } from './const' +import { LIGHT_CLIENT_MAINNET, LIGHT_CLIENT_TESTNET } from './const' -export const getNetworkLabelI18nkey = (type: 'ckb' | 'ckb_testnet' | 'ckb_dev' | string) => { +export const getNetworkLabelI18nkey = (type: State.Network['chain']) => { switch (type) { case 'ckb': { return 'settings.network.mainnet' @@ -11,6 +11,9 @@ export const getNetworkLabelI18nkey = (type: 'ckb' | 'ckb_testnet' | 'ckb_dev' | case LIGHT_CLIENT_TESTNET: { return 'settings.network.lightTestnet' } + case LIGHT_CLIENT_MAINNET: { + return 'settings.network.lightMainnet' + } default: { return 'settings.network.devnet' } diff --git a/packages/neuron-ui/src/utils/is.ts b/packages/neuron-ui/src/utils/is.ts index 1975ddf3ba..f20ca58fad 100644 --- a/packages/neuron-ui/src/utils/is.ts +++ b/packages/neuron-ui/src/utils/is.ts @@ -8,11 +8,11 @@ import { PwAcpLockInfoOnMainNet, PwAcpLockInfoOnTestNet, } from 'utils/enums' -import { LIGHT_MAINNET_TAG, MAINNET_TAG } from './const' +import { MAINNET_CLIENT_LIST } from './const' export const isMainnet = (networks: Readonly, networkID: string) => { const network = networks.find(n => n.id === networkID) - return network?.chain === MAINNET_TAG || network?.chain === LIGHT_MAINNET_TAG + return MAINNET_CLIENT_LIST.includes(network?.chain ?? '') } export const isSuccessResponse = (res: Pick): res is SuccessFromController => { diff --git a/packages/neuron-ui/src/widgets/Icons/Switch.svg b/packages/neuron-ui/src/widgets/Icons/Switch.svg new file mode 100644 index 0000000000..d998334223 --- /dev/null +++ b/packages/neuron-ui/src/widgets/Icons/Switch.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx index b05e00b7cf..542cb2843d 100644 --- a/packages/neuron-ui/src/widgets/Icons/icon.tsx +++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx @@ -44,6 +44,8 @@ import { ReactComponent as OverviewSendSvg } from './OverviewSend.svg' import { ReactComponent as OverviewReceiveSvg } from './OverviewReceive.svg' import { ReactComponent as AddressbookSvg } from './Addressbook.svg' import { ReactComponent as AddSvg } from './Add.svg' +import { ReactComponent as AddSimpleSvg } from './AddSimple.svg' +import { ReactComponent as SwitchSvg } from './Switch.svg' import styles from './icon.module.scss' @@ -104,3 +106,5 @@ export const OverviewSend = WrapSvg(OverviewSendSvg) export const OverviewReceive = WrapSvg(OverviewReceiveSvg) export const Addressbook = WrapSvg(AddressbookSvg) export const Add = WrapSvg(AddSvg) +export const Switch = WrapSvg(SwitchSvg) +export const AddSimple = WrapSvg(AddSimpleSvg) diff --git a/packages/neuron-ui/src/widgets/RadioGroup/index.tsx b/packages/neuron-ui/src/widgets/RadioGroup/index.tsx index fb56c07757..1ca7197d1a 100644 --- a/packages/neuron-ui/src/widgets/RadioGroup/index.tsx +++ b/packages/neuron-ui/src/widgets/RadioGroup/index.tsx @@ -13,22 +13,32 @@ export interface RadioGroupProps { options: RadioGroupOptions[] onChange?: (arg: string | number) => void defaultValue?: string | number + value?: string | number itemClassName?: string className?: string + inputIdPrefix?: string } -const RadioGroup = ({ defaultValue, options, onChange, itemClassName = '', className = '' }: RadioGroupProps) => { - const [checkedValue, setCheckedValue] = useState(defaultValue || options[0]?.value) +const RadioGroup = ({ + defaultValue, + options, + onChange, + itemClassName = '', + className = '', + value, + inputIdPrefix = '', +}: RadioGroupProps) => { + const [checkedValue, setCheckedValue] = useState(defaultValue ?? options[0]?.value) const handleChange = useCallback( (e: React.SyntheticEvent) => { - const { value } = e.target as HTMLInputElement - if (value !== checkedValue) { - setCheckedValue(value) - onChange?.(value) + const { value: selectedValue } = e.target as HTMLInputElement + if (selectedValue !== value ?? checkedValue) { + setCheckedValue(selectedValue) + onChange?.(selectedValue) } }, - [onChange, checkedValue, setCheckedValue] + [onChange, checkedValue, setCheckedValue, value] ) return ( @@ -36,12 +46,12 @@ const RadioGroup = ({ defaultValue, options, onChange, itemClassName = '', class {options.map(item => (
-