Skip to content

UMBR is live on testnet. Private payments with test funds on Robinhood Chain and Tempo.

Open the app

Build

API reference

Every public export of @umbr/sdk, its arguments and what it returns. The API is a preview and may change before the first release.

Package
@umbr/sdk
Status
Preview
Amounts
Decimal strings

Preview

Not on npm yet. Names and shapes below may change before the first release.

Conventions: every amount is a decimal string in the asset's units ("40.00"), assets are named by symbol ("pathUSD", "USDG"), and every method that writes returns once the transaction is included. Failures throw an UmbrError.

Import#

imports.ts
import {
	createUmbrClient,
	verifyProof,
	networks,
	fileStore,
	UmbrError,
} from "@umbr/sdk";

import type { Proof, VerifyResult, Note, PayrollLine, ClientOptions, NoteStore } from "@umbr/sdk";

fileStore(path) returns a NoteStore that writes to a folder, for Node. In browsers the default store is IndexedDB.

createUmbrClient#

ts
function createUmbrClient(options: ClientOptions): UmbrClient;
NameTypeNotes
networkNetworkRequired. One of networks.
walletWalletClientA viem wallet. Use in browsers. Pass this or signer.
signer0x${string}A private key. Server and agent code only, read from a secret store.
relayer"default" | string | falseOptional. A custom relayer URL, or false to submit from your own wallet. Default "default".
limits{ perPayment?, perDay? }Optional hard caps, checked before any proof is made.
storageNoteStoreOptional. Defaults to IndexedDB in browsers and fileStore("./.umbr") in Node.

Returns an UmbrClient. Its address property is your private receive address (umbr1q...), derived locally from the wallet or signer.

Example
const umbr = createUmbrClient({
	network: networks.tempoModerato,
	wallet,                                    // browser
	relayer: "default",
	limits: { perPayment: "2.00", perDay: "50.00" },
});

umbr.address; // "umbr1qexample...you"

networks#

ts
const networks: {
	tempoModerato: Network;     // Tempo testnet, chain id 42431
	robinhoodTestnet: Network;  // Robinhood Chain testnet, chain id 46630
	tempo: Network;             // chain id 4217, usable only once configured
	robinhood: Network;         // chain id 4663, usable only once configured
};

Each Network has chainId, name, assets and configured. A network is configured only when its pool address and start block are set; otherwise writes throw POOL_NOT_CONFIGURED. Mainnet entries are not configured today. See Networks.

Example
const { chainId, assets, configured } = networks.robinhoodTestnet;
// 46630, ["USDG"], true when NEXT_PUBLIC_UMBR_POOL_46630 and its start block are set

umbr.shield#

ts
umbr.shield(args: { asset: string; amount: string }): Promise<{ txHash: `0x${string}`; notes: Note[] }>;
NameTypeNotes
assetstringSymbol of a supported stablecoin on the client's network.
amountstringHow much public balance to move in. Approves the pool first if needed.

Returns the deposit txHash and the new notes. Shielding is visible on chain: the explorer shows your public address depositing this amount.

Example
const { txHash, notes } = await umbr.shield({ asset: "pathUSD", amount: "250.00" });

umbr.send#

ts
umbr.send(args: {
	to: string;        // "umbr1q..."
	asset: string;
	amount: string;
	memo?: string;
}): Promise<{ txHash: `0x${string}`; paymentId: string }>;
NameTypeNotes
tostringThe recipient's umbr1q... address. A 0x address throws.
assetstringAsset to pay in.
amountstringChecked against limits first.
memostring?Optional note encrypted for the recipient only.

Returns txHash and a paymentId you can later pass to umbr.prove.

Example
const { paymentId } = await umbr.send({
	to: "umbr1qexample...ana",
	asset: "pathUSD",
	amount: "40.00",
	memo: "Invoice 0192",
});

umbr.links

Claim links pay someone who has no UMBR address yet. create locks the amount; anyone holding the link can claim it until it expires; revoke returns unclaimed funds to you.

