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
, 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) | |
| Wallet balance / transfer / login | |
| 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 (
) and buyer (
) require an authenticated wallet session. The CLI calls
internally and bails on
.
- Logged in → proceed.
- Not logged in → ask the user to log in via (AK login, no email) or
onchainos wallet login <email>
(OTP login). Do NOT attempt to sign without a live session.
does not require additional pre-flight beyond what the CLI itself enforces.
Operation Flow
Seller — Create a Payment Link ()
Inputs:
- Required: (decimal token amount, e.g. ), (e.g. ), (0x... EVM address — seller wallet)
- Optional: , , (seconds, default 1800)
Steps:
-
Run pre-flight (see above) — the CLI requires a live session.
-
Shell out:
bash
onchainos payment a2a-pay create \
--amount <amount> --symbol <symbol> --recipient <recipient> \
[--description <text> --realm <domain> --expires-in <seconds>]
-
Parse the response — only
and
(optional) are present. The CLI no longer returns
/
; the skill echoes the seller's input args back for display.
-
Display to the user:
Payment link created.
• paymentId:
• Amount:
<amount input> <symbol input>
(decimal as you submitted)
• Recipient:
• Share with buyer:
(if returned by the server) or
-
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 ()
Required input:
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
/
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): ,
- Terminal (stop): , , ,
If
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 plus the paymentId, and tell the user: "Status is still after 60s; you can run again later."
Terminal display strings:
| status | Display |
|---|
| "✅ Payment confirmed on-chain. tx_hash: block: " |
| "❌ Payment failed. (include the server-provided reason if any)" |
| "⌛ Payment link expired before settlement. Ask the seller for a new one." |
| "🚫 Seller cancelled this payment." |
Status — Query Payment State ()
Steps:
-
Run:
bash
onchainos payment a2a-pay status --payment-id <paymentId>
-
Map the returned
to a human-readable line:
| status | Meaning | Display |
|---|
| Awaiting buyer signature | "⏳ Awaiting buyer signature." |
| Credential received, settling on-chain | "🔄 Settling on-chain (credential submitted, awaiting confirmation)." |
| Confirmed on-chain | "✅ Confirmed on-chain. tx_hash: block: fee: <fee_decimal> <fee_symbol>
" |
| Payment failed | "❌ Failed. (include the server-provided reason if any)" |
| Expired before settlement | "⌛ Expired before settlement." |
| Seller cancelled | "🚫 Cancelled by seller." |
-
Rendering the fee. The CLI returns
as a top-level string in minimal units (and
as the basis-points used). To compute
, look up the token decimals in the table under "Amount Display Rules". For
, reuse the
the seller passed to
for the same
— the upstream caller (or the seller flow that issued the link) is the source of truth for the token symbol; the
response itself does not echo it back. If neither is available, display
minimal units as-is.
-
Suggest next:
- / → "Check again in a few moments" or wait briefly and re-run .
- → recommend to verify the buyer's post-payment balance delta.
- → recommend checking buyer balance via , and if is present, inspect it via .
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 (skill stops and asks the user if missing). Upstream is also responsible for confirming, before invoking this skill, that the
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 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
/
/
and always calls
), 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
:
-
Detect existing paymentId in the incoming message. If the buyer's message contains an
id (or a
you previously issued), route to
for that id. Do NOT call
.
-
Disambiguate intent beyond keywords. Map upstream intents to commands:
| Buyer says | Intent | Route to |
|---|
| "I want to pay" / "请付款" / "怎么付" / "give me a link" | request-invoice | |
| "paid" / "payment successful" / "已付" / "已转账" / contains a paymentId or tx hash | payment-receipt | (or no-op if already terminal) |
| "cancel" / "refund" | cancel/refund | out of scope for this skill |
Plain keyword matching on
/
/
is not enough — both request-invoice and payment-receipt utterances contain those tokens.
-
Track per-conversation order state upstream. Once
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
against the remembered paymentId until either the payment reaches a terminal state or the user explicitly asks for a new order.
-
Idempotency on . Before issuing a new
, 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
(or
) 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 | ( minimal = 1.00 ETH) |
For any symbol not in the table: render
and append the warning
unknown decimals — please double-check the seller-provided amount
.
Do not block the flow.
Edge Cases
| Scenario | Handling |
|---|
| reports not logged in | Prompt the user to run . Never attempt to sign without a live session. |
| User provides no | STOP and ask the user for the seller-issued paymentId. |
| CLI reports / expired challenge / unsupported intent | Relay the error verbatim and surface it as a terminal failure — do NOT retry signing. |
| not found / 404 from server | Relay the error and ask the user to confirm the paymentId with the seller or upstream caller. |
| succeeded but status is still / 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 verbatim. Do not auto-retry — every retry produces a fresh EIP-3009 nonce + signature; let the upstream caller decide whether to re-invoke. is read-only and safe to retry manually. |
| is not in the hardcoded decimals table | Apply the unknown-decimals fallback (see Amount Display Rules). Do not block. |
| was set too short and the link is now past its window | returns ; 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 |
|---|
| Yes | - | Decimal token amount (e.g. or ) |
| Yes | - | ERC-20 token symbol (e.g. ) |
| Yes | - | Seller wallet address (= EIP-3009 ) |
| No | - | Human-readable description shown to the buyer |
| No | - | Seller / provider domain (e.g. ) |
| No | 1800 | Payment-link expiration window in seconds |
Return fields:
,
(object containing
when issued by the server).
2. onchainos payment a2a-pay pay
bash
onchainos payment a2a-pay pay --payment-id <id>
| Param | Required | Default | Description |
|---|
| Yes | - | Seller-issued paymentId |
Return fields:
,
,
(optional),
,
,
.
3. onchainos payment a2a-pay status
bash
onchainos payment a2a-pay status --payment-id <id>
| Param | Required | Default | Description |
|---|
| Yes | - | The paymentId to query |
Return fields:
,
,
(optional),
(optional),
(optional),
(optional, minimal units),
(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