From a3fff6b7b862b9da54004b67373b03585d5533b1 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Tue, 19 Aug 2025 07:51:00 -0700 Subject: [PATCH] Extract wallet refresh to separate module and refactor to use batch RPC endpoints. --- src/lib/wallet/index.ts | 19 +++------------ src/lib/wallet/refresh.ts | 43 ++++++++++++++++++++++++++++++++++ test/test.refresh-accounts.mjs | 19 +++++++++++---- 3 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 src/lib/wallet/refresh.ts diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index fc85a6f..6f7d202 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -3,6 +3,7 @@ import { KeyPair, NamedData, WalletType } from '#types' import { Account, AccountList } from '../account' +import { _accounts } from './accounts' import { _backup } from './backup' import { Block } from '../block' import { ADDRESS_GAP } from '../constants' @@ -12,12 +13,12 @@ import { Database } from '../database' import { _get } from './get' import { _load } from './load' import { _lock } from './lock' +import { _refresh } from './refresh' import { _restore } from './restore' 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' @@ -261,21 +262,7 @@ export class Wallet { * @returns {Promise} Accounts with updated balances, frontiers, and representatives */ async refresh (rpc: Rpc | string | URL, from: number = 0, to: number = from): Promise { - if (typeof rpc === 'string' || rpc instanceof URL) { - rpc = new Rpc(rpc) - } - if (!(rpc instanceof Rpc)) { - throw new TypeError('RPC must be a valid node') - } - const accounts = await this.accounts(from, to) - for (const a in accounts) { - try { - await accounts[a].refresh(rpc) - } catch (err) { - delete accounts[a] - } - } - return accounts + return await _refresh(this, rpc, from, to) } /** diff --git a/src/lib/wallet/refresh.ts b/src/lib/wallet/refresh.ts new file mode 100644 index 0000000..40373fc --- /dev/null +++ b/src/lib/wallet/refresh.ts @@ -0,0 +1,43 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { Account, AccountList } from '../account' +import { Rpc } from '../rpc' +import { Vault } from '../vault' +import { Wallet } from '#wallet' +import { KeyPair } from '#types' + +export async function _refresh (wallet: Wallet, rpc: Rpc | string | URL, from: number, to: number): Promise +export async function _refresh (wallet: Wallet, rpc: unknown, from: unknown, to: unknown): Promise { + try { + if (typeof rpc === 'string' || rpc instanceof URL) { + rpc = new Rpc(rpc) + } + if (!(rpc instanceof Rpc)) { + throw new TypeError('RPC must be a valid node') + } + if (typeof from !== 'number' || typeof to !== 'number') { + throw new TypeError('Invalid account range', { cause: `${from}-${to}` }) + } + if (from > to) [from as number, to as number] = [to, from] + const accounts = await wallet.accounts(from, to) + const addresses = [] + for (const account of accounts) { + addresses.push(account.address) + } + const data = { + accounts: addresses + } + const { balances } = await rpc.call('accounts_balances', data) as { balances: { [address: string]: { balance: string, receivable: string } } } + const { frontiers } = await rpc.call('accounts_frontiers', data) as { frontiers: { [address: string]: string } } + for (const account of accounts) { + account.balance = balances[account.address]?.balance + account.receivable = balances[account.address]?.receivable + account.frontier = frontiers[account.address] + } + return accounts + } catch (err) { + console.error(err) + throw new Error('Failed to refresh accounts', { cause: err }) + } +} diff --git a/test/test.refresh-accounts.mjs b/test/test.refresh-accounts.mjs index 2a0f270..6f3291c 100644 --- a/test/test.refresh-accounts.mjs +++ b/test/test.refresh-accounts.mjs @@ -27,7 +27,7 @@ if (isNode) { const rpc = new Rpc(env.NODE_URL ?? '', env.API_KEY_NAME) await Promise.all([ - suite('Refreshing account info', { skip: true }, async () => { + suite('Refreshing account info', { skip: false }, async () => { await test('fetch balance, frontier, and representative', async () => { const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED) @@ -163,9 +163,9 @@ await Promise.all([ }) }), - suite('Refreshing wallet accounts', { skip: true }, async () => { + suite('Refreshing wallet accounts', { skip: false }, async () => { - await test('should get balance, frontier, and representative for one account', async () => { + await test('get balance, frontier, and representative for one account', async () => { const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED) await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) const accounts = await wallet.refresh(rpc) @@ -184,8 +184,17 @@ await Promise.all([ await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) const accounts = await wallet.refresh(rpc, 0, 2) - assert.equal(accounts.length, 1) - assert.ok(accounts[0] instanceof Account) + assert.equal(accounts.length, 3) + for (let i = 0; i < 2; i++) { + assert.exists(accounts[i]) + assert.ok(accounts[i] instanceof Account) + assert.equal(accounts[i].index, i) + assert.equal(accounts[i].balance, 0n) + assert.equal(accounts[i].receivable, 0n) + } + assert.equal(accounts[0].address, 'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d') + assert.equal(accounts[1].address, 'nano_3phqgrqbso99xojkb1bijmfryo7dy1k38ep1o3k3yrhb7rqu1h1k47yu78gz') + assert.equal(accounts[2].address, 'nano_3b5fnnerfrkt4me4wepqeqggwtfsxu8fai4n473iu6gxprfq4xd8pk9gh1dg') await assert.resolves(wallet.destroy()) }) -- 2.47.3