import { Block } from '../block'
import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from '../constants'
import { dec } from '../convert'
-import { Rpc, Schema } from '../rpc'
+import { Rpc } from '../rpc'
import { _account } from './account'
import { _cache } from './cache'
import { _close } from './close'
'json_block': 'true',
'hash': input
}
- const res = await node.post('block_info', data)
- if (!res || typeof res !== 'object' || !('ok' in res) || 'error' in res) {
- throw new Error('Unable to fetch block info', { cause: res })
- }
- const blockInfo = res as Record<string, unknown> & Record<'ok', unknown>
- if (!(blockInfo.contents != null && typeof blockInfo.contents === 'object')) {
- throw new Error('Block info missing contents', { cause: blockInfo })
- }
- const contents = {} as Record<string, string>
- for (const [k, v] of Object.entries(blockInfo.contents)) {
- if (Schema.block_info.contents.fields.includes(k) && typeof v === 'string') {
- contents[k] = v
- }
- }
- const { account, balance, link, previous, representative, signature } = contents
+ const blockInfo = await node.post('block_info', data)
+ const { account, balance, link, previous, representative, signature } = blockInfo.contents
const block = new Block(account, balance, previous, representative).receive(link, 0)
block.signature = signature
input = block
}
if (!(input instanceof Block)) {
- throw new TypeError('Invalid block format')
+ throw new TypeError('Invalid block format for Ledger cache')
}
return queue<LedgerResponse>(async () => _cache(this.#transport, index, input))
}
//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-export type AccountBalance = {
+import { RpcError } from '../errors'
+
+export type AccountBalanceResponse = {
balance: string
- pending: string
+ pending?: string
receivable: string
}
-export function account_balance (body: unknown): AccountBalance {
- const response: AccountBalance = {
- balance: '0',
- pending: '0',
- receivable: '0',
- }
- if (body == null || typeof body !== 'object') {
- return response
- }
- if ('balance' in body && typeof body.balance === 'string') {
- response.balance = body.balance
- }
- if ('pending' in body && typeof body.pending === 'string') {
- response.pending = body.pending
- }
- if ('receivable' in body && typeof body.receivable === 'string') {
- response.receivable = body.receivable
+export function account_balance (body: unknown): AccountBalanceResponse {
+ if (body != null && typeof body === 'object') {
+ if ('balance' in body && typeof body.balance === 'string'
+ && 'receivable' in body && typeof body.receivable === 'string'
+ ) {
+ const response: AccountBalanceResponse = {
+ balance: body.balance,
+ receivable: body.receivable,
+ }
+ if ('pending' in body && typeof body.pending === 'string') {
+ response.pending = body.pending
+ }
+ return response
+ }
+ if ('error' in body && typeof body.error === 'string') {
+ throw new RpcError(body.error)
+ }
}
- return response
+ throw new RpcError('Invalid account_balance response')
}
//! SPDX-License-Identifier: GPL-3.0-or-later
import { Account } from '../account'
-import { AccountBalance, account_balance } from './account_balance'
+import { RpcError } from '../errors'
+import { AccountBalanceResponse, account_balance } from './account_balance'
-type AccountsBalances = {
+type AccountsBalancesResponse = {
balances?: {
- [address: string]: AccountBalance
+ [address: string]: AccountBalanceResponse
}
errors?: {
[address: string]: string
}
}
-export function accounts_balances (body: unknown): AccountsBalances {
- const response: AccountsBalances = {}
- if (body == null || typeof body !== 'object') {
- return response
- }
- const balances = 'balances' in body ? body.balances : null
- const errors = 'errors' in body ? body.errors : null
+export function accounts_balances (body: unknown): AccountsBalancesResponse {
+ if (body != null && typeof body === 'object') {
+ const response: AccountsBalancesResponse = {}
- if (balances != null) {
- if (typeof balances === 'object') {
- for (const [address, data] of Object.entries(balances) as [string, unknown][]) {
+ if ('errors' in body && body.errors != null && typeof body.errors === 'object') {
+ for (const [address, error] of Object.entries(body.errors) as [string, unknown][]) {
try {
Account.validate(address)
- if (data == null || typeof data !== 'object') {
- throw new Error('Invalid account balance data')
+ if (typeof error === 'string') {
+ throw new Error(error)
}
- response.balances ??= {}
- response.balances[address] = account_balance(data)
} catch (err: any) {
response.errors ??= {}
response.errors[address] = err?.message
}
}
}
- }
- if (errors != null) {
- if (typeof errors === 'object') {
- for (const [address, error] of Object.entries(errors) as [string, unknown][]) {
+ if ('balances' in body && body.balances != null && typeof body.balances === 'object') {
+ for (const [address, data] of Object.entries(body.balances) as [string, unknown][]) {
try {
Account.validate(address)
- if (typeof error === 'string') {
- throw new Error(error)
+ if (data == null || typeof data !== 'object') {
+ throw new Error('Invalid account balance data')
}
+ response.balances ??= {}
+ response.balances[address] = account_balance(data)
} catch (err: any) {
response.errors ??= {}
- response.errors[address] = err?.message
+ response.errors[address] ??= err?.message
}
}
}
- }
- return response
+ return response
+ }
+ throw new RpcError('Invalid accounts_balance response')
}
import { RpcError } from '../errors'
-export type BlockInfo = {
- contents: {
- account: string
- balance: string
- link: string
- link_as_account: string
- previous: string
- representative: string
- signature: string
- type: string
- work: string
- }
+export type BlockInfoContents = {
+ account: string
+ balance: string
+ link: string
+ link_as_account: string
+ previous: string
+ representative: string
+ signature: string
+ type: 'state'
+ work: string
+}
+
+export type BlockInfoResponse = {
+ contents: BlockInfoContents
amount: string
balance: string
block_account: string
successor: string
}
-export function block_info (body: unknown): BlockInfo {
- if (body != null && typeof body === 'object'
- && 'amount' in body && typeof body.amount === 'string'
- && 'balance' in body && typeof body.balance === 'string'
- && 'block_account' in body && typeof body.block_account === 'string'
- && 'confirmed' in body && typeof body.confirmed === 'string'
- && 'height' in body && typeof body.height === 'string'
- && 'linked_account' in body && typeof body.linked_account === 'string'
- && 'local_timestamp' in body && typeof body.local_timestamp === 'string'
- && 'subtype' in body && (body.subtype === 'change' || body.subtype === 'epoch' || body.subtype === 'open' || body.subtype === 'receive' || body.subtype === 'send')
- && 'successor' in body && typeof body.successor === 'string'
- && 'contents' in body && body.contents != null && typeof body.contents === 'object'
- && 'account' in body.contents && typeof body.contents.account === 'string'
- && 'balance' in body.contents && typeof body.contents.balance === 'string'
- && 'link' in body.contents && typeof body.contents.link === 'string'
- && 'link_as_account' in body.contents && typeof body.contents.link_as_account === 'string'
- && 'previous' in body.contents && typeof body.contents.previous === 'string'
- && 'representative' in body.contents && typeof body.contents.representative === 'string'
- && 'signature' in body.contents && typeof body.contents.signature === 'string'
- && 'type' in body.contents && typeof body.contents.type === 'string'
- && 'work' in body.contents && typeof body.contents.work === 'string'
- ) {
- return {
- contents: {
- account: body.contents.account,
- balance: body.contents.balance,
- link: body.contents.link,
- link_as_account: body.contents.link_as_account,
- previous: body.contents.previous,
- representative: body.contents.representative,
- signature: body.contents.signature,
- type: body.contents.type,
- work: body.contents.work,
- },
- amount: body.amount,
- balance: body.balance,
- block_account: body.block_account,
- confirmed: body.confirmed,
- height: body.height,
- linked_account: body.linked_account,
- local_timestamp: body.local_timestamp,
- subtype: body.subtype,
- successor: body.successor,
+export function block_info (body: unknown): BlockInfoResponse {
+ if (body != null && typeof body === 'object') {
+ if ('amount' in body && typeof body.amount === 'string'
+ && 'balance' in body && typeof body.balance === 'string'
+ && 'block_account' in body && typeof body.block_account === 'string'
+ && 'confirmed' in body && typeof body.confirmed === 'string'
+ && 'height' in body && typeof body.height === 'string'
+ && 'linked_account' in body && typeof body.linked_account === 'string'
+ && 'local_timestamp' in body && typeof body.local_timestamp === 'string'
+ && 'subtype' in body && (body.subtype === 'change' || body.subtype === 'epoch' || body.subtype === 'open' || body.subtype === 'receive' || body.subtype === 'send')
+ && 'successor' in body && typeof body.successor === 'string'
+ && 'contents' in body && body.contents != null && typeof body.contents === 'object'
+ && 'account' in body.contents && typeof body.contents.account === 'string'
+ && 'balance' in body.contents && typeof body.contents.balance === 'string'
+ && 'link' in body.contents && typeof body.contents.link === 'string'
+ && 'link_as_account' in body.contents && typeof body.contents.link_as_account === 'string'
+ && 'previous' in body.contents && typeof body.contents.previous === 'string'
+ && 'representative' in body.contents && typeof body.contents.representative === 'string'
+ && 'signature' in body.contents && typeof body.contents.signature === 'string'
+ && 'type' in body.contents && body.contents.type === 'state'
+ && 'work' in body.contents && typeof body.contents.work === 'string'
+ ) {
+ return {
+ contents: {
+ account: body.contents.account,
+ balance: body.contents.balance,
+ link: body.contents.link,
+ link_as_account: body.contents.link_as_account,
+ previous: body.contents.previous,
+ representative: body.contents.representative,
+ signature: body.contents.signature,
+ type: body.contents.type,
+ work: body.contents.work,
+ },
+ amount: body.amount,
+ balance: body.balance,
+ block_account: body.block_account,
+ confirmed: body.confirmed,
+ height: body.height,
+ linked_account: body.linked_account,
+ local_timestamp: body.local_timestamp,
+ subtype: body.subtype,
+ successor: body.successor,
+ }
+ }
+ if ('error' in body && typeof body.error === 'string') {
+ throw new RpcError(body.error)
}
- } else {
- throw new RpcError('Invalid block_info response')
}
+ throw new RpcError('Invalid block_info response')
}
//! SPDX-License-Identifier: GPL-3.0-or-later
import { hex } from '../convert'
-import { BlockInfo, block_info } from './block_info'
+import { RpcError } from '../errors'
+import { BlockInfoResponse, block_info } from './block_info'
-type BlocksInfo = {
+type BlocksInfoResponse = {
blocks?: {
- [hash: string]: BlockInfo
- },
+ [hash: Hex]: BlockInfoResponse
+ }
errors?: {
- [address: string]: string
- },
+ [hash: string]: string
+ }
}
-export function blocks_info (body: unknown): BlocksInfo {
- const response: BlocksInfo = {}
- if (body == null || typeof body !== 'object') {
- return response
- }
- const blocks = 'blocks' in body ? body.blocks : null
- const errors = 'errors' in body ? body.errors : null
+export function blocks_info (body: unknown): BlocksInfoResponse {
+ if (body != null && typeof body === 'object') {
+ const response: BlocksInfoResponse = {}
- if (blocks != null) {
- if (typeof blocks === 'object') {
- for (const [hash, data] of Object.entries(blocks) as [string, unknown][]) {
+ if ('errors' in body && body.errors != null && typeof body.errors === 'object') {
+ for (const [hash, error] of Object.entries(body.errors) as [string, unknown][]) {
try {
- if (hex.is(hash)) {
- response.blocks ??= {}
- response.blocks[hash] = block_info(data)
+ if (hex.is(hash) && typeof error === 'string') {
+ throw new Error(error)
} else {
- throw new Error('Invalid blocks_info hash')
+ throw new Error('Invalid blocks_info error')
}
} catch (err: any) {
response.errors ??= {}
}
}
}
- }
- if (errors != null) {
- if (typeof errors === 'object') {
- for (const [hash, error] of Object.entries(errors) as [string, unknown][]) {
+ if ('blocks' in body && body.blocks != null && typeof body.blocks === 'object') {
+ for (const [hash, data] of Object.entries(body.blocks) as [string, unknown][]) {
try {
- if (hex.is(hash) && typeof error === 'string') {
- throw new Error(error)
+ if (hex.is(hash)) {
+ response.blocks ??= {}
+ response.blocks[hash] = block_info(data)
} else {
- throw new Error('Invalid blocks_info error')
+ throw new Error('Invalid blocks_info hash')
}
} catch (err: any) {
response.errors ??= {}
- response.errors[hash] = err?.message
+ response.errors[hash] ??= err?.message
}
}
}
- }
- return response
+ return response
+ }
+ throw new RpcError('Invalid blocks_info response')
}
import { RpcError } from '../errors'
import { Schema } from './schema'
-export { Schema }
-
/**
* Represents a Nano network node. It primarily consists of a URL which will
* accept RPC calls; an optional authentication header name can be passed if an
//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-export const process = Object.freeze({
- fields: [
- 'action',
- 'async',
- 'block',
- 'json_block',
- 'subtype',
- ],
- block: {
- fields: [
- 'account',
- 'balance',
- 'link',
- 'link_as_account',
- 'previous',
- 'representative',
- 'signature',
- 'type',
- 'work',
- ],
- },
-})
+import { hex } from '../convert'
+import { RpcError } from '../errors'
+import { BlockInfoContents } from './block_info'
+
+export type ProcessRequest = {
+ action: 'process'
+ block: BlockInfoContents
+ async: boolean
+ force: boolean
+ json_block: boolean
+ subtype: 'change' | 'epoch' | 'open' | 'receive' | 'send'
+}
+
+export type ProcessResponse = {
+ hash: Hex
+}
+
+export function process (body: unknown): ProcessResponse {
+ if (body != null && typeof body === 'object') {
+ if ('hash' in body && hex.is(body.hash)) {
+ return { hash: body.hash }
+ }
+ if ('error' in body && typeof body.error === 'string') {
+ throw new RpcError(body.error)
+ }
+ }
+ throw new RpcError('Invalid process response')
+}
// accounts_representatives,
block_info,
blocks_info,
- // process,
+ process,
})