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

Swap privately

Build on Gloam

SDK

The privacy layer for Robinhood Chain, Tempo, Base and Ethereum, as a package. Give any app or agent shielded balances, private payments, and selective disclosure, without building a proving stack from scratch.

Package
@gloamtrade/sdk
Runtime
browser + node
Chains
Mainnet 4663 · 4217 · 8453 · 1, testnet 46630 · 42431 · 84532 · 11155111
Proofs
Groth16 + Poseidon

What Gloam gives your app

Robinhood Chain, Tempo, Base and Ethereum are public: every balance, size, and move is visible. Gloam is the shielded chamber on top of them, and @gloamtrade/sdk is that chamber as a dependency. The Gloam vault app is the reference implementation, not a special case: the same core runs in your app.

  • Shielded balances. Deposit stablecoins (and $GLOAM on Robinhood Chain) into a private balance only the holder can see or spend. The testnets also take test ETH and stock tokens.
  • Private payments. Send inside the vault to a receive tag, with on-chain encrypted discovery.
  • Selective disclosure. Let a holder prove one balance to a chosen party, such as an auditor or a counterparty, revealing nothing else. See the guide.
  • Cash out. Unshield to a public balance with a real browser- or node-generated Groth16 proof.

Install

The SDK ships TypeScript source, consumed through your bundler (Next transpilePackages, Vite, tsx), the same way the reference app uses it. snarkjs is an optional peer, needed only when you generate proofs (shield, unshield, disclosure).

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

Mainnet and testnet

From 0.1.0 the SDK knows the Robinhood Chain and Tempo mainnets and testnets; 0.3.0 adds Base and Base Sepolia, and 0.4.0 Ethereum and Ethereum Sepolia. The builders still default to Robinhood Chain testnet so older code keeps working, so pass a mainnet network explicitly.

  • mainnetNetwork("robinhood" | "tempo" | "base" | "ethereum") returns a mainnet GloamNetwork, or null until its pool is published. gloamNetworks() lists every network Gloam runs on right now, mainnets first. getGloamNetwork(key) takes "robinhood-mainnet", "tempo-mainnet", "base-mainnet", "ethereum-mainnet", "robinhood", "tempo", "base" (Base Sepolia) or "ethereum" (Ethereum Sepolia).
  • GLOAM_NETWORKS.robinhood and .tempo stay the testnets, unchanged.
  • Builders resolve the pool from chainId when poolAddress is left out, and refuse a mainnet chain whose pool is not in the SDK yet.
  • Mainnet proofs must use the final keys from the 13-contributor ceremony. circuitArtifactUrls(circuit, chainId) picks the right set for the chain; ceremonyForChainId tells you which one it is.
  • On Robinhood Chain mainnet the pool takes USDG (RH_MAINNET_USDG) and $GLOAM (GLOAM_TOKEN), not native ETH. On Tempo mainnet it takes TEMPO_USDCE, TEMPO_PATHUSD, TEMPO_USDT0 and TEMPO_OUSD.
  • Beta deposit limits: 500 per deposit and 25,000 in total for each stablecoin; $GLOAM 25,000 per deposit and 250,000 in total. Each network's assets carry the depositLimit.
  • A mainnet cash out costs 0.25% (CASH_OUT_FEE_BPS = 25); cashOutFee(amount) is what the pool keeps. Deposits and private payments are free.

The model

Gloam is a shielded pool. A private balance is a note:

commitment = Poseidon(secret, amount, asset)
nullifier  = Poseidon(secret, commitment)

Shielding inserts the commitment as a leaf in a depth-20 incremental Merkle tree; the secret is the only spend authority and never leaves the client. Spending (send, cash out, trade) proves in zero knowledge that you know the secret for a commitment in the tree, and publishes the nullifier so it cannot be spent twice, without revealing which note. The chain verifies a Groth16 proof, never your identity or size. Nothing is a mock; if a path cannot be private, it waits.

Quickstart: shield privately

The hardened pool enforces a proof at deposit (audit C1), so plain shield() reverts. buildShieldBoundIntent mints the note and generates the shield proof; you sign the resolved call.

