SDK quickstart
From a wallet to a private transfer in a few lines — derive keys, create the client, shield, send, and unshield.
This walks through a full lifecycle in Node: derive an identity, create a client, shield USDC, pay another agent privately, and withdraw to a clean address. It assumes you have a deployed pool + registry and the proving artifacts available on disk.
1 · Derive an identity
An identity is a deterministic function of one signature. With an in-process key:
import { privateKeyToAccount } from "viem/accounts";
import { keccak256 } from "viem";
import { deriveKeysFromSeed } from "@hestia/sdk";
const account = privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`);
// derive the Hestia seed however you like; here, from the account key
const keys = await deriveKeysFromSeed(keccak256(account.address, "bytes"));In a browser you'd instead sign the canonical message and use
deriveKeysFromSignature — see keys & identity.
2 · Create the client
import { base, baseSepolia } from "viem/chains";
import { Hestia, AssociationSet, USDC_ADDRESS, type ArtifactsByArity } from "@hestia/sdk";
const artifacts: ArtifactsByArity = {
"1x2": { wasm: "./circuits/transaction1x2.wasm", zkey: "./circuits/transaction1x2.zkey" },
"2x2": { wasm: "./circuits/transaction2x2.wasm", zkey: "./circuits/transaction2x2.zkey" },
};
const hestia = await Hestia.create({
chain: baseSepolia,
rpcUrl: "https://sepolia.base.org",
pool: POOL_ADDRESS,
registry: REGISTRY_ADDRESS,
usdc: USDC_ADDRESS.baseSepolia,
account, // an Account in Node
keys,
association: await AssociationSet.create(), // see association-providers
artifacts,
});
console.log("Pay me at:", hestia.metaAddress); // hestia1…3 · Shield
Move a public balance into the pool. For an ERC-20 the SDK handles the approve for you; for
ETH it sends msg.value.
import { parseUnits } from "viem";
await hestia.shield({
token: USDC_ADDRESS.baseSepolia,
amount: parseUnits("25", 6), // USDC has 6 decimals
});
await hestia.sync();
const bal = await hestia.balance(USDC_ADDRESS.baseSepolia);
console.log("private USDC:", bal); // 25_000_000n4 · Send privately
Pay another agent by their meta-address. This generates a Groth16 proof on the device, then submits.
await hestia.sync(); // discover your notes first
await hestia.send({
token: USDC_ADDRESS.baseSepolia,
amount: parseUnits("10", 6),
to: "hestia1…recipient",
});No amount, sender, or recipient is revealed on-chain — only two new commitments and two ciphertexts appear.
5 · Unshield
Withdraw to a clean public address. The amount and destination are public, but unlinkable to your deposit.
await hestia.sync();
await hestia.unshield({
token: USDC_ADDRESS.baseSepolia,
amount: parseUnits("5", 6),
to: "0xCleanRecipient…",
});The whole thing
const hestia = await Hestia.create({ /* …config… */ });
await hestia.shield({ token, amount: parseUnits("25", 6) });
await hestia.sync();
await hestia.send({ token, amount: parseUnits("10", 6), to: payee });
await hestia.unshield({ token, amount: parseUnits("5", 6), to: cleanAddress });
await hestia.sync();
console.log(await hestia.balance(token)); // remaining private balanceAlways
sync()before reading a balance or spending. It pulls the latest events so the SDK sees your newest notes and the current tree. The operations that spend do this for you, but calling it explicitly after a deposit keeps your view fresh.
Next: The Hestia client for every config field, then
operations for what shield / send / unshield do under the hood.
