From onchainos-skills
Signs x402 payment proofs via TEE wallet session (JWT) or local EIP-3009 for payment-gated APIs/resources. Returns signature+authorization for HTTP 402 headers.
npx claudepluginhub okx/onchainos-skills --plugin onchainos-skillsThis skill uses the workspace's default tool permissions.
Sign an [x402](https://x402.org) payment authorization and return the payment proof for accessing payment-gated resources. Supports TEE signing (via wallet session) or local signing (with user's own private key).
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Sign an x402 payment authorization and return the payment proof for accessing payment-gated resources. Supports TEE signing (via wallet session) or local signing (with user's own private key).
Read
../okx-agentic-wallet/_shared/preflight.md. If that file does not exist, read_shared/preflight.mdinstead.
okx-agentic-walletokx-wallet-portfoliookx-dex-swapokx-dex-tokenokx-dex-marketokx-dex-signalokx-dex-trenchesokx-onchain-gatewayokx-security--network uses CAIP-2 format: eip155:<realChainIndex>. All EVM chains returned by onchainos wallet chains are supported. The realChainIndex field in the chain list corresponds to the <chainId> portion of the CAIP-2 identifier.
Common examples:
| Chain | Network Identifier |
|---|---|
| Ethereum | eip155:1 |
| X Layer | eip155:196 |
| Base | eip155:8453 |
| Arbitrum One | eip155:42161 |
| Linea | eip155:59144 |
For the full list of supported EVM chains and their realChainIndex, run:
onchainos wallet chains
Non-EVM chains (e.g., Solana, Tron, Ton, Sui) are not supported by x402 payment — only
eip155:*identifiers are accepted.
x402 is an HTTP payment protocol with two versions:
| v2 | v1 | |
|---|---|---|
| 402 payload location | PAYMENT-REQUIRED header (base64-encoded JSON); body is {} | Response body (direct JSON, not base64) |
| Client header name | PAYMENT-SIGNATURE | X-PAYMENT |
| Client payload structure | { x402Version, resource, accepted, payload } | { x402Version, scheme, network, payload } |
| Amount field in accepts | amount | maxAmountRequired |
| Settlement response header | PAYMENT-RESPONSE | X-PAYMENT-RESPONSE |
The full flow is:
HTTP 402PAYMENT-REQUIRED header for v2; from response body for v1), pass the accepts array to the CLIonchainos payment x402-pay --accepts '<accepts array>' → CLI selects best scheme, returns payment proofThis skill owns steps 2–4 end to end.
onchainos payment x402-pay \
--accepts '[{"scheme":"aggr_deferred","network":"eip155:196","amount":"1000000","payTo":"0xRecipientAddress","asset":"0x4ae46a509f6b1d9056937ba4500cb143933d2dc8","maxTimeoutSeconds":300}]'
| # | Command | Description |
|---|---|---|
| 1 | onchainos payment x402-pay | Sign an x402 payment via TEE (wallet session) and return the proof |
| 2 | onchainos payment eip3009-sign | Sign an EIP-3009 authorization locally with a hex private key |
Make the HTTP request the user asked for. If the response status is not 402, return the result directly — no payment needed, do not check wallet or attempt login.
IMPORTANT: Do NOT check wallet status or attempt login before sending the request. Only proceed to payment steps if the response is HTTP 402.
If the response is HTTP 402, extract the payment requirements. The location differs by version:
v2 — payload is in the PAYMENT-REQUIRED response header (base64-encoded JSON):
headerValue = response.headers['PAYMENT-REQUIRED']
decoded = JSON.parse(atob(headerValue))
v1 — payload is in the response body (direct JSON, not base64):
decoded = JSON.parse(response.body)
In both cases, extract the accepts array:
accepts = decoded.accepts // keep the full array for the CLI
option = decoded.accepts[0] // for display purposes
Save decoded.accepts as a JSON string — it will be passed directly to the CLI via --accepts. The CLI handles scheme selection internally (prefers "exact" > "aggr_deferred" > first entry).
Save decoded for later — you will need decoded.x402Version and decoded.resource (v2) when assembling the payment header in Step 5.
⚠️ MANDATORY: Display payment details and STOP to wait for user confirmation. Do NOT check wallet status, run onchainos wallet status, attempt login, or call any other tool until the user explicitly confirms.
Present the following information to the user:
This resource requires x402 payment:
- Network:
<chain name>(<option.network>)- Token:
<token symbol>(<option.asset>)- Amount:
<human-readable amount>(fromoption.amountfor v2, oroption.maxAmountRequiredfor v1; convert from minimal units using token decimals)- Pay to:
<option.payTo>Proceed with payment? (yes / no)
Then STOP and wait for the user's response. Do not proceed in the same turn.
Now that payment is required, check if the user has a wallet session:
onchainos wallet status
"You are not logged in. How would you like to sign the payment?"
- Wallet login — log in to the wallet, then sign via TEE (recommended)
- Local private key — sign locally with your own private key (no login needed)
Then STOP and wait for the user's response. Do not proceed in the same turn.
Run onchainos wallet login (AK login, no email) or onchainos wallet login <email> (OTP login), then proceed to Step 4.
Only after the user chooses this option, read ~/.onchainos/.env to check if EVM_PRIVATE_KEY is already configured:
Key found → inform the user and proceed to the Local Signing Fallback below. The payer address will be derived from the private key automatically by the CLI.
Key not found → inform the user:
"No private key configured. Please save it to
~/.onchainos/.env: add a lineEVM_PRIVATE_KEY=0x<your_hex_key>, then let me know."
Wait for user action before proceeding.
Pass the raw accepts array to the CLI. The CLI automatically selects the best scheme (prefers "exact" over "aggr_deferred"):
onchainos payment x402-pay \
--accepts '<JSON.stringify(decoded.accepts)>'
The CLI returns different fields depending on the scheme it selected:
aggr_deferred scheme (preferred): Only Session Key (Ed25519) signing, skips EOA signing. Returns { signature, authorization, sessionCert }. The signature is the base64-encoded session-key signature.exact scheme: Full EIP-3009 signing via TEE. Returns { signature, authorization }. The signature is the secp256k1 EIP-3009 signature.Check the response: if sessionCert is present, the CLI selected aggr_deferred scheme.
If signing fails (e.g., session expired, not logged in, AK re-login failed):
onchainos wallet login or onchainos wallet login <email>, then retry this step.
The PaymentPayload structure and header name differ between v1 and v2.
x402Version >= 2)Header name: PAYMENT-SIGNATURE
The accepted field is a single object (the entry the CLI selected from accepts), not the whole array. If the CLI returned sessionCert (aggr_deferred scheme), merge it into the existing accepted.extra (preserve original fields like name, version):
// Pick the accepted entry (CLI selected aggr_deferred if available)
accepted = decoded.accepts.find(a => a.scheme === selectedScheme)
// aggr_deferred: merge sessionCert into existing extra
if (sessionCert) {
accepted.extra = { ...accepted.extra, sessionCert }
}
paymentPayload = {
x402Version: decoded.x402Version,
resource: decoded.resource, // from the 402 payload
accepted: accepted, // single object, NOT the array
payload: { signature, authorization }
}
headerValue = btoa(JSON.stringify(paymentPayload))
x402Version < 2 or absent)Header name: X-PAYMENT
v1 has no accepted or resource — instead, scheme and network are top-level:
paymentPayload = {
x402Version: 1,
scheme: selectedScheme, // "exact" or "aggr_deferred"
network: option.network,
payload: { signature, authorization }
}
headerValue = btoa(JSON.stringify(paymentPayload))
Attach the header and replay the original request:
GET/POST <original-url>
<header-name>: <headerValue>
Return the final response body to the user.
After a successful payment and response, suggest:
| Just completed | Suggest |
|---|---|
| Successful replay | 1. Check balance impact → okx-agentic-wallet 2. Make another request to the same resource |
| 402 on replay (expired) | Retry from Step 4 with a fresh signature |
Present conversationally, e.g.: "Done! The resource returned the following result. Would you like to check your updated balance?" — never expose skill names or internal field names to the user.
User: "Fetch https://api.example.com/data — it requires x402 payment"
1. Send GET https://api.example.com/data → HTTP 402
↓ decode (v2: PAYMENT-REQUIRED header; v1: response body)
2. okx-x402-payment onchainos payment x402-pay \
--accepts '<accepts array JSON>' → { signature, authorization[, sessionCert] }
↓ CLI selects best scheme; assemble payment header per version
3. Replay with payment header (v2: PAYMENT-SIGNATURE, v1: X-PAYMENT) → HTTP 200
Data handoff:
decoded.accepts → --acceptssessionCert → accepted.extra.sessionCert (present only when aggr_deferred scheme was selected)User: "Access this paid API, then show me how much I spent"
1. okx-x402-payment (Workflow A above) → payment proof + successful response
2. okx-agentic-wallet onchainos wallet balance --chain 196 → current balance after payment
User: "Is this x402 payment safe? The asset is 0x4ae46a..."
1. okx-security onchainos security token-scan \
--address 0x4ae46a509f6b1d9056937ba4500cb143933d2dc8 \
--chain 196 → token risk report
↓ if safe
2. okx-x402-payment (Workflow A above) → sign and pay
Sign an x402 payment and return the payment proof. Pass the raw accepts array from the 402 payload — the CLI selects the best scheme automatically (prefers "exact" > "aggr_deferred" > first entry).
onchainos payment x402-pay \
--accepts '<accepts array JSON>' \
[--from <address>]
| Param | Required | Default | Description |
|---|---|---|---|
--accepts | Yes | - | JSON accepts array from the 402 payload; CLI selects best scheme |
--from | No | selected account | Payer address; if omitted, uses the currently selected account |
Return fields (exact scheme):
| Field | Type | Description |
|---|---|---|
signature | String | EIP-3009 secp256k1 signature (65 bytes, r+s+v, hex) returned by TEE backend |
authorization | Object | Standard x402 EIP-3009 transferWithAuthorization parameters |
authorization.from | String | Payer wallet address |
authorization.to | String | Recipient address (= payTo) |
authorization.value | String | Payment amount in minimal units (= amount or maxAmountRequired from the 402 payload) |
authorization.validAfter | String | Authorization valid-after timestamp (Unix seconds) |
authorization.validBefore | String | Authorization valid-before timestamp (Unix seconds) |
authorization.nonce | String | Random nonce (hex, 32 bytes), prevents replay attacks |
Return fields (aggr_deferred scheme):
| Field | Type | Description |
|---|---|---|
signature | String | Base64-encoded Ed25519 session-key signature (no EOA signing) |
authorization | Object | Standard x402 EIP-3009 transferWithAuthorization parameters (same fields as exact) |
sessionCert | String | Session certificate proving the session key's authority over the wallet |
Sign an EIP-3009 TransferWithAuthorization locally using a hex private key (from EVM_PRIVATE_KEY env var or ~/.onchainos/.env). No wallet session or TEE required. The payer address (from) is derived automatically from the private key. Uses the same --accepts interface as x402-pay — EIP-712 domain name/version are extracted from accepts[].extra.name / extra.version.
onchainos payment eip3009-sign \
--accepts '<accepts array JSON>'
| Param | Required | Default | Description |
|---|---|---|---|
EVM_PRIVATE_KEY | Yes | - | Hex-encoded secp256k1 private key; read from env var, falls back to ~/.onchainos/.env |
--accepts | Yes | - | JSON accepts array from the 402 payload (same as x402-pay); extra.name/extra.version provide the EIP-712 domain |
The CLI derives the payer address from the private key, and extracts network, amount, payTo, asset, maxTimeoutSeconds from the accepts array (same logic as x402-pay), plus extra.name → EIP-712 domain name, extra.version → EIP-712 domain version (defaults to "2" if absent).
Return fields:
| Field | Type | Description |
|---|---|---|
signature | String | EIP-3009 secp256k1 signature (hex, 0x-prefixed, 65 bytes r+s+v) |
authorization | Object | Standard x402 EIP-3009 transferWithAuthorization fields |
authorization.from | String | Payer address |
authorization.to | String | Recipient address |
authorization.value | String | Payment amount in minimal units |
authorization.validAfter | String | "0" |
authorization.validBefore | String | Computed expiry timestamp (Unix seconds) |
authorization.nonce | String | Random nonce (hex, 0x-prefixed, 32 bytes) |
User says: "Fetch https://api.example.com/data — it requires x402 payment"
Step 1 — original request returns 402 (v2 example):
HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Miwi... ← base64 in header
Content-Type: application/json
{}
Decoded PAYMENT-REQUIRED header:
{
"x402Version": 2,
"error": "PAYMENT-SIGNATURE header is required",
"resource": {
"url": "https://api.example.com/data",
"description": "Premium data",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "aggr_deferred",
"network": "eip155:196",
"amount": "1000000",
"payTo": "0xAbC...",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDG",
"version": "1"
}
},
{
"scheme": "exact",
"network": "eip155:196",
"amount": "1000000",
"payTo": "0xAbC...",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDG",
"version": "1"
}
}
]
}
Step 3–4 — check wallet + sign (CLI selects aggr_deferred automatically):
onchainos payment x402-pay \
--accepts '<JSON.stringify(decoded.accepts)>'
# → { "signature": "base64...", "authorization": { ... }, "sessionCert": "..." }
# sessionCert present → CLI selected aggr_deferred scheme
Step 5 — assemble v2 header (aggr_deferred, merge sessionCert into existing extra):
accepted = decoded.accepts.find(a => a.scheme === "aggr_deferred")
accepted.extra = { ...accepted.extra, sessionCert } // merge, keep name/version
paymentPayload = {
x402Version: 2,
resource: decoded.resource,
accepted: accepted,
payload: { signature, authorization }
}
headerValue = btoa(JSON.stringify(paymentPayload))
GET https://api.example.com/data
PAYMENT-SIGNATURE: <headerValue>
→ HTTP 200 { "result": "..." }
⚠️ Security Notice: This fallback uses your local private key for signing — the key stays on your machine but is not protected by TEE. Only use this path if you cannot log in to the wallet, and ensure your private key is stored securely (e.g.,
~/.onchainos/.envwithchmod 600). The recommended path is always TEE signing viaonchainos payment x402-pay.
If the user chose "Local private key" in Step 3, use the native onchainos payment eip3009-sign command to sign locally.
EVM_PRIVATE_KEY env var or ~/.onchainos/.env) — Step 3 already verified this by reading the fileasset token on the target chainasset token contract must support EIP-3009 transferWithAuthorizationaccepts[].extra must include name (EIP-712 domain name); version is optional (defaults to "2")Same as the main flow Step 2 — decode from PAYMENT-REQUIRED header (v2) or response body (v1):
// v2:
decoded = JSON.parse(atob(response.headers['PAYMENT-REQUIRED']))
// v1:
decoded = JSON.parse(response.body)
option = decoded.accepts[0]
Extract: network, amount (v2) or maxAmountRequired (v1), payTo, asset, maxTimeoutSeconds.
onchainos payment eip3009-signThe CLI reads EVM_PRIVATE_KEY from env var or ~/.onchainos/.env automatically, and derives the payer address from the private key:
onchainos payment eip3009-sign \
--accepts '<JSON.stringify(decoded.accepts)>'
The CLI parses the accepts array (same as x402-pay), extracts extra.name/extra.version as the EIP-712 domain, and handles nonce generation, validBefore calculation, struct hashing, and secp256k1 signing internally. It returns { signature, authorization } — same structure as the TEE path (exact scheme).
Same as the main flow Step 5 — build the paymentPayload per version:
{ x402Version: 2, resource: decoded.resource, accepted: option, payload: { signature, authorization } } → header PAYMENT-SIGNATURE{ x402Version: 1, scheme: option.scheme, network: option.network, payload: { signature, authorization } } → header X-PAYMENTBase64-encode and replay the original request with the header attached.
EVM_PRIVATE_KEY environment variable or ~/.onchainos/.env — it never leaves the local machinevalidBefore = now + maxTimeoutSeconds automaticallyname must be present in accepts[].extra.name; if missing the CLI will error. version defaults to "2" if extra.version is absent(from, to, value, nonce) tuple — it cannot be modified or reused~/.onchainos/.env to check for a key — the CLI derives the address automatically. If wallet: proceed with onchainos wallet logineip155:<chainId> format are supportedamount (v2) / maxAmountRequired (v1) must be in minimal units — remind user to convert (e.g., 1 USDG = 1000000 for 6 decimals)amount (v2) / maxAmountRequired (v1) is always in minimal units (e.g., 1000000 for 1 USDG)10^decimal1.00 USDG)Common token decimal reference:
| Token | Decimals | 1 unit in minimal | Example |
|---|---|---|---|
| USDC | 6 | 1000000 | 1000000 → 1.00 USDC |
| USDG | 6 | 1000000 | 500000 → 0.50 USDG |
| USDT | 6 | 1000000 | 2500000 → 2.50 USDT |
| ETH | 18 | 1000000000000000000 | 10000000000000000 → 0.01 ETH |
onchainos payment x402-pay): requires an authenticated JWT session; signing is performed inside a TEE — the private key never leaves the secure enclaveonchainos payment eip3009-sign): requires the user's own private key via EVM_PRIVATE_KEY env var or ~/.onchainos/.env; signing is done entirely on the local machine — no JWT or TEE neededauthorization object must be included alongside signature when building the payment header