#### Caculating proof-of-work from an online service
```javascript
-const node = new Node('https://nano-node.example.com/')
+const node = new Rpc('https://nano-node.example.com/')
try {
await block.pow('https://nano-node.example.com/')
} catch (err) {
#### Processing a block on the network
```javascript
-const node = new Node('https://nano-node.example.com', 'nodes-api-key')
+const node = new Rpc('https://nano-node.example.com', 'nodes-api-key')
try {
const hash = await block.process('https://nano-node.example.com/')
} catch (err) {
import { ACCOUNT_KEY_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants.js'\r
import { base32, bytes, hex } from './convert.js'\r
import Ed25519 from './ed25519.js'\r
-import { Node } from './node.js'\r
+import { Rpc } from './rpc.js'\r
import { Safe } from './safe.js'\r
\r
/**\r
* A successful response sets the balance, frontier, and representative\r
* properties.\r
*\r
- * @param {Node|string|URL} node - Node information required to call `account_info`\r
+ * @param {Rpc|string|URL} rpc - RPC node information required to call `account_info`\r
*/\r
- async refresh (node: Node | string | URL): Promise<void> {\r
- if (typeof node === 'string' || node.constructor === URL) {\r
- node = new Node(node)\r
+ async refresh (rpc: Rpc | string | URL): Promise<void> {\r
+ if (typeof rpc === 'string' || rpc.constructor === URL) {\r
+ rpc = new Rpc(rpc)\r
}\r
- if (node.constructor !== Node) {\r
+ if (rpc.constructor !== Rpc) {\r
throw new TypeError('RPC must be a valid node')\r
}\r
const data = {\r
"representative": "true",\r
"weight": "true"\r
}\r
- const { balance, frontier, receivable, representative, weight } = await node.call('account_info', data)\r
+ const { balance, frontier, receivable, representative, weight } = await rpc.call('account_info', data)\r
if (frontier == null) {\r
throw new Error('Account not found')\r
}\r
import { Account } from './account.js'
import { bytes, dec, hex } from './convert.js'
import Ed25519 from './ed25519.js'
-import { Node } from './node.js'
+import { Rpc } from './rpc.js'
import Tools from './tools.js'
/**
*
* A successful response sets the `work` property.
*
- * @param {Node|string|URL} node - Node information required to call `work_generate`
+ * @param {Rpc|string|URL} rpc - RPC node information required to call `work_generate`
*/
- async pow (node: Node | string | URL): Promise<void> {
- if (typeof node === 'string' || node.constructor === URL) {
- node = new Node(node)
+ async pow (rpc: Rpc | string | URL): Promise<void> {
+ if (typeof rpc === 'string' || rpc.constructor === URL) {
+ rpc = new Rpc(rpc)
}
- if (node.constructor !== Node) {
+ if (rpc.constructor !== Rpc) {
throw new TypeError('RPC must be a valid node')
}
const data = {
"hash": this.previous
}
- const { work } = await node.call('work_generate', data)
+ const { work } = await rpc.call('work_generate', data)
this.work = work
}
* The block must already be signed (see `sign()` for more information).
* The block must also have a `work` value.
*
- * @param {Node|string|URL} node - Node information required to call `process`
+ * @param {Rpc|string|URL} rpc - RPC node information required to call `process`
* @returns {Promise<string>} Hash of the processed block
*/
- async process (node: Node): Promise<string> {
+ async process (rpc: Rpc): Promise<string> {
if (!this.signature) {
throw new Error('Block is missing signature. Use sign() and try again.')
}
"json_block": "true",
"block": this.json()
}
- const res = await node.call('process', data)
+ const res = await rpc.call('process', data)
if (res.hash == null) {
throw new Error('Block could not be processed', res)
}
import { default as TransportHID } from '@ledgerhq/hw-transport-webhid'
import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, LEDGER_STATUS_CODES } from './constants.js'
import { bytes, dec, hex, utf8 } from './convert.js'
-import { Node } from './node.js'
+import { Rpc } from './rpc.js'
import { ChangeBlock, ReceiveBlock, SendBlock } from './block.js'
export class Ledger {
*
* @param {number} index - Account number
* @param {string} hash - Hexadecimal block hash
- * @param {Node} node - Node class object with a node URL
+ * @param {Rpc} rpc - Rpc class object with a node URL
*/
- async updateCache (index: number, hash: string, node: Node): Promise<{ status: string }>
- async updateCache (index: number, input: any, node?: Node): Promise<{ status: string }> {
- if (typeof input === 'string' && node?.constructor === Node) {
+ async updateCache (index: number, hash: string, rpc: Rpc): Promise<{ status: string }>
+ async updateCache (index: number, input: any, node?: Rpc): Promise<{ status: string }> {
+ if (typeof input === 'string' && node?.constructor === Rpc) {
const data = {
'json_block': 'true',
'hash': input
// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
-import { Safe } from './safe.js'
-
/**
* Represents a Nano network node. It primarily consists of a URL which will
* accept RPC calls, and an optional API key header construction can be passed if
-* required by the node. Once instantiated, the Node object can be used to call
+* required by the node. Once instantiated, the Rpc object can be used to call
* any action supported by the Nano protocol. The URL protocol must be HTTPS; any
* other value will be changed automatically.
*/
-export class Node {
+export class Rpc {
#u: URL
#n?: string
import { UNITS } from './constants.js'
import { bytes, hex } from './convert.js'
import Ed25519 from './ed25519.js'
-import { Node } from './node.js'
+import { Rpc } from './rpc.js'
import { Bip44Wallet, Blake2bWallet, LedgerWallet } from './wallet.js'
import { SendBlock } from './block.js'
* Collects the funds from a specified range of accounts in a wallet and sends
* them all to a single recipient address.
*
-* @param {Node|string|URL} node - Node information required to refresh accounts, calculate PoW, and process blocks
+* @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks
* @param {Blake2bWallet|Bip44Wallet|LedgerWallet} wallet - Wallet from which to sweep funds
* @param {string} recipient - Destination address for all swept funds
* @param {number} from - Starting account index to sweep
* @param {number} to - Ending account index to sweep
* @returns An array of results including both successes and failures
*/
-export async function sweep (node: Node | string | URL, wallet: Blake2bWallet | Bip44Wallet | LedgerWallet, recipient: string, from: number = 0, to: number = from) {
- if (node == null || wallet == null || recipient == null) {
+export async function sweep (rpc: Rpc | string | URL, wallet: Blake2bWallet | Bip44Wallet | LedgerWallet, recipient: string, from: number = 0, to: number = from) {
+ if (rpc == null || wallet == null || recipient == null) {
throw new ReferenceError('Missing required sweep arguments')
}
- if (typeof node === 'string' || node.constructor === URL) {
- node = new Node(node)
+ if (typeof rpc === 'string' || rpc.constructor === URL) {
+ rpc = new Rpc(rpc)
}
- if (node.constructor !== Node) {
+ if (rpc.constructor !== Rpc) {
throw new TypeError('RPC must be a valid node')
}
const blockQueue = []
const results: { status: 'success' | 'error', address: string, message: string }[] = []
const recipientAccount = new Account(recipient)
- const accounts = await wallet.refresh(node, from, to)
+ const accounts = await wallet.refresh(rpc, from, to)
for (const account of accounts) {
if (account.representative?.address && account.frontier) {
const block = new SendBlock(
)
blockQueue.push(new Promise(async resolve => {
try {
- await block.pow(node)
+ await block.pow(rpc)
await block.sign(account.index)
- const hash = await block.process(node)
+ const hash = await block.process(rpc)
results.push({ status: 'success', address: block.account.address, message: hash })
} catch (err: any) {
results.push({ status: 'error', address: block.account.address, message: err.message })
import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js'\r
import { bytes, dec } from './convert.js'\r
import { Entropy } from './entropy.js'\r
-import { Node } from './node.js'\r
+import { Rpc } from './rpc.js'\r
import { Safe } from './safe.js'\r
import Tools from './tools.js'\r
import type { Ledger } from './ledger.js'\r
* Fetches the lowest-indexed unopened account from a wallet in sequential\r
* order. An account is unopened if it has no frontier block.\r
*\r
- * @param {Node|string|URL} node - Node information required to refresh accounts, calculate PoW, and process blocks\r
+ * @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks\r
* @param {number} batchSize - Number of accounts to fetch and check per RPC callout\r
* @param {number} from - Account index from which to start the search\r
* @returns {Promise<Account>} The lowest-indexed unopened account belonging to the wallet\r
*/\r
\r
- async getNextNewAccount (node: Node, batchSize: number = ADDRESS_GAP, from: number = 0): Promise<Account> {\r
+ async getNextNewAccount (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 data = {\r
"accounts": addresses\r
}\r
- const { errors } = await node.call('accounts_frontiers', data)\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 new Account(key)\r
}\r
}\r
- return await this.getNextNewAccount(node, batchSize, from + batchSize)\r
+ return await this.getNextNewAccount(rpc, batchSize, from + batchSize)\r
}\r
\r
/**\r
*\r
* A successful response will set these properties on each account.\r
*\r
- * @param {Node|string|URL} node - Node information required to refresh accounts, calculate PoW, and process blocks\r
+ * @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks\r
* @returns {Promise<Account[]>} Accounts with updated balances, frontiers, and representatives\r
*/\r
- async refresh (node: Node | string | URL, from: number = 0, to: number = from): Promise<Account[]> {\r
- if (typeof node === 'string' || node.constructor === URL) {\r
- node = new Node(node)\r
+ async refresh (rpc: Rpc | string | URL, from: number = 0, to: number = from): Promise<Account[]> {\r
+ if (typeof rpc === 'string' || rpc.constructor === URL) {\r
+ rpc = new Rpc(rpc)\r
}\r
- if (node.constructor !== Node) {\r
+ if (rpc.constructor !== Rpc) {\r
throw new TypeError('RPC must be a valid node')\r
}\r
const accounts = await this.accounts(from, to)\r
for (let i = 0; i < accounts.length; i++) {\r
try {\r
- await accounts[i].refresh(node)\r
+ await accounts[i].refresh(rpc)\r
} catch (err) {\r
accounts.splice(i--, 1)\r
}\r
import { Account } from './lib/account.js'
import { SendBlock, ReceiveBlock, ChangeBlock } from './lib/block.js'
-import { Node } from './lib/node.js'
+import { Rpc } from './lib/rpc.js'
import { Rolodex } from './lib/rolodex.js'
import { Safe } from './lib/safe.js'
import Tools from './lib/tools.js'
import { Bip44Wallet, Blake2bWallet, LedgerWallet } from './lib/wallet.js'
-export { Account, SendBlock, ReceiveBlock, ChangeBlock, Node, Rolodex, Safe, Tools, Bip44Wallet, Blake2bWallet, LedgerWallet }
+export { Account, SendBlock, ReceiveBlock, ChangeBlock, Rpc, Rolodex, Safe, Tools, Bip44Wallet, Blake2bWallet, LedgerWallet }
import { describe, it } from 'node:test'
import { strict as assert } from 'assert'
import { NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'
-import { Account, Bip44Wallet, Node } from '../dist/main.js'
+import { Account, Bip44Wallet, Rpc } from '../dist/main.js'
// WARNING: Do not send any funds to the test vectors below
// Test vectors from https://docs.nano.org/integration-guides/key-management/ and elsewhere
const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)
-const node = new Node(process.env.NODE_URL, process.env.API_KEY_NAME, process.env.API_KEY_VALUE)
+const node = new Rpc(process.env.NODE_URL, process.env.API_KEY_NAME, process.env.API_KEY_VALUE)
const skip = true
})
it('should throw with invalid node', async () => {
- const invalidNode = new Node('http://invalid.com')
+ const invalidNode = new Rpc('http://invalid.com')
const accounts = await wallet.accounts()
const account = accounts[0]
await assert.rejects(account.refresh(invalidNode),
import { describe, it } from 'node:test'\r
import { strict as assert } from 'assert'\r
import { RAW_MAX, NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'\r
-import { Bip44Wallet, Account, SendBlock, Node, Tools } from '../dist/main.js'\r
+import { Bip44Wallet, Account, SendBlock, Rpc, Tools } from '../dist/main.js'\r
\r
const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
-const rpc = new Node(process.env.NODE_URL, process.env.API_KEY_NAME, process.env.API_KEY_VALUE)\r
+const rpc = new Rpc(process.env.NODE_URL, process.env.API_KEY_NAME, process.env.API_KEY_VALUE)\r
\r
describe('unit conversion tests', async () => {\r
it('should convert nano to raw', async () => {\r