ts
umbr.links.create(args: { asset: string; amount: string; expiresIn: "1d" | "7d" | "30d" }): Promise<{ url: string; id: string }>;
umbr.links.claim(url: string): Promise<{ txHash: `0x${string}` }>;
umbr.links.revoke(id: string): Promise<{ txHash: `0x${string}` }>;

umbr.unshield#

ts
umbr.unshield(args: { asset: string; amount: string; to: `0x${string}` }): Promise<{ txHash: `0x${string}` }>;
NameTypeNotes
assetstringAsset to withdraw.
amountstringChecked against limits first.
to0x${string}Public destination. Bound inside the proof, so a relayer cannot change it.

Returns txHash. The withdrawal amount and destination are public.

Example
await umbr.unshield({ asset: "pathUSD", amount: "60.00", to: "0xYourPublicWallet" });

umbr.balance#

ts
umbr.balance(asset: string): Promise<string>;
umbr.balances(): Promise<Record<string, string>>;
NameTypeNotes
assetstringSymbol to total. Unknown symbols throw.

Returns the sum of unspent notes as a decimal string. balances() returns every asset on the network. Both read the local note store; call notes.sync() first for fresh numbers.

Example
await umbr.balance("pathUSD"); // "210.00"
await umbr.balances();         // { pathUSD: "210.00", AlphaUSD: "0.00" }

umbr.notes#

ts
umbr.notes.sync(): Promise<void>;
umbr.notes.list(filter?: { asset?: string }): Promise<Note[]>;
umbr.on(event: "payment", handler: (p: IncomingPayment) => void): () => void;
NameTypeNotes
filter.assetstring?Optional. Only notes in this asset.
event"payment"Fires during sync for each newly found incoming payment.
handler(p) => voidReceives { paymentId, asset, amount, memo, from: undefined }.

Returns: sync resolves when the store is up to date with the pool; list returns Note[], spent ones included; on returns an unsubscribe function.

Example
const off = umbr.on("payment", (p) => console.log(p.amount, p.asset, p.memo));
await umbr.notes.sync();
const notes = await umbr.notes.list({ asset: "pathUSD" });
off(); // stop listening

umbr.payroll#

ts
umbr.payroll.run(args: { asset: string; lines: PayrollLine[] }): Promise<{
	id: string;
	txHashes: `0x${string}`[];
	lines: (PayrollLine & { status: "settled" | "failed" })[];
}>;
umbr.payroll.parseCsv(text: string): PayrollLine[];
umbr.payroll.schedule(args: {
	asset: string;
	lines: PayrollLine[];
	every: "week" | "month";
	startAt: string | Date;
}): Promise<{ id: string }>;
NameTypeNotes
assetstringOne asset per run.
linesPayrollLine[]Recipients and amounts. The whole run counts towards limits.
textstringCSV with columns address,amount and an optional label. A header row is allowed.
every"week" | "month"Schedule interval.
startAtstring | DateFirst run time, ISO 8601 when a string.

Returns: run gives the run id, every txHash and the status of each line; parseCsv gives lines and throws on a malformed row; schedule gives a schedule id.

Example
const lines = umbr.payroll.parseCsv(`address,amount,label
umbr1qexample...ana,4200.00,Design
umbr1qexample...ben,3850.00,Support`);

const run = await umbr.payroll.run({ asset: "USDG", lines });

umbr.prove#

ts
umbr.prove(args: {
	claim: "balance-exact" | "balance-at-least" | "payment" | "payroll-total";
	asset?: string;
	amount?: string;
	payment?: string;               // paymentId, for claim "payment"
	run?: string;                   // run id, for claim "payroll-total"
	reveal?: "exact" | "at-least";  // for claim "payment"
	audience: string;
	expiresIn: "1d" | "7d" | "30d";
}): Promise<Proof>;
NameTypeNotes
claimstringWhich of the four proofs to make.
assetstring?Required for balance claims.
amountstring?The floor for at-least and for a payment with reveal: "at-least".
paymentstring?A paymentId from send or a payment event.
runstring?A payroll run id you sent.
reveal"exact" | "at-least"Payment proofs only. Default "exact".
audiencestringWho the proof is for. Bound into the proof.
expiresIn"1d" | "7d" | "30d"Bound into the proof.

