Production-grade TypeScript SDK for interacting with a Nano node, providing fully typed and runtime-validated RPC and WebSocket APIs, a built-in WebSocket client with typed ack and topic listeners, and utilities for blocks, cryptography, and safe raw amount arithmetic.
npm i nano-sdk zodimport { Nano } from "nano-sdk";
const nanoRpcUrl = "http://127.0.0.1:7076";
// Alias
type AccountString = Nano.Types.AccountString;
const AccountString = Nano.Types.AccountString();
async function fetchAccountBalance(account: AccountString) {
// 1) Validate user input
const validatedAccount = AccountString.parse(account);
// 2) Typed RPC call
const response = await Nano.RPC.account_balance(
nanoRpcUrl,
{
action: "account_balance",
account: validatedAccount,
include_only_confirmed: true,
},
{ timeoutInMs: 2000 }
);
// 3) Convert and format raw balance
const balanceInNano = Nano.Math.rawToNano({ raw: response.balance, decimalPlaces: 6 });
// 4) Output balanceInNano
console.log("Balance (Nano):", balanceInNano);
}
fetchAccountBalance("nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3").catch((err) => console.error("Unexpected failure:", err));RPC requests and responses are fully typed and validated at runtime. Response types are derived from the corresponding request parameters, meaning the shape of the response object adapts based on the options you provide.
The SDK uses a single root namespace (Nano), organized into sub-namespaces, each focused on a specific responsibility:
Nano.Blocks: Block schemas (Zod) and state-block creation helpersNano.Crypto: Key derivation, block hashing, signing, and verification utilitiesNano.Math: Raw amount conversion, formatting, and safe raw arithmeticNano.RPC: Nano node RPC methods (HTTP POST), with typed requests and typed responsesNano.Types: Runtime validators for common Nano primitives (account, hash, keys, raw amounts, etc.)Nano.WebSocket: Fully typed Nano node WebSocket request and response schemas, including acknowledgement and topic message definitionsNano.WebSocketClient: Instantiable WebSocket client providing fully typed acknowledgement and topic-based message handling using theNano.WebSocketschemas
Applications that only use one SDK area can import that domain explicitly instead of loading the complete Nano
namespace:
import { rawPlus } from "nano-sdk/math";
import { HashString } from "nano-sdk/types";These explicit module imports limit the Nano SDK code that a bundler needs to include in the compiled application. This can reduce the application's JavaScript bundle size and therefore its storage and download footprint. The actual reduction depends on the selected modules and the application's build tool.
The available domain entry points are:
nano-sdk/blocksnano-sdk/cryptonano-sdk/mathnano-sdk/rpcnano-sdk/typesnano-sdk/web-socket
All RPC methods:
- validate requests using Zod
- perform an HTTP POST (default: native
fetch) - detect and handle post errors
- validate responses using Zod
All RPC methods follow a consistent signature:
Nano.RPC.<method>(rpcUrl, request, config?)
rpcUrl– Nano node RPC endpointrequest– typed request object (includingaction)config(optional) – request behavior and transport options
By default, RPC calls throw Nano.RPC.Error for:
- Invalid request payloads
- HTTP or transport errors
- Nano node error responses
- Invalid or unexpected response data
Blocks, crypto, and math use the same throwing/non-throwing pattern with their own error types. Every domain exposes
its error class as Error and its stable string enum as ErrorCode (for example, Nano.Crypto.Error and
Nano.Crypto.ErrorCode).
Set throwOnError: false to receive a discriminated result instead. Successful results contain data; failures
contain an error with the same code and message as the thrown error (RPC results use
{ success: false, error: { code, message } }). TypeScript narrows both the result and the method-specific set of
possible codes. Messages are intended for people and may change, so application logic should compare enum members:
const result = Nano.Math.rawMinus({ raw: "1", subtrahend: "2", throwOnError: false });
if (!result.success && result.error.code === Nano.Math.ErrorCode.NegativeResult) {
// Handle underflow.
}Nano node error responses use Nano.RPC.ErrorCode.NodeError and preserve the node's free-form message. If one SDK
domain wraps a failure from another, its operation-level error retains the original error as cause.
The optional config supports:
throwOnError– true (default) throws, false returns{ success, data / error }timeoutInMs– HTTP timeoutabortSignal– cancellation via AbortControllerheaders– custom HTTP headers merged with the default JSON content typehttpClient– custom HTTP transport
By default, errors throw Nano.RPC.Error.
Custom HTTP headers, such as bearer tokens required by authenticated RPC providers, can be passed directly through the RPC request configuration:
const response = await Nano.RPC.account_balance(
nanoRpcUrl,
{
action: "account_balance",
account: account,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);RPC methods accept a custom httpClient via the request config. See the axios example in
Non-standard RPC providers can be integrated by defining custom Zod request and response schemas and passing them
to Nano.RPC.postFunction. For example, Nano.to extends and changes parts of the official Nano Node RPC API; the
custom-rpc.example.ts
example demonstrates how to integrate its custom work_generate action.
Nano.WebSocket provides a complete, strongly typed implementation of all Nano node WebSocket request and response schemas and ships with a built-in WebSocketClient. By default, the client uses the globally available WebSocket implementation provided by the current runtime environment (browser or Node.js).
The WebSocketClient exposes fully typed WebSocket interactions through two complementary listener APIs:
- Ack listeners for handling typed acknowledgement messages returned by the node when a WebSocket action explicitly requests an acknowledgement (
subscribe,unsubscribe,update, orpong) - Topic listeners for typed streaming subscriptions, including
bootstrap,confirmation,new_unconfirmed_block,started_election,stopped_election,telemetry,work, andvote
All incoming messages are validated and typed based on the corresponding WebSocket schemas, enabling safe, ergonomic access to response data without manual parsing or casting.
const ws = new Nano.WebSocketClient(webSocketUrl, undefined, {
webSocketClass: undefined, // WebSocket constructor, if none provided, defaults to global WebSocket
connectionTimeout: 4000, // retry connect if not connected after this time, in ms
minUptime: 5000, // min time in ms to consider connection as stable
maxEnqueuedMessages: Infinity, // maximum number of messages to buffer until reconnection
maxReconnectionAttempts: Infinity, // maximum number of reconnection attempts
maxReconnectionDelay: 10000, // max delay in ms between reconnections
minReconnectionDelay: 1000 + Math.random() * 4000, // min delay in ms between reconnections
reconnectionDelayGrowFactor: 1.3, // how fast the reconnection delay grows
startClosed: false, // start websocket in CLOSED state, call `.reconnect()` to connect
});Nano.Types provides a collection of reusable Zod schemas for common Nano-specific primitives. These schemas form the foundation for higher-level features such as cryptography and RPC validation. Use them to validate external input and to type values throughout your codebase:
- Prefer
.parse(...)when invalid input should throw immediately - Prefer
.safeParse(...)when you want a typed success/failure result without throwing
The available schemas cover a wide range of Nano primitives, including but not limited to:
AccountString,RawAmountString,NanoAmountString,PrivateKeyString,PublicKeyString,SignatureString,HashString,HexString, ...
Nano.Math provides safe arithmetic, comparison, and formatting utilities for nano and raw amounts.
Raw amounts can be represented as strings (RawAmountString, to remain JSON-serializable) and native BigInt values (RawAmount).
Nano amounts are represented as strings (NanoAmountString) to avoid precision issues and eliminate the need for a BigNumber.js dependency.
All raw functions support mixed inputs (RawAmount and RawAmountString).
The type of the first parameter of arithmetic functions determines the return type:
- If the first
rawparameter is aRawAmount(BigInt), the result is returned asBigInt - If the first
rawparameter is aRawAmountString(String), the result is returned asString
Available utilities:
- Conversion:
nanoToRaw,rawToNano - Formatting:
formatRaw - Arithmetic (with bounds checking):
rawPlus,rawMinus,rawMultiply,rawDivide,rawModulo - Comparisons:
rawIsZero,rawIsGreaterThan,rawIsGreaterThanOrEqualTo,rawIsLessThan,rawIsLessThanOrEqualTo,rawIsEqualTo
Nano.Crypto provides cryptographic utilities commonly needed when building Nano-related services:
- Key pair generation and deterministic key derivation
- Block hashing and hash verification
- Block signing and signature verification
- Block verification (combines hash, link, and signature validation for state blocks into a single method call)
- Proof-of-work hash verification
derive-account-from-seed.example.tshash-and-verify-hash.example.tssign-and-verify-signature.example.tsverify-block.example.ts
Nano.Blocks provides predefined Zod schemas for composing and validating Nano block variants. All schemas are provided as composable building blocks and can be used as a base to define extended schemas with additional custom fields.
Available block schemas:
StateBlockLegacySendBlock,LegacyReceiveBlock,LegacyOpenBlock,LegacyChangeBlockBlockas a union of all supported block variants
createOpenBlock, createSendBlock, createReceiveBlock, and createChangeBlock validate and create new state
blocks. They only support complete StateBlock inputs; legacy blocks are not supported.
Send, receive, and change use the account's latest frontierBlock. Open uses the funding sendBlock, while
receive needs both the destination frontierBlock and the complete source sendBlock.
Pass privateKey to sign during creation or omit it and sign the returned block later with
Nano.Crypto.signBlock or the lower-level Nano.Crypto.signHash. Open requires an explicit
representative; the other helpers inherit it where possible. Pass { throwOnError: false } to receive a
Result<StateBlock, Nano.Blocks.Error> instead of throwing.
Created blocks contain zero work so proof of work can be generated independently and assigned afterward. For
local PoW without an external work server, install
nano-pow with npm i nano-pow. It supports local generation through
WebGPU, WebGL, WASM, or CPU depending on the environment. See the NanoPow example for the different work roots
used by open and subsequent blocks.
create-state-blocks.example.tscreate-state-block-with-nano-pow.example.tsstate-block-with-hash.example.ts
- Unit tests (
test/unit) cover schemas, math edge cases, crypto utilities, and parsing/validation flows (throwing and non-throwing). - Integration tests (
test/integration) verify both RPC and WebSocket functionality against a real Nano node, ensuring request and response schemas match live node behavior.
Unit tests are executed using:
npm run test:unitIntegration test requirements:
- A running Nano node is required.
- All public Nano RPC and WebSocket endpoints must be reachable.
enable_control=trueis not required for any currently enabled integration tests. - The RPC endpoint URL can be configured via the
config.nanoRpcUrlfield inpackage.jsonor via theNANO_RPC_URLenvironment variable. The environment variable takes precedence if both are set. - The WebSocket endpoint URL can be configured via the
config.nanoWebSocketUrlfield inpackage.jsonor via theNANO_WEB_SOCKET_URLenvironment variable. The environment variable takes precedence if both are set.
Integration tests for RPC and WebSocket are executed together using:
npm run test:integration- npm: https://www.npmjs.com/package/nano-sdk
- GitHub: https://github.com/infinitybay/nano-sdk
- Nano Node RPC Protocol: https://docs.nano.org/commands/rpc-protocol/
- Nano Node WebSockets Integration Guide: https://docs.nano.org/integration-guides/websockets/

