Guide
SDK
The Tenrai SDK for TypeScript and JavaScript. Every REST route, the WebSocket streams, signed answers and a wallet that signs locally.
@tenrai/sdk is the client for the node API and the Tenrai API. It runs in Node.js 22.12 or later and in browsers, from TypeScript and plain JavaScript.
npm install @tenrai/sdkConnect
import { TenraiClient } from "@tenrai/sdk";
const api = new TenraiClient(); // the Tenrai API
const node = new TenraiClient({ url: "http://127.0.0.1:28701", target: "node" }); // your own node| Option | Default | Meaning |
|---|---|---|
url | https://api.tenrai-technologies.org | A path prefix such as /tenrai is kept |
target | "api" | "api" paces requests for the Tenrai API, "node" sends without a limit |
timeoutMillis | 15000 | Per request |
retries | 2 | For reads refused with 429 or 503, or without an answer |
verify | signedResponses(…), see below | |
fetch, WebSocket | the runtime's | For example WebSocket from the ws package |
With target: "api" the client sends at most 10 reads per second in bursts of 20, 8 at a time, and one submission at a time, at most two per second. That keeps it below the limits of the Tenrai API.
Read
Each group of routes is a namespace on the client:
await tenrai.chain.head();
await tenrai.address.balance(address);
await tenrai.address.balances([a, b, c]); // up to 64 in one request
await tenrai.transactions.status(transactionId);
await tenrai.fees.estimate();
await tenrai.staking.validators();Answers are the API's JSON documents, typed. pages walks paged routes:
import { pages } from "@tenrai/sdk";
for await (const page of pages((cursor) => tenrai.address.history(address, { cursor }))) {
console.log(page.events.length);
}Wallet
@tenrai/sdk/wallet builds and signs with the protocol rules compiled to WebAssembly, the same code the node runs. Keys stay inside the WASM.
import { Wallet } from "@tenrai/sdk/wallet";
const wallet = await Wallet.fromMnemonic(phrase, { network: "mainnet" });
wallet.address(0); // first receiving address
const { atoms } = await wallet.balance(tenrai);
const sent = await wallet.send(tenrai, { to, amount, payload: "invoice 42" });
const payout = await wallet.send(tenrai, { payments: [{ to: alice, amount: a }, { to: bob, amount: b }] });
wallet.close();| Call | Use |
|---|---|
prepare, submit | Sign first, send later, for a confirm step in between |
maxSendable | The most one transfer can send |
sweep, compound | Everything to one address; merge the outputs of each address |
burn | Pays an amount to the burn address of the wallet's network. It cannot be undone |
mergeFor | Compounds first when an amount needs more outputs than one transfer spends |
resend | Sends transfers whose answer was lost again while they can still execute |
bond, unbond, withdraw, positions | Staking |
newAddress, owns, rescan | Address handling |
proveAddress | Answers an address ownership request |
Messenger
wallet.messenger(tenrai) chats with other wallets, TCC-0009. Every frame pays one atom to the other side and its fee; the messenger key stays in the WASM.
const messenger = wallet.messenger(tenrai);
messenger.id(); // TRM-4CA0-35E2-…, the same on every device of this wallet
await messenger.start(address, "Hi"); // the note is public
const chats = await messenger.chats(); // rebuilt from the wallet's history
const request = chats.find((chat) => chat.state === "request");
await messenger.accept(request, "Hello"); // spends a coin of the address the request reached
await messenger.send(request, "From here on end-to-end encrypted");| State | Meaning |
|---|---|
request | Someone wrote to this wallet; accept opens the chat |
waiting | This wallet asked; it can write once the other side accepts |
open | Both sides write, every text end-to-end encrypted |
MESSENGER_LIMITS gives the longest note (218 bytes), accept text (202) and message (970). chats throws when a busy node left transactions unread; what it read stays cached, so the next call asks only for the rest. Show a chat's text escaped, never as markup.
An app that keeps only a limited transaction history would lose older messages with it. exportFrames() returns the messenger frames the wallet has seen, by default up to 10,000, to store apart from the history; importFrames(frames) hands them to a new messenger, so its chats include messages older than the kept history.
Failures throw TenraiWalletError with a code: insufficient, compound-needed, fee-ceiling, fee-unsettled, too-heavy, too-many-outputs, wrong-network, invalid, rejected or unconfirmed.
Streams
const events = tenrai.events({ afterHeight: lastHeight }); // every finalized cut, resumed
events.on("cut", (event) => save(event.cut.height));
const status = tenrai.transactions.watch(transactionId); // one transaction
status.on("final", (update) => console.log(update.lifecycle));
const live = tenrai.live(); // subscriptions by topic
live.subscribe({ topic: "cuts", onEvent: (event) => console.log(event.data.cut.height) });waitForFinal(id, { validUntil }) resolves once a transaction is finalized or can no longer execute.
Signed answers
import { signedResponses } from "@tenrai/sdk/verify";
const tenrai = new TenraiClient({ verify: signedResponses({ identity: "c97d68ce…", chainId: "…" }) });Pin the identity the operator publishes. Every REST answer is then checked, and a missing or wrong signature throws TenraiSignatureError.
Errors
| Class | Thrown when |
|---|---|
TenraiApiError | The server refused: status, code, retryAfterMillis, retryable |
TenraiNetworkError, TenraiTimeoutError | No answer |
TenraiResponseError | The answer is not what the API defines |
TenraiSignatureError | A signature is missing or does not verify |
TenraiWalletError | The wallet cannot build the transfer |
All extend TenraiError.
Helpers
| Function | Import | Result |
|---|---|---|
parseTrai("12.00005") | @tenrai/sdk | 1200005000n |
formatTrai("1200005000") | @tenrai/sdk | "12.00005" |
addressVerificationWords(address) | @tenrai/sdk | The six verification words |
burnAddress(network), isBurnAddress(address) | @tenrai/sdk | The burn address of a network; whether an address is one |
createAddressProofRequest, verifyAddressProof | @tenrai/sdk | Address ownership |
paymentRequest, parsePaymentRequest | @tenrai/sdk/wallet | Payment requests |
isValidAddress(text, network) | @tenrai/sdk/wallet | Checks an address by the protocol rules |
isMessengerPayload(payload) | @tenrai/sdk/wallet | Whether a payload is a messenger frame, to label it instead of showing it |
isChatText(text) | @tenrai/sdk/wallet | The text rule of the messenger |
The package ships its full API reference and runnable examples.
