Private stocks are live. Swap TSLA, NVDA, AAPL and more on Robinhood Chain from your private balance.

Swap privately

Start here

Quickstart

Go from an empty project to a real private balance in about ten minutes, on mainnet or on the free testnet. Every step below runs: no mocks, real Groth16 proofs.

Time
~10 minutes
Runtime
browser or node
Chain
RH mainnet 4663, or testnet 46630
You end with
a spendable private note

Fastest start

Scaffold a working private app in one command:

npm create gloam-app@latest my-private-app
cd my-private-app && npm install && npm run dev

That gives you the browser shield flow, wired and running on localhost:3000. The rest of this page builds the same thing by hand so you understand each piece.

What you need

  • Node 18+ (or a browser app with a bundler, such as Next or Vite). The SDK ships TypeScript source, transpiled by your bundler.
  • For mainnet: USDG on Robinhood Chain (chain id 4663) plus a little ETH for gas, or USDC.e, pathUSD, USDT0 or OUSD on Tempo (4217), where gas is paid in stablecoins. Beta limits cap each stablecoin deposit at 500. See Networks.
  • To practice first: a wallet on Robinhood Chain testnet (46630) with test ETH. See practice on testnet.
  • The circuit artifacts for your chain (step 2).

1. Install

viem handles signing and RPC; snarkjs is the proving peer, loaded only when you generate a proof.

npm install @gloamtrade/sdk viem
npm install snarkjs   # peer, for proving

2. Get the circuit artifacts

Every deposit and spend needs a proof, so you need each circuit's wasm and zkey. The keys depend on the network: mainnet uses the final keys from the 13-contributor ceremony, the testnets use development keys, and a proof made with the wrong set does not verify. circuitArtifactUrls(circuit, chainId) returns the right { wasm, zkey } for a chain, served from https://www.gloam.trade/circuits (mainnet keys live under /circuits/final/).

import { circuitArtifactUrls } from "@gloamtrade/sdk";

circuitArtifactUrls("shield", 4663);   // final keys, Robinhood Chain mainnet
circuitArtifactUrls("shield", 46630);  // dev keys, Robinhood Chain testnet

Base Sepolia (84532) is the exception among the testnets: it runs the mainnet vault, so its proofs use the final keys too. SDK 0.3.0 knows this, and has Base Sepolia's pool.

You can also host the files yourself and pass them to artifactProver({ wasm, zkey }): file paths in node, URLs in the browser. Just keep mainnet on the final keys.

3. Shield your first note

buildShieldBoundIntent does the private half: it mints a note (a Poseidon commitment binding your secret to the amount and asset) and generates the shield proof. You sign the resolved call. The note.secret it returns is your only spend authority, so persist it. On mainnet the Robinhood Chain pool takes USDG and $GLOAM, not native ETH, so you approve the token first.

import {
  mainnetNetwork, buildShieldBoundIntent, artifactProver,
  circuitArtifactUrls, RH_MAINNET_USDG, gloamReadTransport,
} from "@gloamtrade/sdk";
import { createWalletClient, createPublicClient, http, parseUnits, erc20Abi } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// Robinhood Chain mainnet. null until the pool is published.
const net = mainnetNetwork("robinhood");
if (!net) throw new Error("Robinhood Chain mainnet pool not published yet");

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: net.chain, transport: http(net.rpcUrl) });
// reads at the chain head, old logs from the network's full-history RPC where it names one
const publicClient = createPublicClient({ chain: net.chain, transport: gloamReadTransport(net) });

const amount = parseUnits("25", 6);   // 25 USDG (6 decimals); beta cap is 500 per deposit

// mint the note + generate the shield proof with this chain's keys
const intent = await buildShieldBoundIntent({
  chainId: net.chainId,
  poolAddress: net.pool,
  asset: RH_MAINNET_USDG,
  amountWei: amount,
  prover: artifactProver(circuitArtifactUrls("shield", net.chainId)),
});