import {
  mainnetNetwork, buildShieldBoundIntent, artifactProver,
  circuitArtifactUrls, TEMPO_USDCE,
} from "@gloamtrade/sdk";

const net = mainnetNetwork("tempo");          // null until the pool is published
if (!net) throw new Error("Tempo mainnet pool not published yet");

const intent = await buildShieldBoundIntent({
  chainId: net.chainId,
  poolAddress: net.pool,
  asset: TEMPO_USDCE,
  amountWei: 25_000_000n,                    // 25 USDC.e (6 decimals); beta cap is 500 per deposit
  prover: artifactProver(circuitArtifactUrls("shield", net.chainId)),
});

// approve the pool for the token first (it pulls it with transferFrom), then
// sign intent.exec: a ready shieldBound(asset, amount, commitment, proof) call.
// intent.note.secret is the spend key. PERSIST IT.
await wallet.writeContract({
  address: intent.exec.poolAddress,
  abi: shieldPoolAbi,
  functionName: intent.exec.fn,      // "shieldBound"
  args: intent.exec.args,
});

To practice on the free testnet, use GLOAM_NETWORKS.tempo (or GLOAM_NETWORKS.robinhood, where you can also shield test ETH) as net; the same code proves with the dev keys there. A complete runnable agent is in examples/agent-shield. See the quickstart and Build a private agent.

Intents: plan and exec

Every action is an intent with two layers. The plan is portable and safe to log or hand to an agent (symbol, amount, no secrets). The exec resolves the on-chain call (wei amounts, resolved addresses, proof args). The SDK and the agent server share one intent shape.

BuilderResolves
buildShieldBoundIntentMints a note + shield proof → shieldBound(...) (the live deposit path)
buildShieldIntentPlain shield(...), only for pools without a shield verifier
buildUnshieldIntentWitness + proof → unshield(...) (cash out)
buildPrivateSendIntentTransfer witness → transfer(...) to a receive tag

API surface

Notes

import { makeBoundNotePoseidon, noteNullifierPoseidon } from "@gloamtrade/sdk";

const note = await makeBoundNotePoseidon(amountWei, assetAddress);
// { secret, commitment, secretField, commitmentField }
const nullifier = await noteNullifierPoseidon(note.secretField, note.commitmentField);

Prover (injected)

Proving is environment-bound, so it is passed in. artifactProver binds snarkjs to your circuit artifacts (paths in node, URLs in the browser); proveGroth16 is the one-shot form.

import { artifactProver, proveGroth16 } from "@gloamtrade/sdk";

const prover = artifactProver({ wasm, zkey });
const { proofBytes, publicSignals } = await prover(circomInput);

Merkle

A depth-20 incremental tree and a circom path builder (buildPoseidonMerklePath) for spend proofs. Rebuild the tree from the pool's Shielded events, then prove membership.

Rates & privacy

Pure sealed-rate math (exactSealedAmounts) and the size-privacy floor policy (publicAmountOutMin), proven against the circuit equality amountOut·rateOut = amountSwap·rateIn.

Constants

SEALED_VAULT (the hardened Robinhood Chain testnet pool), SHIELD_VERIFIER, NATIVE_ASSET, the chain ids, and for mainnet MAINNET_POOLS, the asset addresses and MAINNET_POSEIDON_T3. The default SEALED_VAULT is the hardened C1/C2/C3 pool, never the retired 0x4F38 pool.

Integration patterns

  • Private balances in your app. Shield with buildShieldBoundIntent, persist note.secret in your own store keyed by commitment, cash out with the unshield builder.
  • Private payments. Send to a receive tag; the recipient scans the encrypted GloamPayMemo inbox and opens the note (on mainnet, until it has a board, the payer hands over the sealed code). See Private pay and Take payments and read their notes.
  • Compliance without opacity. A holder proves one balance to an auditor via a disclosure, revealing nothing else.
  • Autonomous agents. The same core runs server-side, so an AI agent can hold and move value privately under policy. See Agents.

