*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 *USDZin total and never more than0.50 *USDZa 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 *USDZan invoice and5,000 *USDZin 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. Otherwiseaccount create creates a
one-owner account on testnet:
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:
*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
--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:
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:The owner changes or revokes the mandate
Only the owner changes a limit, by replacing the mandate:./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:
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.