Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the gitignore and test case for script. As of now we are not adding the test for scripts.

7 changes: 5 additions & 2 deletions skills/metamask-agent-wallet/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: metamask-agent-wallet
description: Use when the user asks anything about blockchain wallets, transactions, signing, token transfers, supported chains, wallet balances, perpetual futures trading, prediction markets, token swaps, cross-chain bridges, market data, token discovery, decoding EVM calldata, Aave V3 lending and borrowing, or authentication via the MetaMask Agentic CLI; also when an HTTP request returns 402 Payment Required (x402) or the agent needs to pay for a paywalled API, endpoint, file, or resource over HTTP. Single entry point for all mm CLI operations.
description: Use when the user asks anything about blockchain wallets, transactions, signing, token transfers, supported chains, wallet balances, perpetual futures trading, prediction markets, token swaps, cross-chain bridges, market data, token discovery, decoding EVM calldata, Aave V3 lending and borrowing, or authentication via the MetaMask Agentic CLI; also when an HTTP request returns 402 Payment Required (x402), when an MCP tool call returns an x402 payment-required result, or the agent needs to pay for a paywalled API, endpoint, file, tool, or resource. Single entry point for all mm CLI operations.
license: MIT
metadata:
author: metamask
Expand Down Expand Up @@ -105,6 +105,7 @@ Match the user's intent to a command and reference file, then read the reference
| Check swap or bridge status | `mm swap status` | [swap.md](references/swap.md) |
| Bridge tokens to another chain | `mm swap execute` | [swap.md](references/swap.md) |
| Pay an HTTP `402` / x402 paywalled request | `python3 scripts/x402_pay.py` | [x402.md](references/x402.md) |
| Pay an x402 challenge from a paid MCP tool | `python3 scripts/x402_pay.py` | [x402.md](references/x402.md) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pay an x402-gated MCP tool call


## Workflows

Expand Down Expand Up @@ -135,6 +136,7 @@ CLI behavior lives in `references/`. Repeatable patterns live in `workflows/`. L
| Check Aave V3 positions and health factor | [aave-positions.md](workflows/aave-positions.md) |
| Discover Aave V3 tokens, rates, and liquidity | [aave-markets.md](workflows/aave-markets.md) |
| Pay an HTTP `402` (x402) paywalled request | [x402-pay.md](workflows/x402-pay.md) |
| Pay an x402-gated MCP tool call | [x402-pay.md](workflows/x402-pay.md) |

## Global Flags

Expand Down Expand Up @@ -224,6 +226,7 @@ Before constructing any command, validate all user-provided values:
| x402 `payTo` / authorization `to` | Must match `^0x[0-9a-fA-F]{40}$` and equal the recipient in the `402` |
| x402 `value` | Atomic-unit integer that exactly equals the offered amount (the `exact` scheme is not a maximum) |
| x402 resource URL | Must be `https://`. Reject a `402` reached via an unexpected cross-host redirect |
| x402 MCP challenge | x402 v2 only. The signed `payment` object goes, as-is, in `_meta["x402/payment"]` of the retried tool call; a repeated `isError` result is a new challenge, never re-sign it without new user approval |

Do not pass unvalidated user input into any command.

Expand All @@ -236,7 +239,7 @@ Do not pass unvalidated user input into any command.
| Message signing | Always show exact message and chain before signing |
| Typed-data signing | Always show domain, primary type, chain, verifying contract, and message summary before signing |
| Swaps / bridges | Always confirm from/to tokens, amount, source/destination chain, slippage, quoted output, recipient address (if `--to-address` is set), and the destination gas top-up (if `--refuel` is set) before executing |
| x402 payments | Always confirm asset, decimals-correct amount, network, `payTo`, and resource URL before signing the authorization. One payment attempt per resource, never auto-retry a payment. Autonomous auto-pay is not supported. |
| x402 payments | Always confirm asset, decimals-correct amount, network, `payTo`, and resource (URL or MCP tool) before signing the authorization — over HTTP with `pay --confirm`, over MCP with `mcp-sign --confirm`. One payment attempt per resource, never auto-retry a payment. Autonomous auto-pay is not supported. |
| Perps trading | Always confirm symbol, side, size, leverage, venue, order type, and limit price if present before executing |
| Perps deposit/withdraw | Always confirm amount, asset, venue, network, and destination where applicable before executing |
| Predict trading | Always confirm token ID, side, size, price, order type, market, and outcome before executing |
Expand Down
87 changes: 78 additions & 9 deletions skills/metamask-agent-wallet/references/x402.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# x402 Payments (buyer / payer)

