Build
Pay-per-result agents with Assay
Fund an agent's Assay balance from your private balance through a one-time address. Assay pays tools only when results pass its checks; UMBR keeps the agent's spending from pointing back at you.
- Partner service
- Assay (goassay.ai)
- Network
- Robinhood Chain testnet
- Asset
- USDG
- Status
- Demo in the app
Demo today
What Assay is#
Assay is an escrow for agents that pay for tools over x402. Before a payment settles, Assay checks the result: it was delivered, it matches the promised schema, the price is what was quoted, and it is not a duplicate. If a check fails, the agent keeps its money, and a provider with stake behind its endpoint pays a penalty.
Assay is a separate service run by its own team. UMBR does not operate it, hold its keys or vouch for it.
Why UMBR and Assay fit#
Assay answers did I get what I paid for. It does not answer who is paying. An agent that funds Assay from its owner's wallet publishes that link: anyone can follow the deposit back and read every tool the agent pays for.
UMBR closes that gap. You fund the agent from your private balance through a one-time address, so the escrow balance, the vouchers and the tool calls all point at an address with no history. Both run on Robinhood Chain and settle in USDG, so no bridge sits in between.
The flow#
One-time address
A fresh key is made for the agent, in your browser or your own code.Private payout
UMBR pays USDG out of your private balance to that address, with a proof made on your device.Escrow deposit
The one-time address approves and deposits the USDG into Assay's escrow.Agent spends
The agent signs Assay vouchers with the one-time key. Tools are paid only when results pass.Top up or retire
Repeat with a new address for a fresh start, or withdraw what is left and shield it back.
In the app#
- Open Agents in demo mode and pick Robinhood Chain testnet.
- Enter an amount of USDG from your private balance.
- Press Fund agent. The progress sheet shows each beat: the one-time address, the proof, the relay, the approval and the deposit.
- Copy the agent address from the receipt. In a real run you would hand its key to the agent.
Outside demo the page explains what is missing: a wallet, UMBR's pool on the network, or Assay's escrow address.
From code#
The same flow from a script. This is an integration pattern, not a shipped helper: @umbr/sdk is a preview and depositToAssayEscrow stands for Assay's own deposit call.
import { createUmbrClient, networks } from "@umbr/sdk";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const umbr = createUmbrClient({ network: networks.robinhoodTestnet, signer: process.env.UMBR_OWNER_KEY });
// 1. A fresh key for this agent. It is the agent's Assay identity, not yours.
const agentKey = generatePrivateKey();
const agent = privateKeyToAccount(agentKey);
// 2. Pay it out of your private balance. Nothing links it to your wallet.
await umbr.unshield({ asset: "USDG", amount: "25.00", to: agent.address });
// 3. The agent address deposits into Assay's escrow (approve + deposit).
// Use Assay's own docs for the escrow call; the address comes from env.
await depositToAssayEscrow(agent, process.env.NEXT_PUBLIC_ASSAY_ESCROW_46630, "25.00");
// 4. Hand agentKey to the agent through your secret store. Never log it.On the agent side, Assay's SDK takes the one-time key as its signer:
// Inside the agent. Illustrative: check https://docs.goassay.ai/developers
// for the exact client constructor and argument shapes.
import { evmSigner } from "@goassay-ai/sdk";
import { privateKeyToAccount } from "viem/accounts";
const signer = evmSigner(privateKeyToAccount(process.env.AGENT_KEY));
// Bind the one-time address as the paid-call key, then call tools as usual:
// paidCalls.bind -> paidCalls.waitUntilActive -> tools.call -> receipts.getWho sees what#
| Who | Sees | Does not see |
|---|---|---|
| The public explorer | A pool withdrawal to a new address, then that address depositing into Assay's escrow. | Which UMBR balance paid for it. |
| Assay | The one-time address, its deposits, every paid call and whether each result passed. | Your wallet or your other UMBR activity. |
| A tool provider | The one-time address on each signed voucher and the call it paid for. | Who owns the agent. |
| UMBR | Nothing. Proofs are made in your browser and UMBR runs no Assay account. | The agent key, which never touches UMBR. |
- Use each one-time address once. Reusing it across agents, or sending its leftovers to your wallet, links them back to you.
- Mind the timing. A withdrawal followed seconds later by a deposit of the same amount is easy to pair. Leave a gap and avoid round, unique amounts.
- The agent's history stays visible under its one-time address. UMBR hides the owner, not what the agent does.
Configuration#
The app reads Assay's escrow address from public env. Until it is set, the Agents page stays in demo for that network.
# .env.local (public values only)
NEXT_PUBLIC_ASSAY_ESCROW_46630=0x... # AssayEscrow proxy on Robinhood Chain testnet
NEXT_PUBLIC_ASSAY_ESCROW_4663= # leave empty: no mainnet escrow yetStatus and limits#
| Piece | Status |
|---|---|
| Agents page in the app | Demo |
| Real funding on Robinhood Chain testnet | Waiting on UMBR's pool and Assay's escrow address |
| Tempo | Not supported by Assay |
| Mainnet | Not configured |
Assay's contracts are upgradeable by their owner with no timelock, per their own README. Weigh that the same way you would any service that holds an agent's balance, and keep that balance small.