Gloam is live on mainnet. Private payments on Robinhood Chain and Tempo.

Open the app

Build on Gloam

Gloam for Virtuals agents

A private balance and private payments for agents built on GAME, and a provider that pays out privately on ACP. Your agent keeps doing business on chain. Its customers, suppliers, prices and balance stop being public.

Package
@gloamtrade/game-plugin-gloam
Built on
GAME
ACP
Private payout provider
Networks
Robinhood Chain and Base

Why agent commerce needs privacy

Agents now hire each other. On Virtuals' Agent Commerce Protocol (ACP) a client agent opens a job, a provider sets a price, the money sits in escrow and is released when the work is approved. ACP's docs say every transaction is “immutably recorded on-chain for auditability and trust”. That is what makes a job trustworthy. It also means anyone can read the business around it.

A public job tells a competitor, or a bot built to copy you:

  • who your customers are, and how often they come back;
  • what you charge, and what you pay your own suppliers;
  • who you pay, how much, and when;
  • how much you hold, and so how much risk you can take.

No human business would publish that. An agent that runs a real business should not have to either. Gloam keeps ACP's escrow working as it is and makes the money around it private: a private balance in a shielded vault, private payouts, and proofs the agent hands to one named reader when a counterparty needs to check something.

Install and set up

pnpm add @gloamtrade/game-plugin-gloam @virtuals-protocol/game

Give your GAME agent the Gloam worker. The limits are the owner's: each asset has its own caps, and without limits nothing moves.

import { GameAgent } from "@virtuals-protocol/game";
import GloamPlugin from "@gloamtrade/game-plugin-gloam";

const gloam = new GloamPlugin({
  limits: {
    recipients: "any",
    assets: { USDC: { maxPerPayment: "5", maxPerDay: "20" } },
  },
  // Claim links are the money itself: they come to your code, never to the planner.
  onClaimLink: (c) => deliverSomehow(c.link),
});

const agent = new GameAgent(process.env.GAME_API_KEY!, {
  name: "Private Treasurer",
  goal: "Pay suppliers privately and prove funds when asked.",
  description: "Pays and gets paid with Gloam.",
  workers: [gloam.getWorker()],
});
await agent.init();
await agent.run(10, { verbose: true });

The agent's own environment holds the key and the settings. None of it passes through the planner.

# Base Sepolia (testnet) is the default. robinhood-mainnet for mainnet.
GLOAM_NETWORK=base
# The wallet that holds public funds and signs deposits.
GLOAM_AGENT_PRIVATE_KEY=0x...
# Or pass limits in code.
GLOAM_LIMITS_FILE=./limits.json
# Optional: the note store key. Derived from the agent key when unset.
# GLOAM_NOTE_KEY=...
# Optional: send private payments from the wallet, not the Gloam relay.
# GLOAM_SUBMIT=wallet

An ACP agent's Privy smart wallet can sign instead of a local key: new GloamPlugin({ sender: await acpCallSender(evmProvider, chainId) }). Read wallet policies first: the default policy does not let that wallet reach the vault yet.

Functions

Seven GAME functions on one worker. Arguments arrive as strings, and amounts are plain numbers in the asset's units (25 means 25 USDC).

FunctionWhat it doesPublic on chain
gloam_private_balance
refresh?
The private balance per asset, what the wallet holds publicly, the agent's Gloam address, open claim links, and what its limits leave for today. largestPayment is the most one payment can be right now.Nothing
gloam_add_privately
amount, asset?
Moves money from the agent's wallet into its private balance (approve, then a deposit into the vault).The deposit: wallet, asset and amount
gloam_pay_privately
to, amount?, asset?, note?, seal_to?
Pays a Gloam address (gloamr1.…), a payment request link, or "claim link" for someone with no Gloam address. The note is a private line only the payee can read.A private transfer: no amount, no parties
gloam_receive
mode, amount?, asset?, note?, claim_link?
address: the Gloam address to share. request: a payment request link. check: pick up payments sent to the agent and secure them. claim: claim a claim link.A private transfer, when it secures a payment
gloam_cash_out
amount, to, asset?
From the private balance to a public 0x address. The pool keeps 0.25%.The cash out: asset, amount and recipient
gloam_prove_funds
at_least, for, asset?, expires_in_hours?
A gloamfunds1: proof that the agent holds at least an amount, made for one named reader, with an expiry. It shows neither the balance nor which notes back it.Nothing
gloam_verify_proof
proof
Checks a gloamfunds1: or gloampay1: proof against the chain, with the same checks as gloam.trade/verify. ok is true only when every check passes.Nothing
  • One payment spends one note. The most one payment can be is the largest note, shown as largestPayment.
  • A received payment counts once it is secured. check moves each payment into a fresh note only this agent knows, so the payer cannot spend it back.
  • The relay submits private sends by default. The agent's wallet does not appear next to its payments. Set GLOAM_SUBMIT=wallet to send from the wallet instead.

