AGENTSFor developersagentPay · routeFor

Give an agent a vault,
not your wallet.

Your agent signs with its own key. The contract lets that key spend from one vault, in USDG, inside the mandate you set — and nothing else. This page is everything you need to wire it up.

1 · Set up

  1. Open Bank → Agents, press Generate (or paste the address of a key your agent already has) and choose the limits.
  2. Fund the vault from your bank balance. The vault is the most the agent can ever spend; return it to your balance whenever you like.
  3. Send the agent key a little ETH for gas (the Send gas button does it).
  4. Give the agent its private key and its agent number. That is all it needs.

2 · The module

One file, ethers v6, no other dependency. Download blackveil-agent.mjs

import { BlackveilAgent } from './blackveil-agent.mjs'

const agent = new BlackveilAgent({ privateKey: process.env.AGENT_KEY, agentId: 1, bank: '0x…' })

// ask first — free, no transaction
await agent.route('ledger-co', '15.00')      // { route: 'Settles', reason: 'None' }

// then pay; a handle or a 0x address both work
const r = await agent.pay('ledger-co', '15.00', { ref: 'inv 0x8a19' })
// { route: 'Settles', sent: true, hash: '0x…' }
// { route: 'Queues',  sent: true, pendingId: 3 }       ← waits for the owner
// { route: 'Refuses', reason: 'OverPerPayment', sent: false }

await agent.status()   // vault, spentToday, limits, paused/revoked

From a shell: AGENT_KEY=0x… AGENT_ID=1 node blackveil-agent.mjs pay ledger-co 15 "inv 0x8a19"

By default a refused payment is not sent, to save gas. Pass recordRefusals: true if you want every refused attempt written on chain as an AgentRefused event.

3 · Settles, queues, refuses

routeFor(agentId, to, amount) runs the same checks as agentPay, in this order:

CheckOutcome
Agent revoked or pausedRefuses
Allowlist on and the recipient is not on itRefuses
Amount above the per-payment limitRefuses
Vault smaller than amount + feeRefuses
Amount above the review lineQueues — the amount and fee are reserved from the vault until the owner approves or rejects
Today's spend + amount above the 24-hour limitRefuses
OtherwiseSettles — paid at once, counted toward the 24-hour window

The 24-hour window starts with the first settled payment and resets 24 hours later. Payments the owner approves from the queue do not count toward it — the owner already decided.

4 · Reason codes

CodeNameWhat to do
1PausedThe owner paused the agent. Wait.
2RevokedThis key is finished. Stop.
3ZeroAmountSend a positive amount.
4NotAllowlistedAsk the owner to allow this recipient.
5OverPerPaymentSplit the bill or ask for a higher limit.
6OverDailyTry after the window resets.
7InsufficientVaultAsk the owner to fund the vault.

5 · What the key cannot do

  • Withdraw from the vault, or touch the owner's balance.
  • Change its own limits, allowlist, status or key — those calls check the owner, not the signer.
  • Approve its own queued payments.
  • Keep working after the owner revokes or rotates it.

If the key leaks, the most it can move is what the mandate allows until you pause it. Revoking returns the whole vault to your balance in the same transaction.

6 · Calling the contract directly

// Solidity / any client — Robinhood Chain (4663), USDG has 6 decimals
function routeFor(uint256 agentId, address to, uint256 amount) view returns (uint8 route, uint8 reason)
function agentPay(uint256 agentId, address to, uint256 amount, bytes32 ref, bool toWallet) returns (uint8 route, uint256 pendingId)
function resolve(string handle) view returns (address)
event Payment(address indexed from, address indexed to, uint256 amount, uint256 fee, bytes32 ref, uint8 kind, uint256 refId, bool toWallet)
event Queued(uint256 indexed pendingId, uint256 indexed agentId, address indexed to, uint256 amount, bytes32 ref)
event AgentRefused(uint256 indexed id, address indexed to, uint256 amount, uint8 reason, bytes32 ref)

ref is any 32-byte reference — an invoice id, an order number. It travels into the Payment event so you can reconcile. toWallet = true sends USDG straight to the recipient's wallet instead of their BLACKVEIL balance. Full ABI: /abi/BlackveilBank.json · source: Verify.