Check and reserve land in one atomic step
NEAR runs a call as one step until its first promise. Purser checks the budget and reserves inside that step, so two requests can't both pass.
Purser is a spending limit AI agents can't talk their way around. It lives in a NEAR contract, and every payment has to clear it.
Hackathon build, NEAR testnet only. Read what it won't do.
completed + outstanding <= budget
Agents are starting to buy data, compute and API access. Today's controls break in three places.
The limit
A bug, a jailbreak or a bad tool walks straight past a cap in a config file.
The balance
Both see enough, so both spend. A deliberately naive baseline, on NEAR testnet:
120 / cap 100
The timeout
A timed-out payment can still land. Release the hold, and you've overspent without knowing it.
10 / 100 left
A real run: testnet block 276744286, task demo-20261108071557. 1 unit = 0.001 NEAR. The animation illustrates it.
Every drop is a payment the contract admitted. Nothing went below 10.
The AI proposes. The contract does the rest.
One shared budget with a hard cap, set by create_task.
Register each agent. On the NEAR path, list who can get paid.
A recipient, an amount and a request_id. The contract builds the rest.
Active agent? New request? Allowed recipient? Room under the cap?
Check and reservation run as one step, before any promise exists. That's why a race can't overspend.
It builds the transfer from the fields it validated. On the EVM path, the MPC signer signs what the contract built.
The settlement callback confirms the payment. Silence keeps the hold.
| State | Budget | What moves it on |
|---|---|---|
| Reserved | Held | A signature, a settled transfer or a proven failure |
| Signed | Held | Proof of the outcome, or a timeout |
| Unknown | Held | Later proof, or the owner reconciling the observed outcome |
| Confirmed | Spent | Final |
| Failed | Released | Set only on a proven failure |
| Cancelled | Released | EVM only: a confirmed nonce-burn and the owner's release |
Purser builds on what the protocol already enforces.
NEAR runs a call as one step until its first promise. Purser checks the budget and reserves inside that step, so two requests can't both pass.
An agent's key can call one method on one contract: propose_payment. It can't move the account's funds.
One contract can govern spending on another chain. Run live on Sepolia: NEAR tx 523rR7 -> Sepolia tx 0xbf264f ->
Budget and policy live in the contract, out of reach of a compromised tool.
NEAR payments report their own outcome back to the contract, with no relayer in the path.
Attestation shows where the agents' model ran, with Intel TDX and NVIDIA GPU evidence. The contract never reads it.
What the contract did, each time.
Spend past the cap
REFUSED: exceeds task budget
tx BHc6av ->Pay twice with a retry
Same commitment, no new spend
tx HSvCY6 ->Pay someone off the allowlist
REJECTED: recipient not allowlisted
tx 4PNRgP ->Reuse a request_id for something else
REJECTED: request_id reused with a different request
tx 13dNBM ->Keep spending after a revoke
BLOCKED: delegate revoked
tx EMSwHf ->Free a hold by timing out
The hold stays through silence, retry and revoke
Sandbox replay: the shipped contract can't lose a callback0 overruns in 50 concurrent races, checked against the merchant's balance.
Measured on a local nearcore sandbox with the production wasm, 10 repeats. These are not testnet or mainnet figures, and this is not an audit. Regenerate with make metrics or read the full report ->.
The full list is in the README ->.
You need Rust 1.95, cargo-near, Node 26 and pnpm 10. The first two commands cost nothing. Testnet steps also need near-cli-rs 0.30.1.
make proveAttack cases and property tests, with the race's block heights.
make testEvery unit, property and sandbox suite.
OPERATOR_ACCOUNT_ID=<you>.testnet make deploy-testnetContract and crew on testnet, from a funded near-cli account.
./scripts/demo.shSeven outcomes: six live on testnet, one labeled sandbox replay.
make boardThe operator board, with the live invariant and the Revoke control.