Use this skill when the user mentions creating a payment link, paying a paymentId / a2a_... link, or checking a2a payment status. Wraps `onchainos payment a2...
---
name: okx-a2a-payment
description: "Use this skill when the user mentions creating a payment link, paying a paymentId / a2a_... link, or checking a2a payment status. Wraps `onchainos payment a2a-pay` agent-to-agent payment protocol: seller-side `create`, buyer-side `pay` via EIP-3009 + TEE signing, and `status` query. Buyer-side trust is delegated to upstream — the skill signs whatever the on-server challenge declares. Do NOT use for external HTTP 402 resources — use okx-x402-payment. Do NOT use for wallet balance / transfer / login — use okx-agentic-wallet."
license: MIT
metadata:
author: okx
version: "3.1.3"
---
# Onchain OS A2A Payment
Wrap the `onchainos payment a2a-pay` CLI surface end-to-end for both seller and buyer roles. Buyer-side trust is delegated to the upstream caller — when invoked with a `paymentId`, the skill fetches the on-server challenge, TEE-signs it as-is, submits the credential, and auto-polls payment status to a terminal state.
## Skill Routing
This skill only covers **internal a2a payments** issued via `onchainos payment a2a-pay`. If the request fits one of the intents below, route to the corresponding skill instead:
| Intent | Use skill |
|--------|-----------|
| External HTTP 402 payment-gated resource (any non-onchainos URL) | `okx-x402-payment` |
| Wallet balance / transfer / login | `okx-agentic-wallet` |
| Task publish / accept / deliver / verify (business layer) — payment sub-step calls back into this skill via the Workflow A contract | upstream task / agent skill (out of repo) |
| Internal `onchainos payment a2a-pay` payment link | **this skill** |
## Triggers
Skill activates on user intents that match any of:
- "create payment link", "create a2a payment", "generate payment", "create payment authorization"
- "pay paymentId", "pay a2a_...", "pay this link", "settle this payment"
- "payment status", "a2a payment status", "check payment status", "where is my payment"
## Pre-flight Checks
Both seller (`create`) and buyer (`pay`) require an authenticated wallet session. The CLI calls `ensure_tokens_refreshed` internally and bails on `not logged in`.
Before invoking `create` or `pay`:
```bash
onchainos wallet status
```
- **Logged in** → proceed.
- **Not logged in** → ask the user to log in via `onchainos wallet login` (AK login, no email) or `onchainos wallet login <email>` (OTP login). **Do NOT attempt to sign without a live session.**
`status` does not require additional pre-flight beyond what the CLI itself enforces.
## Operation Flow
### Seller — Create a Payment Link (`a2a-pay create`)
**Inputs**:
- **Required**: `--amount` (decimal token amount, e.g. `"0.01"`), `--symbol` (e.g. `"USDT"`), `--recipient` (0x... EVM address — seller wallet)
- **Optional**: `--description`, `--realm`, `--expires-in` (seconds, default 1800)
**Steps**:
1. Run pre-flight (see above) — the CLI requires a live session.
2. Shell out:
```bash
onchainos payment a2a-pay create \
--amount <amount> --symbol <symbol> --recipient <recipient> \
[--description <text> --realm <domain> --expires-in <seconds>]
```
3. Parse the response — only `payment_id` and `deliveries.url` (optional) are present. The CLI no longer returns `amount` / `currency`; the skill echoes the seller's input args back for display.
4. Display to the user:
> Payment link created.
> • paymentId: `<id>`
> • Amount: `<amount input> <symbol input>` (decimal as you submitted)
> • Recipient: `<recipient input>`
> • Share with buyer: `<deliveries.url>` (if returned by the server) or `paymentId=<id>`
5. Suggest next: poll status anytime with `onchainos payment a2a-pay status --payment-id <id>` once the buyer is expected to have paid.
### Buyer — Pay a Payment Link (`a2a-pay pay`)
**Required input**: `paymentId` only. The CLI fetches the seller-issued challenge from the server and signs whatever amount / currency / recipient the challenge declares.
> **Trust model**: the buyer signs the seller's challenge as-is. Verifying that the challenge matches what the buyer agreed to pay is the **upstream caller's responsibility**: the user (or the upstream skill) MUST cross-check the seller's `paymentId` / `deliveries.url` against their out-of-band agreement (chat, task spec, prior negotiation) **before** calling this skill. Once the skill is invoked, it will sign the on-server challenge.
#### Step 1 — Sign and Submit
The skill does not run its own preview / yes-no gate; trust is delegated to the upstream caller (see the trust-model note above). Shell out directly:
```bash
onchainos payment a2a-pay pay --payment-id <paymentId>
```
The CLI fetches the on-server challenge, TEE-signs the EIP-3009 authorization, and submits the credential. The successful response shape:
```json
{
"payment_id": "a2a_xxx",
"status": "<status>",
"tx_hash": "<hash or null>",
"valid_after": 0,
"valid_before": 1746000000,
"signature": "0x..."
}
```
#### Step 2 — Auto-poll Status to Terminal
Status classification:
- **Non-terminal** (poll): `pending`, `settling`
- **Terminal** (stop): `completed`, `failed`, `expired`, `cancelled`
If `status` is already terminal → render the result (see table below) and stop.
If non-terminal → poll every **3 seconds**, up to a **60-second** total budget:
```bash
onchainos payment a2a-pay status --payment-id <paymentId>
```
- As soon as a terminal status is observed → render full result (status + tx_hash + block_number) and stop.
- If 60 seconds elapse and the status is still non-terminal → return the current `status` plus the paymentId, and tell the user: "Status is still `<status>` after 60s; you can run `status` again later."
**Terminal display strings:**
| status | Display |
|--------|---------|
| `completed` | "✅ Payment confirmed on-chain. tx_hash: `<tx_hash>` block: `<block_number>`" |
| `failed` | "❌ Payment failed. (include the server-provided reason if any)" |
| `expired` | "⌛ Payment link expired before settlement. Ask the seller for a new one." |
| `cancelled` | "🚫 Seller cancelled this payment." |
### Status — Query Payment State (`a2a-pay status`)
**Input**: `paymentId`.
**Steps**:
1. Run:
```bash
onchainos payment a2a-pay status --payment-id <paymentId>
```
2. Map the returned `status` to a human-readable line:
| status | Meaning | Display |
|--------|---------|---------|
| `pending` | Awaiting buyer signature | "⏳ Awaiting buyer signature." |
| `settling` | Credential received, settling on-chain | "🔄 Settling on-chain (credential submitted, awaiting confirmation)." |
| `completed` | Confirmed on-chain | "✅ Confirmed on-chain. tx_hash: `<tx_hash>` block: `<block_number>` fee: `<fee_decimal> <fee_symbol>`" |
| `failed` | Payment failed | "❌ Failed. (include the server-provided reason if any)" |
| `expired` | Expired before settlement | "⌛ Expired before settlement." |
| `cancelled` | Seller cancelled | "🚫 Cancelled by seller." |
3. **Rendering the fee.** The CLI returns `fee_amount` as a top-level string in minimal units (and `fee_bps` as the basis-points used). To compute `<fee_decimal>`, look up the token decimals in the table under "Amount Display Rules". For `<fee_symbol>`, reuse the `--symbol` the seller passed to `create` for the same `paymentId` — the upstream caller (or the seller flow that issued the link) is the source of truth for the token symbol; the `status` response itself does not echo it back. If neither is available, display `fee_amount` minimal units as-is.
4. Suggest next:
- `pending` / `settling` → "Check again in a few moments" or wait briefly and re-run `status`.
- `completed` → recommend `okx-agentic-wallet` to verify the buyer's post-payment balance delta.
- `failed` → recommend checking buyer balance via `okx-agentic-wallet`, and if `tx_hash` is present, inspect it via `okx-security tx-scan`.
## Cross-Skill Workflows
### Workflow A — Sub-skill called from an upstream agent flow (most common)
Applicable upstream callers: any agent-to-agent task / chat / agent flow that holds the seller-issued payment information.
**Contract — upstream MUST hand off `paymentId`** (skill stops and asks the user if missing). Upstream is also responsible for confirming, before invoking this skill, that the `paymentId` matches the buyer's agreed terms — once invoked, the skill signs whatever the on-server challenge declares.
```
1. <upstream caller> verifies paymentId matches the buyer's agreed terms → hands off paymentId
↓
2. okx-a2a-payment (this skill) onchainos payment a2a-pay pay → auto-poll status → display terminal state
↓
3. okx-agentic-wallet optional: onchainos wallet balance to see post-payment delta
```
### Workflow B — Seller manually creates a payment link
```
1. okx-a2a-payment create → paymentId + deliveries.url
2. Seller shares paymentId (and optionally deliveries.url) with the buyer out-of-band (chat / QR / message)
3. Buyer cross-checks the paymentId / deliveries.url against the seller's quoted terms, then runs Workflow A starting from step 2 with the received paymentId
```
### Workflow C — Payment failure triage
```
1. okx-a2a-payment status → expired / failed / cancelled
2. Branch on terminal state:
- expired → ask seller to create a new link
- failed → check buyer balance via okx-agentic-wallet; inspect tx_hash via okx-security tx-scan if present
- cancelled → contact seller out-of-band
```
## Upstream Routing — Avoiding `create` Loops
This skill is stateless per call and has no view of the conversation. If the upstream seller agent routes by surface keywords alone (e.g. matches `付款` / `pay` / `payment` and always calls `create`), it will loop:
```
buyer: "I want to pay" → seller create → returns paymentId_A
buyer pays via this skill, then sends:
buyer: "payment successful" → seller matches "payment" → create AGAIN → paymentId_B (wrong)
```
The skill cannot break this loop — the fix lives in the upstream caller's intent router. When you wire this skill into a seller-side agent, enforce the following before calling `create`:
1. **Detect existing paymentId in the incoming message.** If the buyer's message contains an `a2a_...` id (or a `deliveries.url` you previously issued), route to `okx-a2a-payment status` for that id. Do NOT call `create`.
2. **Disambiguate intent beyond keywords.** Map upstream intents to commands:
| Buyer says | Intent | Route to |
|------------|--------|----------|
| "I want to pay" / "请付款" / "怎么付" / "give me a link" | request-invoice | `create` |
| "paid" / "payment successful" / "已付" / "已转账" / contains a paymentId or tx hash | payment-receipt | `status` (or no-op if already terminal) |
| "cancel" / "refund" | cancel/refund | out of scope for this skill |
Plain keyword matching on `付款` / `pay` / `payment` is not enough — both request-invoice and payment-receipt utterances contain those tokens.
3. **Track per-conversation order state upstream.** Once `create` issues a paymentId for a given (buyer, order) context, the upstream agent must remember that paymentId in its own conversation / order state and mark the order as "awaiting payment". Subsequent buyer messages in that context default to `status` against the remembered paymentId until either the payment reaches a terminal state or the user explicitly asks for a new order.
4. **Idempotency on `create`.** Before issuing a new `create`, the upstream agent must check its own state: if a non-terminal paymentId already exists for the same buyer / order context, reuse it instead of creating a new one.
This guidance is advisory for upstream agent authors — this skill itself will still execute whichever command you call. Routing correctness is the upstream caller's job.
## Amount Display Rules
When converting `amount` (or `fee_amount`) from minimal units to a decimal display, use the hardcoded decimals table:
| Token | Decimals | "1000000" minimal renders as |
|-------|----------|------------------------------|
| USDC | 6 | 1.00 USDC |
| USDT | 6 | 1.00 USDT |
| USDG | 6 | 1.00 USDG |
| ETH | 18 | (`1e18` minimal = 1.00 ETH) |
For any symbol not in the table: render `<minimal> <symbol>` and append the warning `unknown decimals — please double-check the seller-provided amount`. **Do not block** the flow.
## Edge Cases
| Scenario | Handling |
|----------|----------|
| `onchainos wallet status` reports not logged in | Prompt the user to run `onchainos wallet login`. Never attempt to sign without a live session. |
| User provides no `paymentId` | STOP and ask the user for the seller-issued paymentId. |
| CLI reports `payment ... not payable` / expired challenge / unsupported intent | Relay the error verbatim and surface it as a **terminal failure** — do NOT retry signing. |
| `paymentId` not found / 404 from server | Relay the error and ask the user to confirm the paymentId with the seller or upstream caller. |
| `pay` succeeded but status is still `pending` / `settling` after the 60s poll budget | Return the current status (verbatim) + paymentId; tell the user `Status is still <status> after 60s; you can run status again later`. |
| Server returns a 5xx | Surface the status code and any `errorMessage` verbatim. **Do not auto-retry `pay`** — every retry produces a fresh EIP-3009 nonce + signature; let the upstream caller decide whether to re-invoke. `status` is read-only and safe to retry manually. |
| `--symbol` is not in the hardcoded decimals table | Apply the unknown-decimals fallback (see Amount Display Rules). Do not block. |
| `--expires-in` was set too short and the link is now past its window | `status` returns `expired`; ask the seller to create a new link. |
## Command Index
| # | Command | Role | Purpose |
|---|---------|------|---------|
| 1 | `onchainos payment a2a-pay create` | Seller | Create a payment link, returns paymentId + deliveries |
| 2 | `onchainos payment a2a-pay pay` | Buyer | Fetch challenge → TEE-sign EIP-3009 → submit credential |
| 3 | `onchainos payment a2a-pay status` | Either | Query current status (pending / settling / completed / failed / expired / cancelled) |
## CLI Command Reference
### 1. `onchainos payment a2a-pay create`
```bash
onchainos payment a2a-pay create \
--amount <decimal> --symbol <symbol> --recipient <address> \
[--description <text>] [--realm <domain>] [--expires-in <seconds>]
```
| Param | Required | Default | Description |
|-------|----------|---------|-------------|
| `--amount` | Yes | - | Decimal token amount (e.g. `"50"` or `"0.01"`) |
| `--symbol` | Yes | - | ERC-20 token symbol (e.g. `"USDT"`) |
| `--recipient` | Yes | - | Seller wallet address (= EIP-3009 `to`) |
| `--description` | No | - | Human-readable description shown to the buyer |
| `--realm` | No | - | Seller / provider domain (e.g. `provider.example.com`) |
| `--expires-in` | No | 1800 | Payment-link expiration window in seconds |
**Return fields**: `payment_id`, `deliveries` (object containing `url` when issued by the server).
### 2. `onchainos payment a2a-pay pay`
```bash
onchainos payment a2a-pay pay --payment-id <id>
```
| Param | Required | Default | Description |
|-------|----------|---------|-------------|
| `--payment-id` | Yes | - | Seller-issued paymentId |
**Return fields**: `payment_id`, `status`, `tx_hash` (optional), `valid_after`, `valid_before`, `signature`.
### 3. `onchainos payment a2a-pay status`
```bash
onchainos payment a2a-pay status --payment-id <id>
```
| Param | Required | Default | Description |
|-------|----------|---------|-------------|
| `--payment-id` | Yes | - | The paymentId to query |
**Return fields**: `payment_id`, `status`, `tx_hash` (optional), `block_number` (optional), `block_timestamp` (optional), `fee_amount` (optional, minimal units), `fee_bps` (optional).
## Quickstart
```bash
# Seller — create a payment link
onchainos payment a2a-pay create \
--amount 0.01 --symbol USDT \
--recipient 0xSellerWalletAddress
# → { "payment_id": "a2a_xxx", "deliveries": { "url": "..." } }
# Buyer — pay (signs the on-server challenge as-is; trust delegated to upstream)
onchainos payment a2a-pay pay --payment-id a2a_xxx
# Either side — query status (skill auto-polls this for ~60s after pay if non-terminal)
onchainos payment a2a-pay status --payment-id a2a_xxx
```
don't have the plugin yet? install it then click "run inline in claude" again.
explicit decision points for wallet session, paymentId validation, status classification, and server errors; hardcoded decimals table integrated into procedure; upstream routing guidance pulled into decision points; edge cases and all CLI return fields documented in inputs and output contract sections.
activate on user intent to create an internal a2a payment link (seller role), pay an existing paymentId via EIP-3009 TEE signing (buyer role), or check payment status. this skill wraps the onchainos payment a2a-pay cli end-to-end for both roles. buyer-side trust is delegated upstream: the skill fetches the on-server challenge and signs it as-is without validation, so the upstream caller (user or agent) must cross-check the paymentId against out-of-band agreement before invoking the pay command. use this for internal onchainos payments only. route external HTTP 402 resources to okx-x402-payment and wallet operations to okx-agentic-wallet.
both create and pay commands require a live authenticated wallet session. run onchainos wallet status before either command:
onchainos wallet login (AK login, no email prompt) or onchainos wallet login <email> (OTP login). do not attempt signing without an active session.required parameters:
amount (string, decimal token amount, e.g. "0.01" or "50")symbol (string, erc-20 token symbol, e.g. "USDT", "ETH", "USDC")recipient (string, 0x... evm address of seller wallet)optional parameters:
description (string, human-readable text shown to buyer)realm (string, seller / provider domain, e.g. provider.example.com)expires-in (integer, seconds until link expires, default 1800)required parameter:
paymentId (string, seller-issued id in format a2a_...)required parameter:
paymentId (string, seller-issued id in format a2a_...)none. onchainos payment a2a-pay communicates directly with the on-server challenge endpoint and tee signing service via the authenticated cli session.
run pre-flight check: execute onchainos wallet status. if output indicates "not logged in", halt and ask user to log in via onchainos wallet login or onchainos wallet login <email>. do not proceed without an active session.
shell out to the create command with required and optional parameters:
onchainos payment a2a-pay create \
--amount <amount> --symbol <symbol> --recipient <recipient> \
[--description <description>] [--realm <realm>] [--expires-in <expires-in>]
parse the cli json response. extract payment_id (string) and optional deliveries.url (string). note: the cli no longer echoes back amount or currency in the response; use the input parameters the seller provided for display.
output result to user:
Payment link created. • paymentId:
<payment_id>• Amount:<amount_input> <symbol_input>(decimal as submitted) • Recipient:<recipient_input>• Share with buyer:<deliveries.url>(if returned by server) orpaymentId=<payment_id>
suggest next action: tell the user they can check status anytime with onchainos payment a2a-pay status --payment-id <payment_id> once the buyer is expected to have paid.
receive paymentId as input from user or upstream caller. if missing, stop and ask the user for the seller-issued paymentId.
relay the trust model to the user (if this is an interactive call): "the seller's challenge will be signed as-is; you are responsible for confirming that this paymentId matches what you agreed to pay out-of-band."
shell out to the pay command:
onchainos payment a2a-pay pay --payment-id <paymentId>
parse the cli json response. extract payment_id, status, tx_hash (may be null), valid_after, valid_before, and signature.
proceed to step 2b (auto-poll).
classify the returned status:
pending, settlingcompleted, failed, expired, cancelledif status is already terminal, skip polling and jump to step 2c.
if non-terminal, enter polling loop:
onchainos payment a2a-pay status --payment-id <paymentId>after each poll, extract status, tx_hash, block_number. check if status is terminal. if terminal, stop polling and jump to step 2c. if 60 seconds elapse and status is still non-terminal, return current status + paymentId and tell the user: "Status is still <status> after 60s; you can run status again later." do not continue polling.
display the status using the following table:
| status | display string |
|---|---|
completed |
✅ Payment confirmed on-chain. tx_hash: <tx_hash> block: <block_number> |
failed |
❌ Payment failed. (include server-provided reason if available) |
expired |
⌛ Payment link expired before settlement. ask the seller for a new one. |
cancelled |
🚫 Seller cancelled this payment. |
receive paymentId as input. if missing, stop and ask user for the paymentId.
shell out:
onchainos payment a2a-pay status --payment-id <paymentId>
parse response. extract status, tx_hash, block_number, block_timestamp, fee_amount (minimal units, optional), fee_bps (optional).
map status to human-readable display:
| status | display string |
|---|---|
pending |
⏳ Awaiting buyer signature. |
settling |
🔄 Settling on-chain (credential submitted, awaiting confirmation). |
completed |
✅ Confirmed on-chain. tx_hash: <tx_hash> block: <block_number> fee: <fee_decimal> <fee_symbol> |
failed |
❌ Failed. (include server-provided reason if available) |
expired |
⌛ Expired before settlement. |
cancelled |
🚫 Cancelled by seller. |
if fee_amount is present, convert from minimal units to decimal using the hardcoded decimals table below. if symbol is not in the table, render <fee_amount> <symbol> and append the warning: "unknown decimals , please double-check the seller-provided amount." do not block the flow.
| token | decimals | example |
|---|---|---|
| USDC | 6 | "1000000" minimal = 1.00 USDC |
| USDT | 6 | "1000000" minimal = 1.00 USDT |
| USDG | 6 | "1000000" minimal = 1.00 USDG |
| ETH | 18 | "1000000000000000000" minimal = 1.00 ETH |
suggest next action based on status:
pending or settling: "check again in a few moments or re-run status."completed: "recommend okx-agentic-wallet to verify post-payment balance delta."failed: "recommend checking buyer balance via okx-agentic-wallet and inspecting tx_hash via okx-security tx-scan if present."if onchainos wallet status indicates "not logged in":
onchainos wallet login (AK login) or onchainos wallet login <email> (OTP login).create or pay.if wallet is logged in:
if user or caller provides no paymentId:
a2a_...).if paymentId is provided but cli returns 404 or "not found":
if paymentId is provided and found:
after the pay command returns with status in response:
if status is terminal (completed, failed, expired, cancelled):
if status is non-terminal (pending, settling):
<status> after 60s; you can run status again later."if cli returns a 5xx error:
errorMessage verbatim.pay (each retry generates a fresh nonce + signature; let upstream caller decide whether to re-invoke).status is read-only and safe for user to retry manually.if cli returns a 4xx error other than 404 (e.g. "payment not payable", "expired challenge", "unsupported intent"):
if the response includes fee_amount but the token symbol is not in the hardcoded decimals table:
<fee_amount> <symbol> (minimal units).json object (internal) containing:
payment_id (string, format a2a_...)deliveries.url (string, optional; when present, shareable link for buyer)display to user:
json object (internal) from final status poll:
payment_id (string)status (string, one of: pending, settling, completed, failed, expired, cancelled)tx_hash (string or null)block_number (integer or null)signature (string, eip-3009 signature)display to user:
completed.json object (internal):
payment_id (string)status (string)tx_hash (string or null)block_number (integer or null)block_timestamp (integer or null)fee_amount (string in minimal units, optional)fee_bps (integer, optional)display to user:
all errors returned verbatim as display strings, marked as failure or blocking state:
status onlyone of:
completed)failed)expired)cancelled)<status> after 60s; you can run status again later." (non-terminal after 60s poll)one of:
pending)settling)completed)failed)expired)cancelled)the skill itself is stateless. to prevent the upstream caller from looping (creating multiple paymentIds for a single order), implement the following in the upstream agent:
before calling create, scan the incoming user message for existing a2a_... ids or deliveries.url. if found, route to status instead of create.
disambiguate intent beyond keywords. map intents:
createstatustrack per-conversation order state. once create issues a paymentId for (buyer, order), remember it in your own state and mark the order "awaiting payment". subsequent messages default to status against that paymentId until terminal or explicit new order.
idempotency on create: before issuing a new create, check your own state. if a non-terminal paymentId exists for (buyer, order), reuse it instead of creating a new one.