Take payments and read their notes

New in 0.2.0. A service can ask to be paid privately, with a note on each payment (an invoice number, an agent ID), and credit each payment by its note. Only your key reads the note, and nothing on chain ties the payer to it.

  1. Hold a receive key. generateReceiveKey() gives you a Gloam address (gloamr1.…) to share and a private key to keep on your server.
  2. Make a request link. buildPaymentRequestLink builds the same link the Gloam app makes: your address, the network, the token, an optional amount, and a note of up to 80 characters. A longer note is refused, not cut.
  3. The payer pays it from their private balance in Gloam. The note travels inside the payment, sealed to your address.
  4. Find it. Where the network has a payment-message board, scanIncomingPayments finds every payment sealed to your key, with its amount, token and note. It only returns payments that are in the vault and not yet spent, read at one block. Resume each scan from scannedTo + 1.
  5. Secure it, then credit. Until you move it, the payer could still spend it back. sweepReceivedNote moves it into a note only you know, through the Gloam relay, so your server needs no gas. Credit the payment only when the sweep returns final: true.
import {
  mainnetNetwork, generateReceiveKey, buildPaymentRequestLink,
  scanIncomingPayments, openIncomingPayment, notePackageToExport,
  sweepReceivedNote, sweepChainFromClient, relaySubmitter, findRequestAsset,
} from "@gloamtrade/sdk";

const net = mainnetNetwork("robinhood")!;
const key = await generateReceiveKey();       // keep key.privateJwk secret
const usdg = findRequestAsset(net, "USDG")!;  // the token you credit

const link = buildPaymentRequestLink({
  payTo: key.tag, network: net, asset: "USDG", amount: "25", note: "agent:42",
});

// With a payment-message board: scan for payments to your key.
const { payments, scannedTo } = await scanIncomingPayments({ client, network: net, receiveKey: key });

// Or check a sealed code a payer hands over directly.
const check = await openIncomingPayment({ client, network: net, receiveKey: key, sealedTicket: code });

for (const p of payments) {
  const r = await sweepReceivedNote({
    note: await notePackageToExport(p.notePackage),
    poolAddress: net.pool, chainId: net.chainId, prove,
    chain: sweepChainFromClient(client, { pool: net.pool, fromBlock: net.deployBlock }),
    submit: relaySubmitter(),
    beforeSubmit: (fresh) => saveNote(fresh),  // persist before it is sent
  });
  // Anyone can pay in any token the vault takes: credit only the one you priced in.
  if (r.final && p.asset.toLowerCase() === usdg.address.toLowerCase()) creditAgent(p.note, p.amountWei);
}

Every network has a payment-message board, Robinhood Chain and Tempo mainnet included, so scanIncomingPayments finds payments to your key on its own. A payer can also hand you the sealed code (gloam2t.…) their app shows after paying: check it with openIncomingPayment, which says whether the payment is ready, has not landed yet, or was already spent. A complete, runnable version is in packages/sdk/examples/receive-and-credit.ts.

Public inputs are pinned

The SDK pins the exact public-input ordering the deployed verifiers expect, so an integrator cannot drift out of proof compatibility:

  • shield, [commitment, amount, asset]
  • unshield, [root, nullifier, asset, amount, to]
  • transfer, [root, nullifier, newCommitment0, newCommitment1]
  • sealedSwap, nine signals ending in amountOutMin, rateIn, rateOut

Guardrails

Mainnet (Robinhood Chain 4663, Tempo 4217, Base 8453, Ethereum 1) is beta software: final ceremony keys, beta deposit limits, a 0.25% cash-out fee. It was reviewed internally before launch; an external audit is planned. The Robinhood Chain and Tempo testnets (46630, 42431) run on dev-ceremony keys with test funds; Base Sepolia (84532) and Ethereum Sepolia (11155111) run the final keys with test funds. Real privacy only, never a mock success. Note secrets are the sole spend authority: persist and protect them. The old sealed trade is retired. Private swaps run in the app on Robinhood Chain and Tempo mainnet; the SDK does not build them yet. See the production gate.