The engine underneath is GloamAgent (plugin.agent), usable without GAME: add, pay, payByClaimLink, requestPayment, checkIncoming, claim, cashOut, proveFunds, provePayment and verifyProof. The ACP provider below uses it directly.

Limits

{
  "recipients": "any",
  "actions": ["add", "pay", "cash_out"],
  "expiresAt": "2026-12-31",
  "assets": {
    "USDC": { "maxPerPayment": "5", "maxPerDay": "20" },
    "ETH": { "maxPerPayment": "0.002", "maxPerDay": "0.01" }
  }
}
  • Every asset has its own caps, in its own units. One number for all assets is refused, because “50” would mean 50 USDC and 50 ETH.
  • Caps cover money leaving the agent: payments and cash outs, over a rolling 24 hours. Adding to its own private balance is allowed or not, never capped, so a provider adding its earnings does not use up its payout budget.
  • Recipients is "any" or a list of Gloam addresses, 0x addresses for cash outs, and "claim-link".
  • Assets are matched by chain and contract address, never by a symbol someone else wrote.
  • Every spend is logged before it is signed, under a lock, so two calls at once cannot both squeeze under the daily cap.

A refusal comes back with a code and a plain reason the agent can act on:

CodeWhen
limits_unsetNo limits were given. Nothing moves until they are.
action_not_allowedThe action (add, pay or cash_out) is not in actions.
asset_not_allowedThe asset has no caps of its own.
over_per_paymentOne payment or cash out above maxPerPayment.
over_dailyIt would take the last 24 hours above maxPerDay.
recipient_not_allowedThe payee is not on the recipients list.
expiredThe limits are past expiresAt.

Where they hold. The plugin enforces the limits before it signs. The vault contract does not know them, so they bind the agent, not whoever holds its key outside the plugin. The MCP server's limits file works too, as long as each asset has its own caps.

What the planner sees

GAME's planner is a hosted model. It reads each function's description, what the function returns, and the worker's environment, every step. So the plugin keeps the money out of reach:

  • No function takes a key, a note or a secret as an argument.
  • Notes live in an encrypted file on the agent's machine (AES-256-GCM), named to the planner only by handle.
  • Claim links go to your onClaimLink, or sealed to a Gloam address. Never to the planner.
  • Every result and every log line is checked for secrets the process has handled, and anything shaped like a claim code, before it leaves.

The planner does see amounts, handles, hashes, Gloam addresses and proofs. Pass shareBalances: false to keep balances out of the environment it reads.

Private payouts on ACP

examples/virtuals-acp-provider is a provider agent with one service, private payout. A client agent hires it with a list of who to pay and how much. The provider pays each recipient privately from its Gloam balance and delivers a proof of each payment, sealed to the client.

  1. The client opens a fund-transfer job, using ACP's FundTransferHook, and evaluates it itself.
  2. The provider sets its fee. When the client funds, the fee goes into escrow and the payout total goes from the client to the provider in the same transaction.
  3. The provider pays each recipient privately and makes a gloampay1: proof of each payment.
  4. It submits the deliverable sealed to the client's Gloam address. The client checks the proofs and completes the job, and the escrow pays the fee.
  5. Later, the provider adds what it received to its private balance in round batches, so its deposits do not line up with any one job.

The requirement the client sends with the job:

{
  "payouts": [
    { "to": "gloamr1.…", "amount": "7", "note": "march" },
    { "to": "claim-link", "amount": "3" }
  ],
  "replyTo": "gloamr1.…"
}

Or the same list sealed to the provider's Gloam address, so Virtuals' ACP backend, which stores the requirement, cannot read it:

{ "sealedPayouts": "gloam2t.…", "replyTo": "gloamr1.…" }

Rules the provider keeps:

  • Payouts never depend on the escrow. A private payout cannot be undone, and a self-evaluating client may reject a funded job and take the escrow back, as ERC-8183 allows. That is why the payout total travels outside the escrow. A client that rejects gets its fee back from the escrow and any unpaid principal back from the provider.
  • It quotes only when one private note covers the whole job.
  • Its fee is 0.5%, at least 0.05, grossed up for ACP's cut, which it reads from the contract.
  • It never submits a job it could not pay in full. It writes down each payout and refund before sending it, with no secrets in the record, and a refund whose answer is lost waits for the owner instead of going out again.
  • Plain escrow jobs only from clients the owner trusts.

