Skip to main content
Spending limits let an agent pay from your Metal account within limits you set: which addresses it may pay, how much per payment, how much in total, and until when. The agent never holds your key. Metal refuses any payment outside the limits when it settles, and sellers see an ordinary x402 payment in *USDZ. Mandates explains what the steps below put on chain. Use cases:
  • A shopping agent that pays per article, per search or per API call as it goes, up to 20 *USDZ in total and never more than 0.50 *USDZ a purchase.
  • A personal assistant that may book with two travel sites you chose and nowhere else.
  • A finance agent that settles supplier invoices by payout to each supplier’s address, no more than 500 *USDZ an invoice and 5,000 *USDZ in all, which the owner renews each month.
Set METAL_RPC_URL to an endpoint of your own; account create and the mandate commands other than credential create read and write the chain through it.

1. The owner creates a Metal account

Skip this step if you already have one. Otherwise account create creates a one-owner account on testnet:
The owner key pays the gas, so account create funds it from the faucet first. If the faucet refuses, the error says why and names the key file; run the same command again once it allows. Then fund the account with what the agent may spend. The faucet grants one client once a minute, so wait a minute:
That faucet grants each IP address ten times a UTC day, so everyone behind one address shares them. Or fund the address from the Faucet page of the Metal console, where you created your endpoint: each grant tops it up to 100 *USDZ, and grants are limited per user. Both draw on the same faucet’s 1,000 grants a day, and when it is down, both are: retry later. To send *USDZ from the owner key, for example to the account, use writeMip20Contract from @metalntwx/metal-ts, with OWNER_PRIVATE_KEY the privateKey field of owner-key.json. A plain viem transfer with no gas limit fails with gas required exceeds allowance (0).
owner-key.json holds the owner’s key in the clear, which is fine for trying this on testnet; keep it where the agent cannot read it. For real funds, sign from your own wallet or a passkey instead.

2. The owner writes the limits

Amounts are in *USDZ, to the cent. Both limits are inclusive: a payment of exactly the cap pays, as does one that brings the spend to exactly the budget. Give the agent the credential, its mandate key file and the issuer’s public key file the command prints as issuer-public-file. The credential holds no secret; the mandate key does. The issuer key itself stays beside the owner’s key. A payment provider can issue the credential instead, and bind then takes the provider’s public key file as --issuer.

3. The owner binds the mandate

Binding puts the mandate on chain, and the owner’s key pays its gas. The account’s owners sign it, as many as its threshold needs: the owner’s key here, a multisig’s members with repeated --signer-key, or your own wallet or a passkey from code.

4. The agent pays

The mandate from step 2 pays only the payee it names. To try it on testnet, issue one to a new file with no --payee and bind it, then pay Metal’s demo seller, which charges 0.01 *USDZ:
A seller the mandate names is paid the same way:
payer is the owner’s account; the facilitator paid the gas. pay signs with the mandate key beside the credential. Through MCP, start the server with the credential’s full path, metal-agent mcp --mandate /path/to/mandate.json, and its pay tool pays under that mandate only. On testnet, set METAL_RPC_URL to the user’s endpoint in its environment, which its mandate tools read. After replace or renew, restart it with the new credential: it keeps the credential file it started with, which is then refused as revoked. To pay from code:
onSigned receives each payment once it is signed, before it is sent. When the answer to a payment is lost, look for its pull.nonce at the end of an id in the facilitator’s /settlements?payTo=<payee> before paying again. A payment outside the mandate comes back as a 402 whose payment-required header names the reason, and one refused at settlement with a payment-response header that says success: false; decodePaymentRequiredHeader and decodePaymentResponseHeader from @x402/core/http read them. Use mandate status --credential ./mandate.json --network testnet, with the credential you paid under, to see the spend so far.

5. Anyone verifies

--issuer is the public key file from step 2. The command checks every payment under the mandate against the limits; a failure names what differs and exits 1, and --tx <hash> checks one transaction. It lists the payments under this credential only: after a replace, verify each credential, the replaced one included. A seller verifies a payment it received from code; require a signed order shows the call. Verifying says what it proves.

Sellers that require a signed order

Some sellers also require the agent’s signature over the order itself. pay --mandate provides it when the 402 asks and prints it as signed-offer; mandate verify --issuer <file> --tx <hash> with --signed-offer <text> checks it against the payment. Require a signed order is the seller’s side.

Refusals

A payment outside the mandate is refused and moves nothing, with the reason:
Every reason is listed under what a mandate enforces.

The owner changes or revokes the mandate

Only the owner changes a limit, by replacing the mandate:
The new credential names the same mandate key, from the copy of ./mandate.key.json the owner kept. With ./wider.json in the same directory as its mandate.key.json, the agent pays under it with the key it already has; from code, keep loading mandate.key.json. A replacement carries the spend into the new budget, so its budget must be above what is already spent. A budget never resets on its own; mandate renew in place of replace binds the new credential with nothing spent. To end a mandate instead of replacing it:
Payments under a replaced or revoked mandate are refused from the transaction that changes it.

Send a direct payout

Pay an address under the mandate, with no seller.

Require a signed order

The seller’s side of the second signature.