--- /dev/null
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! 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<AccountList>
+export async function _accounts (accounts: AccountList, vault: Vault, from: unknown, to: unknown): Promise<AccountList> {
+ 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<ArrayBuffer>({
+ 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
+}
import { _sign } from './sign'\r
import { _unlock } from './unlock'\r
import { Vault } from '../vault'\r
+import { _accounts } from './accounts'\r
+import { _verify } from './verify'\r
+import { _unopened } from './unopened'\r
\r
/**\r
* Represents a wallet containing numerous Nano accounts derived from a single\r
get id (): string { return this.#id }\r
\r
/**\r
- * Method used to create wallet and derive accounts.\r
+ * Algorithm or device used to create wallet and derive accounts.\r
*/\r
get type (): WalletType { return this.#type }\r
\r
* ```\r
*\r
* If `from` is greater than `to`, their values will be swapped.\r
- * @param {number} from - Start index of accounts. Default: 0\r
- * @param {number} to - End index of accounts. Default: `from`\r
+ * @param {number} [from=0] - Start index of accounts. Default: 0\r
+ * @param {number} [to=from] - End index of accounts. Default: `from`\r
* @returns {AccountList} Promise for a list of Accounts at the specified indexes\r
*/\r
async accounts (from: number = 0, to: number = from): Promise<AccountList> {\r
- if (from > to) [from, to] = [to, from]\r
- const output = new AccountList()\r
- const indexes: number[] = []\r
- for (let i = from; i <= to; i++) {\r
- if (this.#accounts[i] == null) {\r
- indexes.push(i)\r
- } else {\r
- output[i] = this.#accounts[i]\r
- }\r
- }\r
- if (indexes.length > 0) {\r
- const promises = []\r
- for (const index of indexes) {\r
- promises.push(this.#vault.request<ArrayBuffer>({\r
- action: 'derive',\r
- index\r
- }))\r
- }\r
- const publicKeys: KeyPair[] = await Promise.all(promises)\r
- if (publicKeys.length > 0) {\r
- const publicAccounts = Account.load(publicKeys)\r
- for (const a of publicAccounts) {\r
- if (a.index == null) {\r
- throw new RangeError('Index missing for Account')\r
- }\r
- output[a.index] = this.#accounts[a.index] = a\r
- }\r
- }\r
- }\r
- return output\r
+ return await _accounts(this.#accounts, this.#vault, from, to)\r
}\r
\r
/**\r
* @returns {Promise<Account>} The lowest-indexed unopened account belonging to the wallet\r
*/\r
async unopened (rpc: Rpc, batchSize: number = ADDRESS_GAP, from: number = 0): Promise<Account> {\r
- if (!Number.isSafeInteger(batchSize) || batchSize < 1) {\r
- throw new RangeError(`Invalid batch size ${batchSize}`)\r
- }\r
- const accounts = await this.accounts(from, from + batchSize - 1)\r
- const addresses = []\r
- for (const a in accounts) {\r
- addresses.push(accounts[a].address)\r
- }\r
- const data = {\r
- "accounts": addresses\r
- }\r
- const { errors } = await rpc.call('accounts_frontiers', data)\r
- for (const key of Object.keys(errors ?? {})) {\r
- const value = errors[key]\r
- if (value === 'Account not found') {\r
- return Account.load(key)\r
- }\r
- }\r
- return await this.unopened(rpc, batchSize, from + batchSize)\r
+ return await _unopened(this, rpc, batchSize, from + batchSize)\r
}\r
\r
/**\r
*/\r
async verify (mnemonic: string): Promise<boolean>\r
async verify (secret: string): Promise<boolean> {\r
- try {\r
- const data: NamedData = {\r
- action: 'verify'\r
- }\r
- if (/^(?:[A-F0-9]{64}){1,2}$/i.test(secret)) {\r
- data.seed = hex.toBuffer(secret)\r
- } else if (/^([a-z]{3,8} ){11,23}[a-z]{3,8}$/i.test(secret)) {\r
- data.mnemonicPhrase = secret.toLowerCase()\r
- } else {\r
- throw new TypeError('Invalid format')\r
- }\r
- const result = await this.#vault.request<boolean>(data)\r
- const { isVerified } = result\r
- return isVerified\r
- } catch (err) {\r
- throw new Error('Failed to verify wallet', { cause: err })\r
- }\r
+ return await _verify(this.#vault, secret)\r
}\r
\r
static #isInternal: boolean = false\r
--- /dev/null
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! 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<Account>
+export async function _unopened (wallet: Wallet, rpc: unknown, batchSize: unknown, from: unknown): Promise<Account> {
+ 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)
+}
--- /dev/null
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! 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<boolean>
+export async function _verify (vault: Vault, secret: unknown): Promise<boolean> {
+ 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<boolean>(data)
+ const { isVerified } = result
+ return isVerified
+ } catch (err) {
+ throw new Error('Failed to verify wallet', { cause: err })
+ }
+}