Status. The provider is open-source example code. It ran end to end on local forks of Base Sepolia and Robinhood Chain mainnet, against ACP's own contracts and Gloam's vaults, with fresh keys and nothing sent to a live network. It is not registered on ACP yet, and it runs on testnet only for now. scripts/live.mts wires the same provider into @virtuals-protocol/acp-node-v2.

What stays public

ACP's contracts are public, and Gloam does not change that. Exactly what anyone can read for one private payout job:

On chain, from ACP:

  • the job: client, provider, evaluator, expiry, and the offering name gloam_private_payout;
  • the fee in escrow, and the escrow paying it and ACP's cut;
  • the payout total moving from the client to the provider at funding;
  • the hash of the deliverable.

On Virtuals' ACP backend: the requirement and deliverable messages. With sealedPayouts and replyTo both are sealed, so it stores ciphertext only.

From Gloam: the provider's deposits of what it earned, batched and rounded, and any cash out (asset, amount and recipient).

Private: who was paid and how much each. A payout is a private transfer: the chain shows a spent marker and two new commitments, not the recipient, not the amount, and not that this provider sent it, since the Gloam relay submits it. The agent's private balance is private too.

One habit gives that back: a recipient who cashes out the exact amount right after being paid is easy to match by amount and time. Cash out a different amount, or later.

Wallet policies

Every ACP agent wallet signer runs under a wallet policy, checked on every transaction. The default, Virtuals Only (ACP_ONLY), allows only Virtuals-approved destinations. The Gloam vault is not one of them yet, so an ACP wallet on that policy cannot add to a private balance.

Until it is, either:

  • give the signer a custom policy that allows the Gloam vault 0x39e28cde55cab798874746ac52cb4b5cc99a0194 and the token's approve to it (the owner approves the change in Virtuals' dashboard); or
  • run the plugin with its own key (GLOAM_AGENT_PRIVATE_KEY) and send ACP earnings to that wallet.

Networks

One vault address on every chain: 0x39e28cde55cab798874746ac52cb4b5cc99a0194. Virtuals agents live on Base and Robinhood Chain, so those come first.

GLOAM_NETWORKNetworkStatusAssets
robinhood-mainnetRobinhood Chain mainnetLive, beta limitsUSDG, GLOAM
base-mainnetBase mainnetNext. Refused until its vault is liveUSDC, EURC, USDT, ETH
base (default)Base Sepolia testnetLiveUSDC, EURC, ETH
tempo-mainnetTempo mainnetLive, beta limitsUSDC.e, pathUSD, USDT0, OUSD
  • Beta limits on mainnet: 500 of a stablecoin per deposit and 25,000 in total per stablecoin.
  • ACP and the vault take the same money. ACP pays in USDG on Robinhood Chain and in USDC on Base, both assets the Gloam vault takes there.
  • On Base Sepolia ACP's escrow takes its fee in Virtuals' own test token, which the vault does not list, so only the payout total can go in privately there.

Chain IDs, RPCs and explorers are on Networks.

FAQ

Is this made by Virtuals?

No. Gloam built it for Virtuals agents, on the GAME framework and against ACP's public contracts. It is Gloam's own open-source plugin, not a Virtuals product.

Does Gloam hold my agent's money?

No. The money sits in the vault contract, which has no admin withdraw, and only the agent holds the secrets to its notes. The relay can submit a payment or refuse it. It cannot move or redirect money.

Does it hide the ACP job?

No. The job, the escrowed fee and the payout total stay public, as above. The payouts and the balance are what go private.

Do I need GAME?

No. GloamAgent works on its own, which is how the ACP provider uses it. Agents on other frameworks can also use the MCP server or the SDK.

Is there a Python version?

Not yet. ACP's current SDK is TypeScript (@virtuals-protocol/acp-node-v2), so the plugin is too.

What does it cost?

Adding money and paying privately are free. A cash out costs 0.25%. The payout provider charges 0.5%, at least 0.05, and ACP takes its own fees on what is escrowed.

Can I try it without mainnet funds?

Yes. GLOAM_NETWORK=base is Base Sepolia, a testnet, and the default. The plugin and the provider also run whole flows on a local fork with one command each; see their READMEs.

Is mainnet ready for my agent?

Mainnet is beta software with deposit limits. The code was reviewed internally before launch, and an external audit is in progress. Start an agent on small limits and raise them as you go.