---
name: okx-agent-payments-protocol
description: "Handle agent payments and paid endpoints via x402, MPP, payment links, a2a-pay, and HTTP-payment recurring or metered billing. Use for HTTP 402/payment-required; paid Agent or A2MCP endpoints; x402/Permit2; MPP channels, vouchers, or sessions; HTTP-payment subscriptions; or paymentId/link operations or status. Trigger phrases: x402/x402Version, X-PAYMENT, PAYMENT-REQUIRED, PAYMENT-SIGNATURE, WWW-Authenticate: Payment, x402 exact/exact+Permit2/upto/aggr_deferred, MPP charge/session, channelId/channel_id, payment-channel voucher/topup/settle/refund, metered billing, paymentId, a2a_, payment link, A2MCP, paid endpoint, and HTTP 402 period/permit2_subscription."
license: MIT
metadata:
  author: okx
  version: "4.6.5"
  homepage: "https://web3.okx.com"
---

# OKX Agent Payments Protocol (Dispatcher)

For an active marketplace A2MCP invocation started by `agent a2mcp invoke`,
follow `../okx-ai/references/a2mcp/invoke.md` through confirmation, payment,
and result presentation. Its bound `paymentId` does not start a new generic
quote or confirmation flow here.

> **READ FIRST — ZERO-TEXT-ON-TRIGGER + NEVER-SKIP-USER-GATES.**
>
> Between detecting a 402 (or any trigger word) and emitting the first user-facing card — the Step A3.5 recommendation card, or the Step A4 confirmation card — output **ZERO** user-visible text. No "received 402", no "triggered OKX Agent Payments Protocol", no "detected N schemes", no enumeration of schemes / networks / tokens / amounts, no "loading skill" — in any language (the same prohibition applies to the equivalent phrases in any other language). The skill-load tool call may run but emits no surrounding prose.
>
> Exactly **one** confirmation card runs per payment: A3.5's recommendation card (2+ candidates and user picks `yes`) OR A4's confirmation card (single candidate, OR user picked an alternative from A3.5's expanded list). Do NOT skip the applicable card under the pretext of "past user preference" / "streamlining" / "already confirmed once" — those preferences do not exist. Do NOT render both cards back-to-back with the same info — after `yes` on A3.5.5, go straight to Step A5. The next user-visible text after detection MUST be one of the two cards.

Three payment paths, distinguished by HTTP signature: **`accepts`-based 402** (challenge in body for v1 or `PAYMENT-REQUIRED` header for v2), **`WWW-Authenticate: Payment` 402** (channel-capable, `intent="charge"` or `"session"`), and **a2a-pay** (paymentId-based, no 402). Shared steps below (detect → decode → confirm → wallet check), then dispatch to a reference.

> **User-facing terminology — IMPORTANT**
>
> **Rule 1 — Always call it "OKX Agent Payments Protocol", and always render it bolded.** Use the exact English term **OKX Agent Payments Protocol** in user-visible messages regardless of the user's language, and always wrap it in markdown bold (`**OKX Agent Payments Protocol**`) so the user sees it emphasized. Keep it as a fixed English noun phrase even inside otherwise-Chinese sentences. Reserve protocol literals and internal identifiers for CLI invocations, HTTP headers, JSON payloads, and code — never speak them to the user.
>
> **Rule 2 — Do not narrate internal protocol detection.** The dispatch logic (which header was detected, which reference is being loaded, which scheme/intent was selected, TEE vs local-key path) is internal — keep it internal. The user only needs to see: (a) what is being paid, (b) what they need to confirm, (c) the result.
>
> **Rule 2 carve-out — narrow, alternatives list only.** Inside Step A3.5, the literals `exact` / `aggr_deferred` / `charge` may be exposed to the user **only** in the expanded **alternatives list** (the list rendered after the user picks "show others"), because at that point the user is explicitly choosing between schemes. They MUST NOT appear in: the default recommendation card, the "N other methods" summary line, status narration, error displays, post-payment summaries, or anywhere else. The recommendation card shows network / token / amount / recipient only — never the scheme name.
>
> **Rule 3 — Externally-defined protocol literals stay byte-for-byte exact.** The JSON field `x402Version`, the HTTP headers `X-PAYMENT` / `PAYMENT-SIGNATURE` / `PAYMENT-REQUIRED` / `WWW-Authenticate: Payment`, and the reference URL `https://x402.org` MUST appear verbatim wherever the protocol/server requires them — these are externally defined and changing them breaks interop. CLI subcommand names (`onchainos payment pay` / `pay-local` / `charge` / `session ...` / `a2a-pay ...`) are this CLI's own surface and may evolve; refer to them by their current name in CLI invocations and code, but never speak them to the user (Rule 2).
>
> **Example**
>
> (EN) `Preparing a payment via the **OKX Agent Payments Protocol**. Here are the charge details — please confirm before I proceed…`
> When narrating in another language, translate this lead line but keep **OKX Agent Payments Protocol** as a bolded English noun phrase.

> **Progress narration counts as user-visible — Rules 1-3 still apply.**
>
> Long-running flows (decode → confirm → wallet check → sign → replay) tempt status updates. Every progress line ("I'm now…", or its Chinese equivalent) is user-facing; Step labels and reference/scheme names are internal — do NOT echo them. The anchors:
>
> | Don't say | Say |
> |---|---|
> | "Detected HTTP 402, triggering OKX Agent Payments Protocol" / "Detected `PAYMENT-REQUIRED`, loading `exact`" | _(silent — detection / routing is internal)_ |
> | "CLI selected `exact`, assembling the `PAYMENT-SIGNATURE` header" / "taking the TEE path" | "Signing done, replaying the request" |
> | "Detected 2 schemes: exact (USD₮0), aggr_deferred (USDG)" / "checking balance to filter candidates" | _(silent — enumeration + balance check are internal; only the recommendation card is user-visible)_ |
> | "Entering session / charge mode" | "Channel opened" — describe the user-visible effect, not the internal mode |
> | "Per past preference, paying without re-confirming" | _(forbidden — no such preference; the gate is mandatory every time)_ |
>
> The same rules apply when narrating in any other language — match the intent of these "Don't say" / "Say" examples, not just the English wording.
>
> **These rules are authoritative and always in force** — when unsure whether a status line leaks internals, match it against the rows above and default to silence.

## Triggers (full list)

- **EN**: `402`, payment required, `x402`, `x402Version`, `X-PAYMENT`, `PAYMENT-REQUIRED`, `PAYMENT-SIGNATURE`, `WWW-Authenticate: Payment`, `permit2`, `upto`, metered billing, open / close / topup / settle channel, voucher, session payment, `channelId`, `channel_id`, `paymentId`, `a2a_`, create payment link, payment link, payment status
- subscribe / subscription / recurring payment / recurring charge / "pay every month" / cancel subscription / upgrade plan / downgrade plan → `period` scheme (see `references/subscription.md`)
  - **Exception:** when the message contains jobId / subId / ASP / provider / trial / renew / deliver / periodCount / subscription task, it is an Agent Commerce subscription task (monthly service agreement); route to `okx-ai` instead.
- The same trigger vocabulary applies to its equivalents in any other language (e.g. Chinese subscription / recurring-billing terms route to the `period` scheme the same way).
- Carve-out: AI-service/ASP subscriptions from the agent marketplace (context: ASP / Agent#N / 任务 / 试用期 / 服务方; NO 402 offer / resource URL / paymentId) belong to okx-ai (onchainos agent my-subscriptions / subscribe-detail), NOT the period scheme. For a bare "my subscriptions / 我的订阅" with neither signal, ask the user once instead of assuming period.

Any close / topup / settle / voucher / refund near a `channel_id` or session context = MPP mid-session op → `references/session.md`.

## Preflight

Preflight checks: At the start of each thread, complete the checks in `../okx-agentic-wallet/_shared/preflight.md`. If missing, read `_shared/preflight.md`.

## Command Routing & Reference map

Each 402 signal (or paymentId) → CLI command → reference. Detailed gating + decode/confirm steps are in Path A / Path B below.

| Signal | Command | Reference |
|---|---|---|
| 402 + `PAYMENT-REQUIRED` (v2) / body `x402Version` (v1) — one **or many** `accepts[]` schemes (`exact` / `exact`+Permit2 / `upto` / `aggr_deferred`) | **Primary — Path A:** `payment quote <url>` → confirm → `payment pay --payment-id --yes`. Single-scheme and multi-scheme take the **same** quote flow (the CLI decodes, converts, balance-checks, signs, **replays**, and returns the receipt). Even if you already curled the raw 402, re-enter via `payment quote <url>` — never assemble a header by hand and never jump straight to sign-only. **Compat only:** `payment pay --payload [--selected-index]` (sign-only + manual replay) when `quote` is unavailable. | Success path loads **no** reference. `references/accepts-schemes.md` only for: post-pay scheme-specific receipt reading, `Permit2 allowance insufficient` one-time approve, `pay-local`, the `pay --payload` compat path, or legacy x402 v1 (the CLI-output field tells you which scheme — `permit2Authorization` = `upto` / `exact`+Permit2, `sessionCert` = `aggr_deferred`, `authorization` = `exact`) |
| 402 offer with an `accepts[]` entry whose `scheme == "period"` (a.k.a. `permit2_subscription`) — recurring/subscription billing | `payment subscription subscribe/access/change/cancel/cancel-pending/my-subscriptions/allowance-status` | `references/subscription.md` |
| 402 + `WWW-Authenticate: Payment`, `intent="charge"` | `payment charge --challenge` | `references/charge.md` |
| 402 + `WWW-Authenticate: Payment`, `intent="session"` (or mid-session `channel_id`) | `payment session open/voucher/topup/close` | `references/session.md` |
| paymentId / `a2a_…` link / create-or-check payment link | `payment a2a-pay create/pay/status` | `references/a2a_charge.md` |
| A2MCP / 402 endpoint URL, "pay this endpoint", entry A/B payment node | `payment quote <url> [--param k=v ...] [--method GET \| POST \| ...]` | (inline — Path A) |
| A2MCP **MCP-transport** endpoint (URL ends `/mcp` or `/sse`, returns `text/event-stream` / JSON-RPC, or you have a tool name) | `payment quote <url>` (discovery → `mcpTools[]`) → `payment quote <url> --tool <name> --param k=v` (trigger 402) → `payment pay --payment-id <id> --yes` | `references/a2mcp-mcp.md` |
| User confirmed the quoted payment (currency/amount/scheme chosen) | `payment pay --payment-id <id> [--selected-index <n>] --yes` | (inline — Path A) |
| Need to decode a `PAYMENT-RESPONSE` header or a charge receipt | `payment decode-receipt (--header <b64> \| --receipt <json>)` | (inline — read-only) |

> **Don't load a reference on the success path.** On the primary Path A flow, `onchainos payment pay --payment-id --yes` signs, replays, and returns the settled receipt directly — skip `references/accepts-schemes.md` entirely (this holds for a single `accepts[]` scheme exactly as for multi-scheme). On the compat `pay --payload` path the CLI returns an `authorization_header` you replay yourself — same rule, no reference on success. Load `references/accepts-schemes.md` only on a **failure / legacy** path: `Permit2 allowance insufficient` → `references/accepts-schemes.md` (one-time approve), or a legacy x402 v1 raw proof → its "Legacy: x402 v1" section. `charge` / `session` / `a2a_charge` are always loaded — those are multi-phase flows.

> **Channel mid-session ops** (close / topup / settle / voucher / refund mentioned with an active `channel_id`, regardless of fresh 402) → stay here, jump straight into `references/session.md` at the matching phase. **Do NOT** search for a separate `close-channel` / `topup-channel` / `settle-channel` tool — they're all `onchainos payment session ...` subcommands.

---

# Path A: HTTP 402

## Path A (accepts-based): quote → confirm → pay — PREFERRED 2-round flow

**For an `accepts`-based 402 / A2MCP endpoint, the CLI does all mechanical work.
You do exactly two reasoning rounds.** (For `WWW-Authenticate: Payment` charge /
session challenges, skip this and use the protocol-detection steps below.)

### Step A1 — Extract params (round 1)
From the user prompt (Entry A) or the task payment node (Entry B), extract the
endpoint `url` and any known business params. Do NOT curl, decode, or convert
anything yourself.

### Step A2 — Quote
Run: `onchainos payment quote <url> [--param key=value ...] [--method GET|POST|...]`
The CLI probes the endpoint, parses the 402, checks your wallet balance, ranks
candidates, and writes a `paymentId`.

> **Probe method** — the CLI probes with `GET` by default. When the service
> declaration or the user's intent says the endpoint's initial call is **not GET**
> (e.g. the Bazaar `outputSchema.method` / business mind-map declares `"POST"`, or
> the user says "POST this endpoint"), pass `--method POST` (or the correct verb).
> Known business params then ride in the JSON **body** instead of the query string.
> Probing a POST-only A2MCP endpoint with the default GET can return 405 / a non-402
> response → `endpoint_unreachable` instead of the payment challenge. (The paid replay
> still uses `outputSchema.method` regardless — this flag only fixes the initial probe.)

> **MCP-transport A2MCP (`tools/call`-gated).** If `payment quote` returns `data.mcpTools[]`
> (the endpoint is MCP-type: URL ends `/mcp`|`/sse`, or replied `text/event-stream` / JSON-RPC),
> the paywall is at the tool-invocation layer, not the bare URL. **Read `references/a2mcp-mcp.md`**
> and follow it: pick a tool from `mcpTools[]` per the user's intent (use `AskUserQuestion` if
> ambiguous), assemble `--param key=value` from the tool's `inputSchema`, and re-run
> `payment quote <url> --tool <name> --param …` to trigger the 402 and land a `paymentId`. Then
> resume the normal Step A3 confirm → Step A4 `payment pay --payment-id <id> --yes`. Do NOT
> hand-write JSON-RPC or parse SSE — the CLI does the `initialize → tools/list → tools/call`
> handshake and SSE parsing internally.

Read `data`:
- `summary` — the human one-liner. `needsConfirm` is always true here.
- `candidates[]` (with `recommended:true`) and `alternatives[]` — the ranked schemes. Each carries `acceptsIndex` — its position in `accepts[]` (the ranked order differs from `accepts[]`, so never treat a candidate's list position as the index).
- Every candidate also carries `balanceStatus` (`sufficient` / `insufficient` / `unavailable`), `availableAmount`, `requiredAmount`, and `shortfall`. Before the user selects a method, display only the status and (when insufficient) the shortfall alongside each method — **do not display `depositAddress`, generate a QR, or send a funding notice yet**.
- `missingParams[]` + `merchantBody` — params the CLI could not fill; find the rest in `merchantBody`.
- `walletError` — if `login_required`, tell the user to log in, then re-quote.
- `recommended:null` on every candidate ⇒ no balance anywhere; present the list and ask.

### Step A3 — Confirm (round 2) — MANDATORY, never skip
Confirm the **full** payment terms — the same set Step A4 shows, so the buyer
always sees where the money goes before signing. Use `AskUserQuestion` for a
sufficient candidate; the insufficient-candidate funding card described below
replaces it as the one confirmation surface:
- **Network**: `chainName` (`chainId`) of the chosen candidate
- **Token / amount**: `amountHuman` `tokenSymbol` (for the `upto` scheme this is an
  authorization cap — render it as "up to `amountHuman`", not a fixed charge)
- **Scheme**: the chosen candidate's `scheme`
- **Pay to**: the challenge `recipient` (the `payTo` address)
- any `missingParams`

If the selected candidate is `insufficient`, use one funding-first card:

1. Run `onchainos agent funding-notice --chain <chainName> --currency <tokenSymbol> --available <availableAmount> --required <requiredAmount> --shortfall <shortfall> --deposit-address <depositAddress> --deposit-chain <chainName> --reason payment-402 --format json`.
2. Localize `contentCanonical`, preserving balance, address, four funding
   options, and one gas line only: X Layer = **on-chain gas is free after the
   funds arrive**; other chains = generic gas warning. Keep the
   "Withdraw from OKX" fee note — **the exchange may charge a withdrawal
   fee** — and never claim exchange withdrawals are free.
3. Follow `displayMode`: `image-notify` → call `user-notify --image-path` once;
   `terminal-unicode` → include `terminalQr`. QR failure → full text + address.

Offer `funded`, `cancel`, and—only if available—`choose another payment method`;
never advertise `pay anyway` or ask generic `yes/no`. Route: `funded` → re-quote;
alternative → show other sufficient entries from `candidates[] + alternatives[]`
(re-quote first if expired), then confirm once using `acceptsIndex`; `cancel` → stop. An unsolicited, unambiguous
request to pay despite the shortfall is that single authorization; ambiguous
`yes` is not. Do not run `funding-check` or add a hard balance gate.

Pass the chosen candidate's **`acceptsIndex`** as `--selected-index`
(NOT its position in `candidates[]`/`alternatives[]`) so the CLI signs exactly the
entry the user approved. **You MUST stop and confirm before paying — do not auto-pay.**

### Step A4 — Pay
Run: `onchainos payment pay --payment-id <id> --selected-index <n> --yes [--param key=value ...]`
`--yes` is required (the fund-moving confirming gate). `pay` signs the quoted payload,
replays, and returns the receipt — it never re-fetches the 402. Read `data.status`:
- `success` → report `txHash`; (Entry B) the task system marks the node paid.
- `failed` → surface `data.error`; offer retry.
- `pending` → poll / await terminal, then continue.

> To decode a returned `PAYMENT-RESPONSE` header or a charge receipt at any time,
> run `onchainos payment decode-receipt (--header <b64> | --receipt <json>)`.

---

## Step A1: Start from the original response (legacy / WWW-Authenticate detail)

> **`accepts`-based 402 → go back to Path A `payment quote`.** The steps below are the **legacy manual path** (decode → assemble → replay yourself) plus the shared decode detail for `WWW-Authenticate: Payment` charge / session challenges. If the 402 you hold is **`accepts`-based** (`PAYMENT-REQUIRED` header v2 / `x402Version` body v1 — `exact` / `exact`+Permit2 / `upto` / `aggr_deferred`, whether a **single** scheme or many), do **not** continue here: discard your raw 402 and re-enter at **Path A** with `payment quote <url>`. The quote flow runs the same mandatory confirm gate and returns the same receipt schema for single- and multi-scheme alike — a single scheme is not a shortcut for skipping `quote`. Continue below **only** for the `WWW-Authenticate: Payment` charge / session detail, or when `payment quote` is genuinely unavailable and you must fall back to the explicit `pay --payload` sign-only compat path.

You already have the original HTTP response. If it is **not 402**, return the body directly. Otherwise → Step A2.

**Capture any request parameters the user's prompt supplies** (e.g. "weather in San Francisco" → `city=San Francisco`, `token=0x…`; "translate to Chinese" → `lang=zh`). Record each as `name → value` for the Step A3-Params plan — values given here are **never re-asked**, just shown in the confirmation card. Keep them even if the first request didn't need them; the seller may require them on the paid replay.

## Step A2: Detect the protocol

```
Priority 1: response.headers['WWW-Authenticate']
  starts with "Payment "        → continue at Step A3-WWW-Authenticate
Priority 2: response.headers['PAYMENT-REQUIRED']
  base64-encoded JSON           → continue at Step A3-Accepts (v2)
Priority 3: response body JSON has "x402Version"
                                → continue at Step A3-Accepts (v1)
Otherwise