Skip to content

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

Open the app

Start here

Quickstart

Install @umbr/sdk, shield test stablecoins into a private balance, send a private payment and unshield it again, all on a testnet.

Time
About ten minutes
Network
Tempo Moderato
Cost
Test funds only

Before you start#

This page runs one complete loop from a Node script: shield test stablecoins, send part of them privately, check the balance, and take the rest back out. You need:

  • Node 20 or newer and a package manager (the examples use pnpm).
  • A fresh private key used for nothing else, holding some test pathUSD on Tempo Moderato. Get it from the network's public faucet; see Networks for chain details.
  • A network whose UMBR pool is configured. If it is not, the client throws POOL_NOT_CONFIGURED.

Preview package

@umbr/sdk is a preview and is not on npm yet. The names below are the intended API and may change before the first release. Everything here is testnet only.

1. Install#

The SDK uses viem for chain access, so install both.

$ pnpm add @umbr/sdk viem

Put the testnet key in an environment file and keep it out of version control.

.env
# .env (never commit this file)
AGENT_KEY=0x...   # a throwaway testnet private key

2. Create a client#

A client binds one network, one key and one place to keep your notes. On a server, pass a signer and a file store. The limits are optional: they are checked before any proof is made, so a bug cannot overspend.

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

export const umbr = createUmbrClient({
	network: networks.tempoModerato,
	signer: process.env.AGENT_KEY,
	storage: fileStore("./.umbr"),
	limits: { perPayment: "50.00", perDay: "200.00" },
});

console.log("private address:", umbr.address); // umbr1q...

In a browser app, pass a viem WalletClient instead of a key. Never put a private key in browser code.

browser.ts
import { createUmbrClient, networks } from "@umbr/sdk";
import { createWalletClient, custom } from "viem";

const wallet = createWalletClient({ transport: custom(window.ethereum) });

// Notes are kept in IndexedDB by default in the browser.
const umbr = createUmbrClient({ network: networks.tempoModerato, wallet });

3. Shield#

Shielding moves stablecoins from your public address into the pool. The SDK asks the token for an allowance if needed, then deposits. The deposit is visible on the explorer; what happens afterwards is not.

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

console.log("shielded in", txHash, "as", notes.length, "note(s)");

4. Send privately#

Send to an UMBR address. The SDK builds a zero-knowledge proof on your machine, which takes a few seconds, then submits it. With the default relayer the transaction is sent for you, so your public address does not appear on it. The memo is encrypted for the receiver.

send.ts
const { txHash, paymentId } = await umbr.send({
	to: "umbr1qexample7k3v0d9w2ana",
	asset: "pathUSD",
	amount: "12.50",
	memo: "Quickstart test",
});

// Keep paymentId if you may want to prove this payment later.
console.log("paid", paymentId, "in", txHash);

No second address handy? Create another client with a second key and send to its umbr.address.

5. Check the balance#

Your private balance is the sum of your unspent notes. Sync first so the client picks up anything new from the chain, then read it.

check.ts
await umbr.notes.sync();

console.log(await umbr.balance("pathUSD")); // "87.50"
console.log(await umbr.balances());          // { pathUSD: "87.50" }

const open = await umbr.notes.list({ asset: "pathUSD" });
console.log(open.filter((n) => !n.spent).length, "unspent note(s)");

6. Unshield#

Unshielding pays out from the pool to any public address. The amount and destination become public; the link to your original deposit does not. Errors come back as UmbrError with a stable code.

unshield.ts
import { UmbrError } from "@umbr/sdk";

try {
	const { txHash } = await umbr.unshield({
		asset: "pathUSD",
		amount: "20.00",
		to: "0xYourPublicWallet",
	});
	console.log("unshielded in", txHash);
} catch (err) {
	if (err instanceof UmbrError && err.code === "INSUFFICIENT_PRIVATE_BALANCE") {
		console.error("Not enough private pathUSD for that amount.");
	} else {
		throw err;
	}
}

Look up the transaction on the Tempo testnet explorer. It shows the pool paying your wallet and nothing about the payment in step 4.

Next steps#