// the pool pulls USDG with transferFrom, so approve it first
const approval = await wallet.writeContract({
  address: RH_MAINNET_USDG, abi: erc20Abi, functionName: "approve", args: [net.pool, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approval });

// sign + broadcast shieldBound(asset, amount, commitment, proof)
const hash = await wallet.writeContract({
  address: intent.exec.poolAddress,
  abi: SHIELD_BOUND_ABI,
  functionName: intent.exec.fn,   // "shieldBound"
  args: intent.exec.args,
  value: intent.exec.valueWei,    // 0n for tokens
});

// PERSIST THIS. Losing it loses the funds.
saveSecret(intent.note.commitment, intent.note.secret);

The minimal ABI fragment for the one call you make:

const SHIELD_BOUND_ABI = [{
  type: "function",
  name: "shieldBound",
  stateMutability: "payable",
  inputs: [
    { name: "asset", type: "address" },
    { name: "amount", type: "uint256" },
    { name: "commitment", type: "bytes32" },
    { name: "proof", type: "bytes" },
  ],
  outputs: [],
}] as const;

Practice on testnet with the same code. Use GLOAM_NETWORKS.robinhood (Robinhood Chain testnet, 46630) as net, and either shield test ETH (leave out asset, skip the approve, and the deposit goes in valueWei) or test USDG (RH_USDG). circuitArtifactUrls hands you the dev keys there. On Tempo, use mainnetNetwork("tempo") with TEMPO_USDCE, or GLOAM_NETWORKS.tempo to practice. On Base Sepolia, use testnetNetwork("base") (SDK 0.3.0) with BASE_SEPOLIA_USDC; it runs the mainnet vault and keys with test funds. mainnetNetwork("base") returns null until Base mainnet's pool is published, then the same code runs there with BASE_USDC. Ethereum is next the same way: mainnetNetwork("ethereum") (SDK 0.4.0) returns null until its pool is published, then runs with ETHEREUM_USDC. For USDT on Ethereum, send the approvals approvalSteps gives you: Tether's token clears an old approval to zero first.

4. Confirm it landed

The deposit inserts your commitment as a leaf in the pool's Merkle tree. Check membership directly, with no index and no backend:

const seen = await publicClient.readContract({
  address: intent.exec.poolAddress,
  abi: [{ type: "function", name: "commitmentSeen", stateMutability: "view",
    inputs: [{ name: "c", type: "bytes32" }], outputs: [{ type: "bool" }] }],
  functionName: "commitmentSeen",
  args: [intent.note.commitment],
});
// seen === true  ->  you hold a private balance

That is a real shielded balance. Nobody can tell it is yours or spend it without note.secret.

5. Cash out

To exit, rebuild the tree from chain, prove membership, and unshield. syncTree replays every leaf-inserting event in order (so the root matches even after transfers); buildUnshieldIntent builds the witness and proof. The amount, asset, and recipient become public on exit; the source note stays unlinkable via the nullifier. On mainnet a cash out costs 0.25%: cashOutFee(amount) is what the pool keeps.

import {
  buildUnshieldIntent, artifactProver, circuitArtifactUrls, syncTree, cashOutFee,
} from "@gloamtrade/sdk";

// rebuild the pool tree, then get this note's membership path by commitment
const synced = await syncTree(publicClient, { pool: net.pool, fromBlock: net.deployBlock });
const path = await synced.pathForCommitment(intent.note.commitment);
if (!path) throw new Error("note not found in the tree yet");

const exit = await buildUnshieldIntent({
  chainId: net.chainId,
  poolAddress: net.pool,
  asset: RH_MAINNET_USDG,
  secretHex: intent.note.secret,
  amountWei: amount,
  to: account.address,
  path,
  prove: artifactProver(circuitArtifactUrls("unshield", net.chainId)),
});

// mainnet: the pool keeps cashOutFee(amount), 0.25%, and pays the rest to "to"
console.log("fee", cashOutFee(amount));

await wallet.writeContract({
  address: exit.exec.poolAddress,
  abi: unshieldAbi,
  functionName: exit.exec.fn,     // "unshield"
  args: exit.exec.args,
});

Where to go next

  • API reference: every builder, the network helpers, the note and prover primitives, merkle, rates, and constants.
  • Private pay: send inside the vault to a receive tag with buildPrivateSendIntent.
  • Selective disclosure: prove one balance to a chosen party, revealing nothing else.
  • Build a private agent: the same core, server-side, under policy.

Honesty

Mainnet is beta software. It runs on the final keys from the 13-contributor ceremony, under beta deposit limits, after an internal review; an external audit is planned. The testnets run on dev-ceremony keys with test funds. Everything here is real: real proofs, real on-chain state, no mocks. Private swaps run in the app on both mainnets (private swaps); the SDK does not build them yet. The old sealed trade is retired. See the production gate.