Nona Lib is a powerful and user-friendly TypeScript library designed to simplify interactions with the Nano cryptocurrency network.
Whether you're developing wallet software, integrating Nano payments into your application, or just exploring the possibilities of Nano's block lattice structure, Nona Lib provides a comprehensive set of tools to manage accounts, perform transactions, and query blockchain data efficiently and securely through your Nano node.
Keys Features
[!NOTE] Before you begin using Nona Lib, ensure that your Nano node's
enable_control
is set to true, as the library requires this permission to perform certain operations.
More information on how to configure your node can be found here.
To install Nona Lib, run the following command in your project directory:
npm install nona-lib
If your node runs on localhost with the default port:
import { Nona } from 'nona-lib';
const nona = new Nona();
If your node runs on a custom port or on a remote server, you can specify the URLs:
import { Nona } from 'nona-lib';
const nona = new Nona({
url: 'http://localhost:7076',
websocketUrl: 'ws://localhost:7078',
});
You can simply create a new account with the following code:
const { privateKey, publicKey, address } = await nona.key.create();
Or from a seed:
// Use KeyService to generate a seed or provide your own
const seed = await KeyService.generateSeed();
const { privateKey, publicKey, address } = await nona.key.create(seed);
These keys are generated locally using the nanocurrency-js package.
Before using an account, you need to open it.
To open an account, you must have sent some funds to it from another account.
Then call the open method:
// You must provide a representative address to open the account
const reprensentative = 'nano_3rep...';
const wallet = nona.wallet(privateKey);
await wallet.open(reprensentative);
If you don't know how to choose a representative, check out this blog post: How to choose a representative.
To send a transaction, you must have an opened account (see Open an account).
const receveiver = 'nano_1rece...';
const amount = 2;
const wallet = nona.wallet(privateKey);
await wallet.send(receveiver, amount);
To receive a transaction, you must have an opened account (see Open an account).
To receive a single transaction:
const wallet = nona.wallet(privateKey);
await wallet.receive();
If you want to receive all pending transactions:
const wallet = nona.wallet(privateKey);
await wallet.receiveAll();
You can also listen and receive transactions in real time:
const wallet = nona.wallet(privateKey);
// This will create a websocket connection, listen for incoming transactions, and automatically receive them.
const subscription = await wallet.listenAndReceive({
// (Optional) next will be called each time a transaction is received
next: (transactionBlock) => console.log('Received transaction', transactionBlock),
});
// Don't forget to unsubscribe when you don't need it anymore
subscription.unsubscribe();
For more details about websocket, see Websocket.
[!NOTE] All methods in the account API are also available via the wallet object.
[!WARNING]
This wallet API does not interact with the wallet RPCs commands this naming is only for convenience.
The wallet is the main object to interact with your account.
const wallet = nona.wallet(privateKey);
open(representative: NanoTarget): Promise<string>
Opens the account with the provided representative represented as a NanoTarget string.
The first transaction of an account is crafted in a slightly different way. To open an account, you must have sent some funds to it from another account.
Returns the hash of the transaction.
const representative = 'nano_3rep...';
await wallet.open(representative);
send(target: NanoTarget, amount: number | string): Promise<string>
Sends a transaction to the specified target.
The amount is in nano unit.
Returns the hash of the transaction.
const address = 'nano_1rece...';
const amount = 2;
await wallet.send(address, amount);
[!NOTE]
The work is generated by the node, the options to provide or generate the work locally are not yet implemented.
receive(hash?: string): Promise<string | null>
Receives a pending transaction.
The hash of the transaction to receive can be provided. If not, a receivable hash will be used.
Returns the hash of the receive block.
If no hash is provided and no transaction is pending, null
is returned.
await wallet.receive();
An hash can also be provided to receive a specific transaction:
const hash = 'D83124BB...';
await wallet.receive(hash);
receiveAll(hashes?: string[]): Promise<string[]>
Receives all pending transactions.
Returns an array of hashes of the received blocks.
await wallet.receiveAll();
An array of hashes can also be provided to receive specific transactions:
const hashes = ['D83124BB...', '1208FF64...'];
await wallet.receiveAll(hashes);
[!NOTE]
All the webscoket related methods use Rxjs Observables and return a Subscription object. For more information about observables and subscriptions, see the Rxjs documentation.
listenAndReceive(params?: WalletListAndReceiveParams): Subscription
Listens for incoming transactions and automatically receives them.
Return a Subscription object.
interface WalletListAndReceiveParams {
/**
* A function that will be called each time a transaction is received.
* @param block The block that was received.
*/
next?: (block: ConfirmationBlock) => unknown;
/**
* A function that will be called when an error occurs.
* @param error The error that occurred.
*/
error?: (error: unknown) => unknown;
/**
* A function that will be called when the listener completes.
*/
complete?: () => unknown;
}
const subscription = await wallet.listenAndReceive({
next: (transactionBlock) => console.log('Received transaction', transactionBlock),
error: (error) => console.error('An error occurred', error),
complete: () => console.log('Subscription completed'),
});
// Don't forget to unsubscribe when you don't need it anymore
subscription.unsubscribe();
[!WARNING] Depending on the node setup and sync status, multiple confirmation notifications for the same block hash may be sent by a single tracking mechanism.
Theses block will call theerror
function with the errorUnreceivable
.
[!NOTE] Theses commands are also available using the wallet object.
All commands related to public account information.
You can create an account object with the following code:
const address = 'nano_13e2ue...';
const account = await nona.account(address);
You can also use your wallet object:
const wallet = nona.wallet(privateKey);
receivable(params?: ReceivableParams): Promise<Receivable>
Returns a list of block hashes which have not yet been received by this account.
interface ReceivableParams {
/**
* Specifies the number of blocks to return.
* Default to 100.
*/
count?: number;
/**
* Specifies whether to sort the response by block amount.
* Default to false.
*/
sort?: boolean;
}
Depending on the sort
parameter, the blocks will be returned in two different ways.
If sort
is false
(default), the blocks will be returned as an array of strings:
const receivable = await account.receivable();
console.log(receivable);
// ['D83124BB...', '1208FF64...', ...]
If sort
is true
, the blocks will be returned as an object with the block hash as the key and the amount as the value:
const receivable = await account.receivable({ sort: true });
console.log(receivable);
// { 'D83124BB...': 2, '1208FF64...': 1, ... }
info(params?: InfoParams): Promise<AccountInfo>
Returns general information for account.
Only works for accounts that have received their first transaction and have an entry on the ledger, will return "Account not found" otherwise.
To open an account, use open.
interface InfoParams {
/**
* Specifies whether to include the representative in the response.
* Default to false.
*/
representative?: boolean;
/**
* Specifies whether to return the raw balance.
* Default to false.
*/
raw?: boolean;
}
const info = await account.info();
console.log(info);
// {
// frontier: '0D60C42554478A2EDAD18AD5E975422297E62082E612C60ECABBDD4D01B65D46',
// open_block: '92CBCAC62345F58A58CE513652D22BD6E13CB094BF6F8D825DEE01CE54718868',
// representative_block: '0D60C42253478E2ADAD18AD5E975426297E62082E612C60ECAACDD4D01B65D46',
// balance: '2.3',
// modified_timestamp: '1712846889',
// block_count: '82',
// account_version: '2',
// confirmation_height: '82',
// confirmation_height_frontier: '0D60C22554478E2EDAD81BD5E975426297E62082E612C60ECAACDD4D01B65D46'
// }
balance({ raw = false }: { raw?: boolean }): Promise<AccountBalance>
Returns how many nano is owned and how many have not yet been received by account.
Set raw
to true
to return the balance in raw unit.
const balance = await account.balance();
console.log(balance);
// { balance: '2.3', receivable: '5', pending: '0' }
balance
is the total amount of nano owned by the account.
receivable
is the amount of nano that has not yet been received by the account.
[!NOTE]
pending
was deprecated in favor ofreceivable
. For compatibility reasons both terms are still available for many calls and in responses.
For more details see: https://docs.nano.org/releases/release-v24-0/#pendingreceivable-term-rpc-updates.
listenConfirmation(params: ListenConfirmationParams): Subscription
Listen for all confirmed blocks for the related account.
export interface ListenConfirmationParams {
/**
* A function that will be called each time a transaction is received.
* @param block The block that was received.
*/
next: (block: ConfirmationBlock) => unknown;
/**
* A function that will be called when an error occurs.
* @param error The error that occurred.
*/
error?: (error: unknown) => unknown;
/**
* A function that will be called when the listener completes.
*/
complete?: () => unknown;
/**
* A filter that will be used to filter the confirmation blocks.
*/
filter?: ConfirmationFilter;
}
export interface ConfirmationFilter {
/** List of block subtypes to filter the confirmation blocks. */
subtype?: string[];
/** Account addresses that received the transaction. */
to?: NanoAddress[];
}
Listen all sent confirmation blocks for the account from a specific address:
const subscription = await account.listenConfirmation({
next: (transactionBlock) => console.log('Received confirmation', transactionBlock),
filter: {
accounts: ['nano_1send...'],
subtype: ['send']
},
});
[!WARNING] Depending on the node setup and sync status, multiple confirmation notifications for the same block hash may be sent by a single tracking mechanism.
In order to prevent potential issues, integrations must track these block hashes externally to the node and prevent any unwanted actions based on multiple notifications.
history(params?: AccountHistoryParams): Promise<AccountHistory>
Retrieves the account history.
Returns only send & receive blocks by default (unless raw is set to true - see optional parameters below): change, state change & state epoch blocks are skipped, open & state open blocks will appear as receive, state receive/send blocks will appear as receive/send entries.
Response will start with the latest block for the account (the frontier), and will list all blocks back to the open block of this account when "count" is set to "-1".
export interface AccountHistoryParams {
/** Number of blocks to return. Default to 100. */
count?: number;
/** Hash of the block to start from. */
head?: string;
/** Number of blocks to skip. */
offset?: number;
/** Reverse order */
reverse?: boolean;
/** Results will be filtered to only show sends/receives connected to the provided account(s). */
accounts?: NanoAddress[];
/**
* if set to true instead of the default false, returns all blocks history and output all parameters of the block itself.
*/
raw?: boolean;
}
const history = await account.history();
console.log(history);
// {
// history: [
// {
// type: 'send',
// account: 'nano_13dtu...',
// amount: '1.23',
// hash: 'C43ED22C09...',
// local_timestamp: '1712846889',
// height: '82',
// },
// [...]
// ],
// previous: 'D83124BB...',
// }
To paginate the history, you can use the head
parameter with the previous
value from the previous call:
let hasNext = true;
let head: string | undefined;
while (hasNext) {
const { history, previous } = await account.history({ head });
console.log(history);
if (previous) {
head = previous;
} else {
hasNext = false;
}
}
If reverse
is true
, use next
instead of previous
.
blockCount(): Promise<number>
Returns the number of blocks for this account.
const blockCount = await account.blockCount();
console.log(blockCount);
// 42
representative(): Promise<string>
Returns the representative for this account.
const representative = await account.representative();
console.log(representative);
// nano_3rep...
weight(): Promise<string>
Returns the voting weight for this account in nano unit (default).
const weight = await account.weight();
console.log(weight);
// 123.456789
Set raw
to true
to return the weight in raw unit.
const weight = await account.weight({ raw: true });
console.log(weight);
// 123456789000000000000000000000000
[!NOTE]
All the webscoket related methods use Rxjs Observables and return a Subscription object. For more information about observables and subscriptions, see the Rxjs documentation.
At the first subscription, the websocket connection to the node will be established.
If all subscriptions are unsubscribed, the connection will be closed.
You can access to the websocket object with the following code:
const ws = nona.ws;
confirmation(params: WebSocketConfirmationParams): Subscription
Listens for confirmed blocks.
Return a Subscription object.
interface WebSocketConfirmationParams {
/**
* A function that will be called each time a transaction is received.
* @param block The block that was received.
*/
next: (block: ConfirmationBlock) => unknown;
/**
* A function that will be called when an error occurs.
* @param error The error that occurred.
*/
error?: (error: unknown) => unknown;
/**
* A function that will be called when the listener completes.
*/
complete?: () => unknown;
/**
* A filter that will be used to filter the confirmation blocks.
*/
filter?: ConfirmationFilter;
}
interface ConfirmationFilter {
/** List of account addresses to filter the confirmation blocks. */
accounts?: NanoAddress[];
/** List of block subtypes to filter the confirmation blocks. */
subtype?: string[];
/** Account addresses that received the transaction. */
to?: NanoAddress[];
}
Subscribe to all new confirmed blocks on the network:
const subscription = await nona.ws.confirmation({
// next will be called each time a transaction is received
next: (confirmationBlock) => console.log('Received confirmation', confirmationBlock),
});
// Don't forget to unsubscribe when you don't need it anymore
subscription.unsubscribe();
Subscribe to all new sent confirmation blocks to a specific account:
const subscription = await nona.ws.confirmation({
next: (block) => console.log('Received confirmation', block),
filter: {
subtype: ['send'],
to: ['nano_1send...'],
},
});
You can access to the blocks object with the following code:
const blocks = nona.blocks;
count(): Promise<BlockCount>
Reports the number of blocks in the ledger and unchecked synchronizing blocks.
This count represent the node ledger and not the network status.
const count = await blocks.count();
console.log(count);
// { count: '198574599', unchecked: '14', cemented: '198574599' }
count
- The total number of blocks in the ledger. This includes all send, receive, open, and change blocks.
unchecked
- The number of blocks that have been downloaded but not yet confirmed. These blocks are waiting in the processing queue.
cemented
- The number of blocks that have been confirmed and cemented in the ledger. Cemented blocks are confirmed irreversible transactions.
[!WARNING] This method is for advanced usage, use it if you know what you are doing.
create(params: CreateBlockParams): Promise<string>
Creates a block object based on input data & signed with private key or account in wallet.
Create a send block.
Let's say you want to send 1 nano to nano_1send...
.
You have currently 3 nano in your account.
const wallet = nona.wallet(privateKey);
const info = await wallet.info();
const recipient = 'nano_1send...';
const recipientPublicKey = KeyService.getPublicKey(recipient);
const sendBlock = await this.blocks.create({
// Current account
account: wallet.address,
// Final balance for account after block creation in raw unit (here: current balance - send amount).
balance: '2000000000000000000000000000000',
// The block hash of the previous block on this account's block chain ("0" for first block).
previous: info.frontier,
// The representative account for the account.
representative: info.representative,
// If the block is sending funds, set link to the public key of the destination account.
// If it is receiving funds, set link to the hash of the block to receive.
// If the block has no balance change but is updating representative only, set link to 0.
link: recipientPublicKey,
// Private key of the account
key: privateKey,
});
[!WARNING] This method is for advanced usage, use it if you know what you are doing.
process(block: Block, subtype: string): Promise<string>
Publish it to the network. Returns the hash of the published block.
If we want to process the block created in the previous example:
await this.blocks.process(sendBlock, 'send');
info(hash: string): Promise<BlockInfo>
Retrieves information about a specific block.
const hash = 'D83124BB...';
const info = await blocks.info(hash);
You can access to the key object with the following code:
const blocks = nona.key;
[!NOTE] The keys are generated locally using the nanocurrency-js package.
create(seed?: string): Promise<AccountKeys>
Create keys for an account.
const { privateKey, publicKey, address } = await nona.key.create();
expand(privateKey: string): AccountKeys
Expand a private key into public key and address.
const { publicKey, address } = nona.key.expand(privateKey);
Service to generate seeds and keys.
KeyService.generateSeed(): Promise<string>
Generates a random seed.
const seed = await KeyService.generateSeed();
KeyService.getPrivateKey(seed: string, index: number): string
Derive a private key from a seed, given an index.
const privateKey = KeyService.getPrivateKey(seed, 0);
KeyService.getPublicKey(privateKeyOrAddress: string): string
Derive a public key from a private key or an address.
const publicKey = KeyService.getPublicKey(privateKey);
KeyService.getAddress(publicKey: string): string
Derive an address from a public key.
const address = KeyService.getAddress(publicKey);
You can access to the node object with the following code:
const node = nona.node;
telemetry(): Promise<Telemetry>
Return metrics from other nodes on the network. Summarized view of the whole network.
const telemetry = await node.telemetry();
uptime(): Promise<number>
Return node uptime in seconds.
const uptime = await node.uptime();
console.log(uptime);
// 832870
version(): Promise<Version>
Returns version information for RPC, Store, Protocol (network), Node (Major & Minor version).
const version = await node.version();
if you prefer to call RPC directly you can use nona.rpc
.
rpc(action: string, body?: object): Promise<unknown>
Call account_info
RPC command:
const info = await nona.rpc('account_info', {
account: 'nano_13e2ue...',
});
You can access to the nameService
object with the following code:
const nameService = nona.nameService;
resolveUsername(username: NanoUsername): Promise<NanoAddress>
Resolves a username registered with the Nano Name Service to the registed NanoAddress
const username = '@nona-lib';
const address = await nona.nameService.resolveUsername(username);
resolveTarget(target: NanoTarget): Promise<NanoAddress>
Takes in a NanoTarget to potentially resolve. It checks if the target is a valid NanoAddress or a NanoUsername. In the case a NanoUsername is passed to the method, it is automatically resolved
const target = '@nona-lib';
const address = await nona.nameService.resolveTarget(target);
const target = 'nano_1rece...';
const address = await nona.nameService.resolveTarget(target);
A valid Nano address according to the offical docs:
nano_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs
Its validity is checked with the nanocurrency-js package.
A Nano username in the form of @name
:
@nona-lib
This username is resolved at runtime with the Nano Name Service.
A NanoTarget is either a valid address (e.g. nano_1rece...
) or a resolveable username registered with the Nano Name Service (e.g. @nona-lib
). Nonalib automatically resolves these usernames to valid adresses for you.
All handled errors are instances of NonaError
.
Each types of errors are extended from NonaError
and have a specific instance.
NonaError
- Base class for all errors and generic error.
NonaNetworkError
- Network error, likely related to the node connection.
NonaRpcError
- Error related to the RPC call response.
NonaParseError
- Error related to the response parsing.
If this occured while using the library, please report it.
NonaUserError
- Error related to the user input.
For example, if you try to send a transaction with an insufficient balance:
try {
await wallet.send('nano_1rece...', 100);
} catch (error) {
if (error instanceof NonaUserError && error.message === 'Insufficient balance') {
console.log('You don\'t have enough balance to send this amount');
} else {
console.error('An error occurred', error);
}
}
Nona Lib is a young and evolving TypeScript library designed to interact with the Nano cryptocurrency network. While we strive to ensure reliability and robustness, the following points need to be considered:
Early-stage Software: As an early-stage library, Nona Lib may undergo significant changes with updates that could improve or alter functionalities drastically. Users are advised to keep this in mind when using the library in production environments.
Limited Usage: Given its nascent stage, the library has been subjected to limited usage. This means there could be undiscovered bugs or issues affecting its performance and reliability. We welcome contributions and reports on any anomalies found during usage.
Dependency on External Systems: Nona Lib operates in conjunction with external systems such as Nano nodes, and its performance is highly dependent on the configuration and stability of these systems.
Security Risks: As with any tool managing financial transactions, there is an inherent risk. Users should be cautious and test thoroughly when integrating and deploying Nona Lib in security-sensitive environments.
We encourage the community to contribute to the development and testing of Nona Lib to help us improve its functionality and security. Use this library with caution, and ensure you have robust backup and recovery processes in place.