From 8df2eecd9d9b32b46d04b6f73cd891c7fde6abcf Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 18 Aug 2025 21:37:42 -0700 Subject: [PATCH] Extract three more wallet methods to separate function modules. --- src/lib/wallet/accounts.ts | 43 +++++++++++++++++++++ src/lib/wallet/index.ts | 78 +++++--------------------------------- src/lib/wallet/unopened.ts | 38 +++++++++++++++++++ src/lib/wallet/verify.ts | 30 +++++++++++++++ 4 files changed, 120 insertions(+), 69 deletions(-) create mode 100644 src/lib/wallet/accounts.ts create mode 100644 src/lib/wallet/unopened.ts create mode 100644 src/lib/wallet/verify.ts diff --git a/src/lib/wallet/accounts.ts b/src/lib/wallet/accounts.ts new file mode 100644 index 0000000..7e0f77f --- /dev/null +++ b/src/lib/wallet/accounts.ts @@ -0,0 +1,43 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { Account, AccountList } from '../account' +import { Vault } from '../vault' +import { KeyPair } from '#types' + +export async function _accounts (accounts: AccountList, vault: Vault, from: number, to: number): Promise +export async function _accounts (accounts: AccountList, vault: Vault, from: unknown, to: unknown): Promise { + if (typeof from !== 'number' || typeof to !== 'number') { + throw new TypeError('Invalid account range', { cause: `${to}-${from}` }) + } + if (from > to) [from as number, to as number] = [to, from] + const output = new AccountList() + const indexes: number[] = [] + for (let i = from; i <= to; i++) { + if (accounts[i] == null) { + indexes.push(i) + } else { + output[i] = accounts[i] + } + } + if (indexes.length > 0) { + const promises = [] + for (const index of indexes) { + promises.push(vault.request({ + action: 'derive', + index + })) + } + const publicKeys: KeyPair[] = await Promise.all(promises) + if (publicKeys.length > 0) { + const publicAccounts = Account.load(publicKeys) + for (const a of publicAccounts) { + if (a.index == null) { + throw new RangeError('Index missing for Account') + } + output[a.index] = accounts[a.index] = a + } + } + } + return output +} diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index 8569b5b..3b8c458 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -17,6 +17,9 @@ import { Rpc } from '../rpc' import { _sign } from './sign' import { _unlock } from './unlock' import { Vault } from '../vault' +import { _accounts } from './accounts' +import { _verify } from './verify' +import { _unopened } from './unopened' /** * Represents a wallet containing numerous Nano accounts derived from a single @@ -122,7 +125,7 @@ export class Wallet { get id (): string { return this.#id } /** - * Method used to create wallet and derive accounts. + * Algorithm or device used to create wallet and derive accounts. */ get type (): WalletType { return this.#type } @@ -216,41 +219,12 @@ export class Wallet { * ``` * * If `from` is greater than `to`, their values will be swapped. - * @param {number} from - Start index of accounts. Default: 0 - * @param {number} to - End index of accounts. Default: `from` + * @param {number} [from=0] - Start index of accounts. Default: 0 + * @param {number} [to=from] - End index of accounts. Default: `from` * @returns {AccountList} Promise for a list of Accounts at the specified indexes */ async accounts (from: number = 0, to: number = from): Promise { - if (from > to) [from, to] = [to, from] - const output = new AccountList() - const indexes: number[] = [] - for (let i = from; i <= to; i++) { - if (this.#accounts[i] == null) { - indexes.push(i) - } else { - output[i] = this.#accounts[i] - } - } - if (indexes.length > 0) { - const promises = [] - for (const index of indexes) { - promises.push(this.#vault.request({ - action: 'derive', - index - })) - } - const publicKeys: KeyPair[] = await Promise.all(promises) - if (publicKeys.length > 0) { - const publicAccounts = Account.load(publicKeys) - for (const a of publicAccounts) { - if (a.index == null) { - throw new RangeError('Index missing for Account') - } - output[a.index] = this.#accounts[a.index] = a - } - } - } - return output + return await _accounts(this.#accounts, this.#vault, from, to) } /** @@ -335,25 +309,7 @@ export class Wallet { * @returns {Promise} The lowest-indexed unopened account belonging to the wallet */ async unopened (rpc: Rpc, batchSize: number = ADDRESS_GAP, from: number = 0): Promise { - if (!Number.isSafeInteger(batchSize) || batchSize < 1) { - throw new RangeError(`Invalid batch size ${batchSize}`) - } - const accounts = await this.accounts(from, from + batchSize - 1) - const addresses = [] - for (const a in accounts) { - addresses.push(accounts[a].address) - } - const data = { - "accounts": addresses - } - const { errors } = await rpc.call('accounts_frontiers', data) - for (const key of Object.keys(errors ?? {})) { - const value = errors[key] - if (value === 'Account not found') { - return Account.load(key) - } - } - return await this.unopened(rpc, batchSize, from + batchSize) + return await _unopened(this, rpc, batchSize, from + batchSize) } /** @@ -374,23 +330,7 @@ export class Wallet { */ async verify (mnemonic: string): Promise async verify (secret: string): Promise { - try { - const data: NamedData = { - action: 'verify' - } - if (/^(?:[A-F0-9]{64}){1,2}$/i.test(secret)) { - data.seed = hex.toBuffer(secret) - } else if (/^([a-z]{3,8} ){11,23}[a-z]{3,8}$/i.test(secret)) { - data.mnemonicPhrase = secret.toLowerCase() - } else { - throw new TypeError('Invalid format') - } - const result = await this.#vault.request(data) - const { isVerified } = result - return isVerified - } catch (err) { - throw new Error('Failed to verify wallet', { cause: err }) - } + return await _verify(this.#vault, secret) } static #isInternal: boolean = false diff --git a/src/lib/wallet/unopened.ts b/src/lib/wallet/unopened.ts new file mode 100644 index 0000000..0b31a3f --- /dev/null +++ b/src/lib/wallet/unopened.ts @@ -0,0 +1,38 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { Account } from '../account' +import { hex } from '../convert' +import { Rpc } from '../rpc' +import { Vault } from '../vault' +import { Wallet } from '#wallet' +import { NamedData } from '#types' + +export async function _unopened (wallet: Wallet, rpc: Rpc, batchSize: number, from: number): Promise +export async function _unopened (wallet: Wallet, rpc: unknown, batchSize: unknown, from: unknown): Promise { + if (!(rpc instanceof Rpc)) { + throw new TypeError('Invalid RPC endpoint', { cause: rpc }) + } + if (typeof batchSize !== 'number' || !Number.isSafeInteger(batchSize) || batchSize < 1) { + throw new TypeError('Invalid batch size', { cause: batchSize }) + } + if (typeof from !== 'number' || !Number.isSafeInteger(batchSize) || batchSize < 0) { + throw new TypeError('Invalid starting account index', { cause: from }) + } + const accounts = await wallet.accounts(from, from + batchSize - 1) + const addresses = [] + for (const a in accounts) { + addresses.push(accounts[a].address) + } + const data = { + "accounts": addresses + } + const { errors } = await rpc.call('accounts_frontiers', data) + for (const key of Object.keys(errors ?? {})) { + const value = errors[key] + if (value === 'Account not found') { + return Account.load(key) + } + } + return await _unopened(wallet, rpc, batchSize, from + batchSize) +} diff --git a/src/lib/wallet/verify.ts b/src/lib/wallet/verify.ts new file mode 100644 index 0000000..01526b7 --- /dev/null +++ b/src/lib/wallet/verify.ts @@ -0,0 +1,30 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { hex } from '../convert' +import { Vault } from '../vault' +import { NamedData } from '#types' + +export async function _verify (vault: Vault, secret: string): Promise +export async function _verify (vault: Vault, secret: unknown): Promise { + try { + if (typeof secret !== 'string') { + throw new TypeError('Wallet secret must be a string', { cause: typeof secret }) + } + const data: NamedData = { + action: 'verify' + } + if (/^(?:[A-F0-9]{64}){1,2}$/i.test(secret)) { + data.seed = hex.toBuffer(secret) + } else if (/^([a-z]{3,8} ){11,23}[a-z]{3,8}$/i.test(secret)) { + data.mnemonicPhrase = secret.toLowerCase() + } else { + throw new TypeError('Invalid format') + } + const result = await vault.request(data) + const { isVerified } = result + return isVerified + } catch (err) { + throw new Error('Failed to verify wallet', { cause: err }) + } +} -- 2.47.3