> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pay Under Spending Limits

> An agent pays from its owner's account within limits the owner set: who it may pay, how much per payment, and how much in total

Spending limits let an agent pay from your [Metal account](https://docs.metalntwx.com/agents/mandates.md) 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](https://docs.metalntwx.com/agents/mandates.md) 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](https://docs.metalntwx.com/agents/payouts.md) to each supplier's address, no more than
  `500 *USDZ` an invoice and `5,000 *USDZ` in all, which the owner renews
  each month.

| | Holds | Runs |
| - | - | - |
| The owner | The owner's key | `account create`, `mandate credential create`, `bind`, `replace`, `renew`, `revoke` |
| The agent | The mandate key and the credential | `pay --mandate`, `mandate pull`, `mandate status` |
| Anyone | The credential and the issuer's public key file | `mandate verify` |

Set `METAL_RPC_URL` to an [endpoint](https://docs.metalntwx.com/network/endpoints.md) 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:

```bash theme={null}
metal-agent account create --out ./owner-key.json --network testnet
```

```text theme={null}
account <account address>
owner <owner address>
keyfile ./owner-key.json (created)
funded <owner address> with 100 *USDZ from the testnet faucet
deployed yes
transaction <transaction hash>
explorer https://testnet.metalntwx.com/tx/<transaction hash>
gas <gas used>
```

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:

```bash theme={null}
metal-agent fund <account address> --network testnet
```

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](https://console-testnet.metalntwx.com), 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)`.

```bash theme={null}
npm install @metalntwx/metal-ts@0.3.2 viem
```

```typescript theme={null}
import { createPublicClient, createWalletClient, erc20Abi, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { writeMip20Contract } from "@metalntwx/metal-ts";

const USDZ = "0x000000000000000000000000000000000000B5d5";
const transport = http(process.env.METAL_RPC_URL);
const publicClient = createPublicClient({ transport });
const hash = await writeMip20Contract({
  walletClient: createWalletClient({ account: privateKeyToAccount(process.env.OWNER_PRIVATE_KEY as `0x${string}`), transport }),
  publicClient,
  abi: erc20Abi,
  address: USDZ,
  functionName: "transfer",
  args: ["<account address>", parseUnits("10", 18)],
  gasToken: USDZ,
  gasFallback: 200_000n,
});
await publicClient.waitForTransactionReceipt({ hash });
```

`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

```bash theme={null}
metal-agent mandate credential create --principal <account address> \
  --payee <payee address> --cap 5 --budget 10 --key ./owner-key.json --out ./mandate.json --network testnet
```

```text theme={null}
credential ./mandate.json
mandate <mandate id>
description A mandate issued by metal-agent
principal <account address>
mandate-key 0x01<x and y in hex> (P-256)
token 0x000000000000000000000000000000000000B5d5
per-payment-cap 5 *USDZ
budget 10 *USDZ until expiry
starts <unix time> (<date>)
expiry <unix time> (<date>)
payee <payee address>
mandate-key-file ./mandate.key.json (created)
issuer-key-file ./owner-key.issuer.json (created)
issuer-public-file ./owner-key.issuer.public.json
```

| Option | Sets |
| - | - |
| `--principal` | The Metal account the agent pays from. |
| `--payee` | An address the agent may pay, with an optional name and website after it, comma-separated; repeat it. Name none and the agent may pay any address. |
| `--cap` | The most one payment may be, in `*USDZ`. |
| `--budget` | The most all payments may add up to over the mandate's life. It never resets. |
| `--expires-in` | The mandate's life in seconds; 30 days when omitted. |
| `--starts-in` | Seconds from now until the mandate starts; 00:00 UTC on the day of issue when omitted. |
| `--max-occurrences` | The most payments the agent may make. |
| `--description` | What the mandate is for; `mandate status` shows it. |
| `--key` | The owner's key file. The keys that sign the credential live beside it, created on first use. |
| `--issuer-key` | Instead of `--key`, for an owner who signs from a wallet: the file of the keys that sign the credential, created on first use. |

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

```bash theme={null}
metal-agent mandate bind --credential ./mandate.json --key ./owner-key.json --network testnet
```

```text theme={null}
bound <mandate id>
transaction <transaction hash>
explorer https://testnet.metalntwx.com/tx/<transaction hash>
gas <gas used>
```

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`](https://docs.metalntwx.com/agents/metal-agent#mandate-bind), 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`:

```bash theme={null}
metal-agent mandate credential create --principal <account address> \
  --cap 1 --budget 5 --key ./owner-key.json --out ./open.json --network testnet
metal-agent mandate bind --credential ./open.json --key ./owner-key.json --network testnet
metal-agent pay https://x402-demo-testnet.metalntwx.com/quote --mandate ./open.json --network testnet
```

A seller the mandate names is paid the same way:

```bash theme={null}
metal-agent pay https://seller.example/quote --mandate ./mandate.json --network testnet
```

```text theme={null}
mandate <mandate id>
nonce <payment nonce>
status 200
payer <account address>
payee <payee address>
amount 4 *USDZ
network eip155:63825
transaction <settlement transaction hash>
explorer https://testnet.metalntwx.com/tx/<settlement transaction hash>
header <name>: <value>
body {"quote":"..."}
```

`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:

```typescript theme={null}
import { readFile } from "node:fs/promises";
import { createMandatePaymentClient, p256DigestSigner, parseCredential, privateKeyHex } from "@metalntwx/metal-pay/mandates";

const credential = parseCredential(JSON.parse(await readFile("./mandate.json", "utf8")));
const mandateKey = JSON.parse(await readFile("./mandate.key.json", "utf8"));
const { fetchWithPayment } = createMandatePaymentClient({
  credential,
  signDigest: p256DigestSigner(privateKeyHex(mandateKey)),
  network: "testnet",
  onSigned: (payment) => console.log(`nonce ${payment.pull.nonce}`),
});
```

`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>`](https://docs.metalntwx.com/agents/facilitator#get-settlements) 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

```bash theme={null}
metal-agent mandate verify --credential ./mandate.json --issuer ./owner-key.issuer.public.json --network testnet
```

`--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](https://docs.metalntwx.com/agents/require-signed-order.md) shows the call.
[Verifying](https://docs.metalntwx.com/agents/mandates.md#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>`](https://docs.metalntwx.com/agents/metal-agent#mandate-verify) checks it
against the payment. [Require a signed order](https://docs.metalntwx.com/agents/require-signed-order.md)
is the seller's side.

## Refusals

A payment outside the mandate is refused and moves nothing, with the
reason:

```text theme={null}
error: payment refused: mandate_cap_exceeded (the amount is above the mandate's per-payment cap) (offer: 7 *USDZ on eip155:63825); nothing was paid
```

Every reason is listed under
[what a mandate enforces](https://docs.metalntwx.com/agents/metal-agent.md#what-a-mandate-enforces).

## The owner changes or revokes the mandate

Only the owner changes a limit, by replacing the mandate:

```bash theme={null}
metal-agent mandate credential create --principal <account address> --mandate-key-file ./mandate.key.json \
  --payee <payee address> --cap 10 --budget 30 --key ./owner-key.json --out ./wider.json --network testnet
metal-agent mandate replace --credential ./wider.json --replaces ./mandate.json --key ./owner-key.json --network testnet
```

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:

```bash theme={null}
metal-agent mandate revoke --credential ./mandate.json --key ./owner-key.json --network testnet
```

Payments under a replaced or revoked mandate are refused from the
transaction that changes it.

> [card] [Send a direct payout](https://docs.metalntwx.com/agents/payouts.md)
>
> Pay an address under the mandate, with no seller.

> [card] [Require a signed order](https://docs.metalntwx.com/agents/require-signed-order.md)
>
> The seller's side of the second signature.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.