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

Swap privately

Build on Gloam

Private commerce with commitments

Many agent economies already post a merkle root of their records on chain every night, and that root is how anyone checks them. Gloam payments fit into it: the payment is private, its receipt becomes one leaf in your root, and anyone holding the record can check it later without the amount or the payee ever going public.

Payment
a real vault transfer
In your root
one bytes32 leaf
Checked
off chain, any time
Burns
stay public

Why this matters

A game city, a marketplace or an agent network can keep its books off chain and still be checkable: it commits a root of its records each night, and anyone can prove a record is in it. Payments are the part that usually stays fully public, because they settle in public stablecoin transfers. That shows every agent's suppliers, prices and balance.

The question is how private payments stay compatible with the commitments you already post. The answer is that each private payment leaves a proof you can commit to, exactly like any other record.

How it fits

  1. Pay privately. The paying agent sends a private payment from its private balance to the payee's Gloam address. It is a real vault transaction the agent signs itself. On chain it shows a nullifier and two commitments, no amount and no payee.
  2. The payee secures it. The payee opens the payment and moves it into a note only it knows. Until then the payer could still spend it back, so the payment is final only after this sweep.
  3. Prove it. The payer proves the payment with the receipt circuit: a private payment of this asset, at least this much (or exactly this much, if you choose), is in the vault. The proof is bound to a label you pick, such as a record id, and an expiry.
  4. Commit it. You hash that proof into one bytes32 leaf and put it in your nightly root with your other records.
  5. Check it, any time. Anyone holding the record checks the leaf is in the root, the proof is valid, the vault had that state, and the named transaction made the payment.

Public transfers you rely on stay public. If your economy burns tokens on each sale and reads that trail, keep sending the burn as a normal token transfer next to the private payment.

Pay and plan the receipt

buildPrivatePaymentWithReceipt builds the private send to a receive tag, seals the payment so only the payee opens it, and returns the plan for the receipt. It signs and sends nothing.

import {
  mainnetNetwork, buildPrivatePaymentWithReceipt, buildPaymentReceipt,
  POOL_TRANSFER_ABI, transferCallArgs, syncTree, artifactProver, proveGroth16,
} from "@gloamtrade/sdk";

const net = mainnetNetwork("robinhood")!;

const pay = await buildPrivatePaymentWithReceipt({
  payTo: supplierTag,                      // the payee's gloamr1.… address
  chainId: net.chainId,
  asset: usdg.address,
  amountWei: parseUnits("25", 6),
  from: { secretHex: note.secret, amountWei: note.amount, path },  // the payer's private note
  prove: transferProver,                   // artifactProver over transfer.wasm + final/transfer_final.zkey
  note: "order 8812",                      // only the payee reads it
  receipt: { verifier: "nightly-root:sale:8812" },  // sealed into the proof
});

// The agent signs the private send, then posts the sealed payment for the payee.
const txHash = await wallet.writeContract({
  address: net.pool, abi: POOL_TRANSFER_ABI, functionName: "transfer", args: transferCallArgs(pay.intent),
});
if (pay.memo) await wallet.writeContract(pay.memo);
// Keep pay.changeNote: it is the rest of the agent's balance.

Prove it and make the leaf

Once the transfer has landed, sync the tree and prove. This works after the payee has swept the payment too.

const synced = await syncTree(client, { pool: net.pool, fromBlock: net.deployBlock });
const record = await buildPaymentReceipt({
  plan: pay.receiptPlan,
  path: (await synced.pathForCommitment(pay.paymentNote.commitment))!,
  txHash,
  prove: (input) => proveGroth16(input, "receipt.wasm", "final/receipt_final.zkey"),
});

record.leaf   // the bytes32 to put in tonight's root
// Store the record next to it, off chain. It holds no secret.

The leaf, exactly

keccak256(abi.encode(
  string  "gloam.receipt-leaf.v1",
  uint256 chainId,
  address pool,           // the vault
  bytes32 txHash,         // the vault transfer that made the payment
  uint256[7] signals      // the receipt's public signals, in circuit order:
                          // [root, commitment, asset, minAmount, reveal, shownAmount, context]
))

abi.encode, not encodePacked. In Solidity it is one line, and a contract can recompute it from the record:

bytes32 leaf = keccak256(abi.encode("gloam.receipt-leaf.v1", chainId, pool, txHash, signals)); // uint256[7]

In TypeScript it is paymentReceiptLeaf(record), and verifyPaymentReceiptLeaf(leaf, record) recomputes and compares. The SDK's tests pin a vector that Solidity and cast both reproduce. A new encoding would get a new domain tag, never a silent change.

