Skip to content

Errors and recovery ​

The important distinction is whether a signed block may have reached the node. A request that times out can still have been accepted. nanopay preserves that exact candidate so you can reconcile it before making another payment.

Error types ​

ErrorMeaningWhat to inspect
Ordinary input errorInvalid input, key, amount, builder fields or URLFix the input; no automatic transaction retry
NanoRpcErrorNode, HTTP, network or response-format failureaction, status, cause
TransactionErrorA high-level publication attempt failedtransaction.hash, signed transaction.block, cause
AccountBlockedErrorThis write did not start because an earlier submission is unresolvedThe earlier transaction and original cause
ReceiveAllErrorA receive batch stoppedcompleted submissions and the underlying cause

AbortError and TimeoutError can also appear as cancellation reasons. When they occur during a high-level publication attempt, inspect the enclosing transaction error rather than assuming nothing happened.

Preserve a failed candidate ​

This pattern demonstrates recovery of the same send if it confirms after a submission error. It assumes a client, loaded account and destination from the quick start:

ts
import { TransactionError } from 'nanopay'

try {
  const result = await nano.send({
    account,
    to: recipientAddress,
    amount: '0.01',
  })
  await nano.waitForConfirmation(result.hash)
} catch (error) {
  if (!(error instanceof TransactionError)) throw error

  // Persist this signed candidate in your application's durable journal.
  const candidate = error.transaction
  await nano.waitForConfirmation(candidate.hash, { timeoutMs: 120_000 })

  // Confirmation above proves this exact candidate settled.
  nano.resumeAccount(account.address, candidate.hash)
}

If the recovery wait times out or another node error occurs, this example leaves the account blocked and propagates the failure. It does not call send again. Reconcile the saved candidate and account state before deciding whether anything needs resubmission.

A missing hash alone is not proof of rejection while a node may still be processing it. Resubmitting the original signed block is different from building a new payment on a later frontier; your recovery policy must distinguish them.

What account blocking does ​

After an uncertain high-level submission, queued and future writes to that account reject with AccountBlockedError. They are not replayed when the account resumes. Reads and operations on other accounts continue.

resumeAccount(address, transactionHash) only clears the local block for that exact candidate. It does not inspect the network, cancel a request, publish a block or assert confirmation. Call it after your reconciliation has established an appropriate next state.

The queue and block are in memory. Restarting a process or creating a new client loses that state; a production service needs a durable transaction journal and account coordination. Granular publishBlock and raw request('process', ...) do not create this automatic high-level block.

Partial batches ​

ReceiveAllError.completed.transactions contains receives already submitted before failure. completed.amount and amountRaw total those submissions. Persist and inspect their hashes even when the overall batch rejects.

If error.cause is a TransactionError, the stopped receive has its own signed candidate and can be uncertain. Handle it separately from completed entries. Do not blindly restart the entire batch or erase the successful records.

Read retries and rate limits ​

Safe reads can use bounded backoff in your application. Distinguish throttling (status: 429) from bad parameters or an action blocked by your provider. Avoid an aggressive retry loop that competes with confirmation polling.

For writes, reconciliation comes first. A timeout is a statement about how long the caller waited, not a statement about ledger acceptance.