Use this when an HTTP request returns **`402 Payment Required`** following the
[x402 protocol](https://docs.x402.org), or when the user asks to pay for / fetch a paywalled API,
endpoint, file, or resource over HTTP.
[x402 protocol](https://docs.x402.org), when a **paid MCP tool** answers with an x402
payment-required result (the x402 MCP transport), or when the user asks to pay for / fetch a
paywalled API, endpoint, file, or resource.

`scripts/x402_pay.py` performs the payment: it fetches the URL, reads the server's payment
requirements, signs an EIP-3009 authorization with `mm wallet sign-typed-data`, retries with the
payment header, and reports the settlement. Signing is delegated to `mm`, so the private key stays
in the wallet. The script is pure Python standard library (run with `python3`).
`scripts/x402_pay.py` performs the payment. Over HTTP it fetches the URL, reads the server's
payment requirements, signs an EIP-3009 authorization with `mm wallet sign-typed-data`, retries
with the payment header, and reports the settlement. Over MCP it signs the tool's payment
challenge and prints the payload the caller places in the retried tool call (see
[MCP transport](#mcp-transport-paid-mcp-tools)). Signing is delegated to `mm`, so the private key
stays in the wallet. The script is pure Python standard library (run with `python3`).

## How x402 works (exact scheme, EVM)

Expand All @@ -30,6 +33,7 @@ per version.

- The `exact` scheme on EVM networks (`eip155:*`) using EIP-3009
`transferWithAuthorization`, protocol v1 and v2.
- Two transports: HTTP (v1 and v2) and MCP (v2 only — the x402 MCP transport is defined for v2).

### Not supported (rejected with a clear error)

Expand Down Expand Up @@ -73,14 +77,77 @@ python3 "$SKILL_DIR/scripts/x402_pay.py" inspect https://api.example.com/premium
python3 "$SKILL_DIR/scripts/x402_pay.py" pay https://api.example.com/premium --confirm
```

## MCP transport (paid MCP tools)

x402 v2 also runs over MCP ([spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/mcp.md)):
a paid MCP tool answers an unpaid `tools/call` with a tool result carrying `isError: true` and a
`PaymentRequired` object (in `structuredContent`, mirrored as JSON text in `content[0].text`).
The client signs the same EIP-3009 authorization as over HTTP and retries the tool call with the
`PaymentPayload` object in the request's `_meta["x402/payment"]`; the settlement receipt comes
back in the response's `_meta["x402/payment-response"]`.

The MCP session (and any OAuth on it) belongs to the caller, so this script does not speak MCP.
It handles the wallet half — parse the challenge, validate, sign, build the payload — and the
caller delivers the payload on the retry:

```bash
python3 "$SKILL_DIR/scripts/x402_pay.py" mcp-inspect [--challenge JSON | --challenge-file PATH]
python3 "$SKILL_DIR/scripts/x402_pay.py" mcp-sign [--challenge JSON | --challenge-file PATH] \
--confirm [--asset <contract>] [--network <network>]
```

The challenge may be passed as the whole JSON-RPC response, the tool result, or the bare
`PaymentRequired` object — via `--challenge` (inline JSON, or `-` to read stdin) or
`--challenge-file`. Per the spec, `structuredContent` is preferred and `content[0].text` is the
fallback; the script also checks `_meta["x402/error"]` (see the Cloudflare note below). Only x402
v2 challenges are accepted (the MCP transport is not defined for v1).

`mcp-inspect` prints the payment options (same fields as HTTP `inspect`) without signing.
`mcp-sign` requires `--confirm`, signs one eligible option (same selection and validation rules
as `pay`, including `--asset`/`--network` disambiguation), and prints:

```json
{
"status": "signed",
"asset": "USDC", "amount": "0.01", "network": "eip155:84532", "payTo": "0x2096…",
"metaKey": "x402/payment",
"payment": { "x402Version": 2, "resource": …, "accepted": …, "payload": … },
"paymentBase64": "eyJ4NDAyVmVyc2lvbiI6Mi..."
}
```

Take `payment` **as-is** (a raw JSON object, not base64) and set it as `_meta["x402/payment"]` in
the params of the retried `tools/call` — this is what the spec's text documents. After the retry,
check the response's `_meta["x402/payment-response"]`: `success: true` plus a `transaction` hash
means the payment settled. A result with `isError: true` again is a fresh challenge (verification
or settlement failed); surface it to the user — do not re-sign automatically, since a new
signature is a new payment authorization.

### Cloudflare Agents SDK servers (`agents/x402`)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be under ## Notes

Also, use the third person voice instead of first person in the SKILLs.


Verified live against a real deployment: servers built with Cloudflare's Agents SDK
(`withX402`/`paidTool`, e.g. `cloudflare/agents examples/x402-mcp`) diverge from the spec text two
ways. The challenge lives in `_meta["x402/error"]` (there is no `structuredContent` key at all —
`mcp-inspect`/`mcp-sign` check for this explicitly). And on the retry, the server decodes
`_meta["x402/payment"]` with `JSON.parse(atob(token))` — a **base64-encoded string**, not the
inline object the spec shows. Use `paymentBase64` instead of `payment` for these servers. A full
mainnet round-trip (real Base USDC, Cloudflare's unmodified example, CDP facilitator) confirmed
both fixes end-to-end.

Each `mcp-sign` run produces one single-use authorization (random nonce, validity window started
at signing time). Sign right before the retry; if the retry never happens the authorization
simply expires unspent.

## What the script checks

It pays an option whose `scheme` is `exact` on a network `mm` supports (resolved from
`mm chains list`). Before signing it verifies the amount is a positive atomic-unit integer, the
asset and `payTo` are valid addresses, the EIP-712 domain (`name`/`version`) is present, and the
URL is `https://` (plain `http` is allowed only on loopback for local testing). It does not follow
redirects, so a cross-host redirect cannot divert payment. After paying it makes a single retry
and reports settlement; it never retries a payment.
and reports settlement; it never retries a payment. The URL and redirect checks belong to the
HTTP transport; over MCP the connection is the caller's session, and all the other checks apply
identically before `mcp-sign` signs.

These are protocol-correctness checks. Which currencies and amounts are allowed is the wallet's
decision: the asset comes from the server's offer, the user confirms it, and `mm` signs (and is
Expand All @@ -103,8 +170,10 @@ amount; the signed value is the server's atomic amount either way.

## Confirmation

Run `inspect` and show the user the asset, amount, network, `payTo`, and resource URL. Run
`pay ... --confirm` only after the user approves. A signature authorizes a real token debit.
Run `inspect` (or `mcp-inspect`) and show the user the asset, amount, network, `payTo`, and
resource. Run `pay ... --confirm` (or `mcp-sign ... --confirm`) only after the user approves. A
signature authorizes a real token debit — over MCP that holds even though the script never
touches the network, because the signed payload settles when the caller replays the tool call.
Comment on lines +173 to +176

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of inspect (or mcp-inspect) we can add when to use either one. Or something that is descriptive so that agent doesn't always default inspect and sues mcp-inspect as fallback.


## Idempotency

Expand Down
Loading