The context signal is proofContext({ kind: "payment", chainId, pool, verifier, expiresAt }), so the leaf also commits to the label and expiry. Treat the leaf as one more value in your tree: if your tree hashes leaves again, hash this one the same way.

Checking a record

checkPaymentReceipt(record, { verifySnark, client }) runs every check and reports each one. ok is true only when all eight ran and passed. Check the leaf against your root with your own merkle proof, as for any record.

CheckWhat it confirmsNeeds
leafThe leaf recomputes from the record.Nothing
fieldsThe asset and amounts written in the record match the proof.Nothing
contextThe label, expiry, chain and vault are the ones sealed into the proof.Nothing
vaultThe record names Gloam's vault on that chain.Nothing
snarkThe zero-knowledge proof verifies against the receipt checking key.verifySnark
chainThe client reads the record's chain.client
rootThe vault has had the proof's root. The vault keeps every root it ever had.client
paymentThe transaction succeeded and its Transferred event created this note as the payment, not as change.client
import { checkPaymentReceipt, verifyGroth16 } from "@gloamtrade/sdk";
import receiptVkey from "./receipt_vkey.json";   // gloam.trade/circuits/final/receipt_vkey.json on mainnet

const result = await checkPaymentReceipt(record, {
  leaf: leafFromYourRoot,
  verifySnark: (signals, proof) => verifyGroth16(receiptVkey, signals, proof),
  client,                                          // a viem PublicClient on the record's chain
});

A record is also a Gloam proof of payment: paymentProofText(record) gives gloampay1:…, which anyone can paste into /verify or check through the partner API. The record has an expiry (30 days by default). Your root does not expire, so checkPaymentReceipt reports expired apart from ok; /verify fails a proof past its expiry.

What an agent signs

If your agents sign only from an allowlist, agentSigningAllowlist({ chainId, assets }) returns every call they sign to use Gloam on a chain, with the contract, the signature, the selector and the rules to enforce. Native value is 0 on every one.

CallSelectorOnWhat it does
approve(address,uint256)0x095ea7b3each tokenLets the vault pull the exact deposit. Spender is the vault.
shieldBound(address,uint256,bytes32,bytes)0x67e763e0vaultDeposit into the private balance. The amount is public.
transfer(bytes,bytes32,bytes32,bytes32[2])0xd02dddd1vaultPrivate send, and a payee's sweep. No amount or payee on chain.
unshield(bytes,bytes32,bytes32,address,address,uint256)0x67cca6c5vaultCash out to a public address. Amount and recipient are public.
postMemo(bytes32,bytes)0x82301844payment boardPosts the sealed payment so the payee finds it. Optional.

A payment needs only transfer and postMemo, and postMemo is optional: an economy that already passes messages between agents can hand the payee the sealed payment directly. The vault checks a proof on shieldBound, transfer and unshield that binds every argument, so an edited call reverts instead of sending money elsewhere.

What is public and what is not

  • Public: deposits (amount and asset), cash outs (amount, asset, recipient), public transfers such as burns, your nightly root, and that an agent's wallet sent a vault transaction at a given time.
  • Private: how much each payment was, who received it, and each agent's private balance.
  • The signer shows. An agent that signs its own vault transfer is that transaction's sender: the amount and the payee stay hidden, the fact that it paid someone privately does not. The Gloam relay (relayIntent, relayMemo) sends it instead, and can only submit or refuse, never redirect money.
  • Records show what is in them. Whoever sees a record sees its asset, its minimum (or amount, if revealed) and its transaction hash. Publish only the root to keep records private.
  • A receipt does not show who got paid. The payer picks the payment note's key, so both sides can make the same proof. Who the payment was for comes from your record and its label.

Limits worth knowing

  • No on-chain receipt verifier. The vault verifies the deposit, transfer and cash-out proofs it takes. Receipt proofs are checked off chain, with snarkjs and the published checking key. A contract can recompute the leaf and check your merkle path today; checking the receipt proof itself on chain would need a receipt verifier contract, which does not exist yet.
  • Beta. Mainnet runs on the final keys of the 13-contributor trusted setup, with beta deposit limits. It had an internal review on 20 October 2026; an external audit is planned and not done. Start small.

Reference integration

A runnable reference integration does the whole flow: a business agent pays a supplier privately, the supplier sweeps, the agent proves the payment, the leaf goes into a nightly root, and a public burn sits next to it. By default it is a dry run with no keys and no network; it also runs on Robinhood Chain testnet. See also Proofs and Agents.