Returns a Proof. Proving runs locally and never touches the chain.

Example
const proof = await umbr.prove({
	claim: "payment",
	payment: paymentId,
	reveal: "at-least",
	amount: "300.00",
	audience: "Northwind Leasing",
	expiresIn: "7d",
});
proof.link; // a /verify link you can share

verifyProof#

ts
function verifyProof(input: Proof | string, options?: { network?: Network }): Promise<VerifyResult>;
NameTypeNotes
inputProof | stringA Proof object, its JSON, or a /verify link.
options.networkNetwork?Optional. Otherwise read from the proof.

Returns a VerifyResult. An invalid proof resolves with valid: false rather than throwing; only malformed input throws. Needs no wallet.

Example
const result = await verifyProof(pastedJsonOrLink);
if (!result.valid || result.expired) reject();
else accept(result.statement, result.audience);

Errors#

Every failure the SDK expects is an UmbrError with a stable code and a readable message.

CodeWhen
LIMIT_EXCEEDEDA send, unshield, link or payroll run would go over perPayment or perDay. Raised before any proof is made.
INSUFFICIENT_PRIVATE_BALANCEYour unspent notes in that asset do not cover the amount (plus relayer fee).
POOL_NOT_CONFIGUREDThe network has no pool address or start block set, so it is read-only.
PROOF_FAILEDProof generation failed, usually from stale notes. Sync and try again.
RELAYER_REJECTEDThe relayer refused or could not submit the transaction. Nothing was spent.
UNKNOWN_ROOTA proof refers to a Merkle root the pool has not recorded.
PROOF_EXPIREDA disclosure proof is past its expiry.
NETWORK_MISMATCHThe wallet, address or proof belongs to a different chain than the client.
Example
import { UmbrError } from "@umbr/sdk";

try {
	await umbr.send({ to, asset: "pathUSD", amount: "75.00" });
} catch (err) {
	if (!(err instanceof UmbrError)) throw err;
	switch (err.code) {
		case "INSUFFICIENT_PRIVATE_BALANCE": return askToShield();
		case "RELAYER_REJECTED": return retryWithoutRelayer();
		default: throw err;
	}
}

Types#

types.ts
type Proof = {
	format: "umbr.disclosure/1";
	claim: "balance-exact" | "balance-at-least" | "payment" | "payroll-total";
	json: string;   // the full proof (format "umbr.disclosure/1"), safe to send to the reader
	text: string;   // the same proof as one line: "umbrproof1:" + base64url(json)
	link: string;   // https://umbr.trade/verify#... with the proof embedded
};

type VerifyResult = {
	valid: boolean;
	claim: Proof["claim"];
	statement: string;   // e.g. "Holds at least 1,000.00 pathUSD"
	audience: string;
	expiresAt: Date;
	expired: boolean;
};

type Note = {
	commitment: `0x${string}`;
	asset: string;
	amount: string;
	spent: boolean;
	createdAt: Date;
};

type PayrollLine = {
	to: string;      // "umbr1q..."
	amount: string;
	label?: string;  // stays on your device
};

type ClientOptions = {
	network: Network;
	wallet?: WalletClient;          // viem, for browsers
	signer?: `0x${string}`;        // private key, for servers and agents only
	relayer?: "default" | string | false;
	limits?: { perPayment?: string; perDay?: string };
	storage?: NoteStore;
};

interface NoteStore {
	get(key: string): Promise<string | undefined>;
	set(key: string, value: string): Promise<void>;
	delete(key: string): Promise<void>;
	keys(prefix?: string): Promise<string[]>;
}