Pay With APP (OKX Agent Payments Protocol on X Layer) Pay HTTP 402 challenges issued by OKX's Agent Payments Protocol (APP) running on X Layer (chain 196). APP's Pay Per Use (OKX product name: Instant Payment) is x402-compatible: a payee server returns HTTP 402 with a payment requirement, the payer signs an EIP-3009 off-chain, and OKX's facilitator verifies and settles the transfer on-chain. Settlement is zero-gas to the payer on X Layer. This skill handles the full happy path: 1. Detect a 402 challenge whose network resolves to X Layer (chain 196) 2. Verify the payer wallet has the requested…

\\n'*)\n echo \"bad resource URL: contains shell metacharacter\" >&2; exit 1 ;;\nesac\n\n# Note: $X402_RESOURCE should be set from accepts[selected].resource if the 402 challenge\n# provides one (it can override the original request URL for proxied or redirected\n# resources); fall back to the originally requested URL only if accepts[].resource is absent.\n# If accepts[selected].resource's host differs from the host of the originally-requested\n# URL, surface the mismatch to the user and require explicit confirmation before\n# continuing. A facilitator-mediated redirect is legitimate but should not be silent.\nif [ -n \"${ORIGINAL_REQUEST_URL:-}\" ]; then\n # Strip userinfo (user:pass@) and port (:NNNN) before comparing so that\n # equivalent hosts don't trip the gate.\n strip_host() {\n echo \"$1\" | awk -F/ '{print $3}' | sed -E 's/^[^@]+@//' | sed -E 's/:[0-9]+$//'\n }\n RESOURCE_HOST=$(strip_host \"$X402_RESOURCE\")\n ORIGINAL_HOST=$(strip_host \"$ORIGINAL_REQUEST_URL\")\n [ -n \"$RESOURCE_HOST\" ] && [ -n \"$ORIGINAL_HOST\" ] || {\n echo \"ERROR: could not parse host from URLs (resource=$X402_RESOURCE, original=$ORIGINAL_REQUEST_URL)\" >&2\n exit 1\n }\n if [ \"$RESOURCE_HOST\" != \"$ORIGINAL_HOST\" ]; then\n if [ \"${X402_HOST_MISMATCH_ACK:-}\" != \"yes\" ]; then\n echo \"ERROR: accepts[].resource host ($RESOURCE_HOST) differs from original request host ($ORIGINAL_HOST).\" >&2\n echo \"Surface this via AskUserQuestion. After explicit user confirmation, re-invoke with X402_HOST_MISMATCH_ACK=yes.\" >&2\n exit 1\n fi\n fi\nfi\n\n# 1) Only \"exact\" scheme is supported in v1.0.0\n[ \"$X402_SCHEME\" = \"exact\" ] || { echo \"Unsupported scheme: $X402_SCHEME\" >&2; exit 1; }\n\n# 2) Network must resolve to chain 196\ncase \"$X402_NETWORK\" in\n x-layer|xlayer|\"eip155:196\"|196) X402_CHAIN_ID=196 ;;\n *) echo \"Network is not X Layer. Use pay-with-any-token instead.\" >&2; exit 1 ;;\nesac\n\n# 3) Wallet must hold enough of the requested asset on X Layer\nRPC_URL=\"${X_LAYER_RPC_URL:-https://rpc.xlayer.tech}\"\n\nASSET_BALANCE=$(cast call \"$X402_ASSET\" \\\n \"balanceOf(address)(uint256)\" \"$WALLET_ADDRESS\" --rpc-url \"$RPC_URL\") || {\n echo \"ERROR: balanceOf call failed; check RPC connectivity\" >&2; exit 1;\n}\n[[ \"$ASSET_BALANCE\" =~ ^[0-9]+$ ]] || {\n echo \"ERROR: balanceOf returned non-integer: $ASSET_BALANCE\" >&2; exit 1;\n}\n\nif [ \"$ASSET_BALANCE\" -lt \"$X402_AMOUNT\" ]; then\n X402_DECIMALS=$(get_token_decimals \"$X402_ASSET\" \"$RPC_URL\") || exit 1\n HAVE=$(format_token_amount \"$ASSET_BALANCE\" \"$X402_DECIMALS\")\n NEED=$(format_token_amount \"$X402_AMOUNT\" \"$X402_DECIMALS\")\n echo \"Insufficient asset balance on X Layer. Have $HAVE, need $NEED.\"\n echo \"Run the funding flow (references/funding-x-layer.md), then return here.\"\n exit 1\nfi\n\n# When the funding flow runs, it captures SOURCE_TX_HASH from the Trading\n# API /swap response. See funding-x-layer.md for the capture + validate\n# pattern (jq with `// empty` fallback plus a 0x[a-fA-F0-9]{64} shape\n# check); a literal \"null\" or empty string must fail the funding flow\n# rather than propagating into this script.\n\n# Capture 402 challenge freshness for the sign-time gate (see Step 4).\n# Named CHALLENGE_FETCHED_AT to disambiguate from the Trading API \"quote\"\n# concept used in funding-x-layer.md; here it refers to the 402 body itself.\nCHALLENGE_FETCHED_AT=$(date +%s)\n```\n\n## Step 3: Generate Nonce and Deadline\n\n```bash\nset -euo pipefail\n\nX402_NONCE=\"0x$(openssl rand -hex 32)\" # 32-byte random nonce\n\n# Assert the nonce is the right shape. If openssl is missing or fails,\n# command substitution can yield \"0x\" (empty), which signs against\n# nonce: 0. First time works, second is a replay. Fail loud here.\n[ ${#X402_NONCE} -eq 66 ] || { echo \"openssl missing or failed: nonce length=${#X402_NONCE}\" >&2; exit 1; }\n[[ \"$X402_NONCE\" =~ ^0x[a-fA-F0-9]{64}$ ]] || { echo \"bad nonce shape: $X402_NONCE\" >&2; exit 1; }\n\nX402_VALID_AFTER=0 # immediately valid\n\n# Default the requested timeout to 5 minutes if not provided by the challenge.\nX402_TIMEOUT=\"${X402_TIMEOUT:-300}\"\n\n# `maxTimeoutSeconds` from the challenge body is an upper bound on\n# (validBefore - validAfter). Default the ceiling to the requested\n# timeout if the challenge omits it, then clamp.\nX402_MAX_TIMEOUT=\"${X402_MAX_TIMEOUT:-$X402_TIMEOUT}\"\nif [ \"$X402_TIMEOUT\" -lt \"$X402_MAX_TIMEOUT\" ]; then\n X402_EFFECTIVE_TIMEOUT=\"$X402_TIMEOUT\"\nelse\n X402_EFFECTIVE_TIMEOUT=\"$X402_MAX_TIMEOUT\"\nfi\n\nX402_VALID_BEFORE=$(( $(date +%s) + X402_EFFECTIVE_TIMEOUT ))\n```\n\nThe challenge body's `maxTimeoutSeconds` is an upper bound on\n`validBefore - validAfter`. The clamp above keeps the request inside\nthe bound while leaving the facilitator time to settle.\n\n## Step 3.5: User Confirmation Gate\n\nThis step is mandatory and not optional. Do not auto-submit. Use\n`AskUserQuestion` (or the equivalent confirmation primitive in the host\nagent) to surface a payment summary and obtain explicit yes/no consent\nbefore signing:\n\n- Token: `$X402_TOKEN_NAME` (`$X402_ASSET`) on X Layer (chain 196)\n- Amount: human-readable amount + base units\n- Recipient: `$X402_PAY_TO`\n- Resource: `$X402_RESOURCE`\n- Expiry: `validBefore` (UTC + epoch)\n- Nonce (first 10 chars of `$X402_NONCE`, for traceability)\n\nIf the user declines, abort. If the user does not respond, abort. Do\nnot proceed to Step 4 without an affirmative answer in the transcript.\n\n## Step 4: Sign the EIP-3009 Authorization\n\nThe EIP-712 domain uses the **token contract's own** `name` and\n`version` (taken verbatim from the challenge's `extra` field).\n`verifyingContract` is the token contract itself.\n\n> **Unicode warning.** USDT0's domain `name` is `\"USD₮0\"` with the\n> Unicode trademark sign `₮` (U+20AE), not an ASCII `T`. EIP-712 hashes\n> the domain `name` as byte-exact UTF-8: pass it through unchanged from\n> `extra.name`. Do not normalize. Do not substitute ASCII `T`. Any\n> mutation produces a signature the facilitator will reject.\n\nFreshness gate (refuse-to-sign-when-stale): a signed-but-stale\nauthorization burns a nonce on the facilitator side; refusing to sign\npreserves the nonce. Run this gate **before** invoking `signTypedData`,\nnot after. The challenge body's prices and `accepts[]` parameters are\nshort-lived (the SKILL doc states roughly 60 seconds); 45 is a safe\nceiling that leaves margin:\n\n```bash\nset -euo pipefail\n\nif [ $(($(date +%s) - CHALLENGE_FETCHED_AT)) -ge 45 ]; then\n echo \"402 challenge is older than 45 seconds; refetch before signing.\" >&2\n exit 1\nfi\n```\n\nSign with viem:\n\n```typescript\nimport { privateKeyToAccount } from 'viem/accounts';\n\n// Validate every input before constructing the typed-data payload.\n// `process.env.FOO!` casts hide undefined and empty-string bugs; an\n// empty domain or message field produces a valid-looking signature\n// the facilitator will reject, burning a fresh nonce.\nfunction requireEnv(key: string): string {\n const v = process.env[key];\n if (!v || !v.trim()) {\n throw new Error(\n `${key} is unset or empty. Re-parse the corresponding field from the 402 challenge.`\n );\n }\n return v;\n}\n\n// Shape-validating wrappers: catch the case where a string is non-empty but\n// the wrong shape (e.g. truncated address, scientific-notation amount). An\n// empty-only check still produces a valid-looking signature the facilitator\n// will reject, burning a fresh nonce.\nfunction requireAddress(key: string): `0x${string}` {\n const v = requireEnv(key);\n if (!/^0x[a-fA-F0-9]{40}$/.test(v)) {\n throw new Error(`${key} is not a valid 0x address: ${v}`);\n }\n return v as `0x${string}`;\n}\nfunction requireUint(key: string): bigint {\n const v = requireEnv(key);\n if (!/^[0-9]+$/.test(v)) {\n throw new Error(`${key} is not a non-negative integer: ${v}`);\n }\n return BigInt(v);\n}\nfunction requireBytes32(key: string): `0x${string}` {\n const v = requireEnv(key);\n if (!/^0x[a-fA-F0-9]{64}$/.test(v)) {\n throw new Error(`${key} is not a 0x bytes32: ${v}`);\n }\n return v as `0x${string}`;\n}\n// Free-text fields (extra.name, extra.version) feed the EIP-712 domain\n// bit-exact and may also be surfaced to the user. Per the SKILL.md\n// Input Validation Rules, reject the whole challenge rather than\n// mutating the value if it contains shell metacharacters.\nfunction requireSafeText(key: string): string {\n const v = requireEnv(key);\n if (/[;|& pay-with-app — Skillopedia ()\u003c>\\\\'\"\\n]/.test(v)) {\n throw new Error(\n `${key} contains shell metacharacters; reject the whole challenge per skill policy.`\n );\n }\n return v;\n}\n\nconst privateKey = requireEnv('PRIVATE_KEY');\nconst tokenName = requireSafeText('X402_TOKEN_NAME'); // from extra.name (e.g. \"USD₮0\")\nconst tokenVersion = requireSafeText('X402_TOKEN_VERSION'); // from extra.version (e.g. \"1\")\nconst walletAddress = requireAddress('WALLET_ADDRESS');\nconst x402Asset = requireAddress('X402_ASSET');\nconst x402PayTo = requireAddress('X402_PAY_TO');\nconst x402Amount = requireUint('X402_AMOUNT');\nconst x402ValidAfter = requireUint('X402_VALID_AFTER');\nconst x402ValidBefore = requireUint('X402_VALID_BEFORE');\nconst x402Nonce = requireBytes32('X402_NONCE');\n\nconst account = privateKeyToAccount(privateKey as `0x${string}`);\n\nconst domain = {\n name: tokenName,\n version: tokenVersion,\n chainId: 196,\n verifyingContract: x402Asset,\n};\n\n// REQUIRED: AskUserQuestion confirmation already happened in Step 3.5.\n// Do not reach this line without an affirmative user answer.\nconst signature = await account.signTypedData({\n domain,\n types: {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n },\n primaryType: 'TransferWithAuthorization',\n message: {\n from: walletAddress,\n to: x402PayTo,\n value: x402Amount,\n validAfter: x402ValidAfter,\n validBefore: x402ValidBefore,\n nonce: x402Nonce,\n },\n});\nprocess.env.X402_SIGNATURE = signature;\n```\n\nTo execute the snippet above, write it to a `.mjs` file and run Node\nfrom the directory Step 0 resolved viem in (`$SIGNER_CWD`). Capture\nstdout into `X402_SIGNATURE`, then verify the shape before continuing,\nbecause `set -euo pipefail` does not fire on a failed command\nsubstitution unless `inherit_errexit` is set (which is bash 4.4+ only,\nnot available on default macOS bash 3.2):\n\n```bash\nset -euo pipefail\n\nSIGNER_SCRIPT=$(mktemp -t x402-signer-XXXXXX).mjs\ntrap 'rm -f \"$SIGNER_SCRIPT\"' EXIT\n\ncat > \"$SIGNER_SCRIPT\" \u003c\u003c'JS'\n// Paste the signing snippet above here, with `process.stdout.write(signature)`\n// at the end instead of assigning to process.env.\nJS\n\nSIG_FILE=$(mktemp)\n( cd \"$SIGNER_CWD\" && node \"$SIGNER_SCRIPT\" > \"$SIG_FILE\" )\nX402_SIGNATURE=$(cat \"$SIG_FILE\")\nrm -f \"$SIG_FILE\"\n\nif ! [[ \"$X402_SIGNATURE\" =~ ^0x[0-9a-fA-F]{130}$ ]]; then\n echo \"ERROR: signer returned bad signature shape\" >&2\n exit 1\nfi\nexport X402_SIGNATURE\n```\n\n> **Domain warning.** `verifyingContract` is the **token contract**\n> (`X402_ASSET`), not a separate verifier. Use `name` and `version`\n> from `extra`. Do not assume defaults. An incorrect domain produces a\n> signature the facilitator will reject with another 402.\n\n## Step 5: Construct the X-PAYMENT Payload\n\nThe wire shape MUST match the x402 v1 spec §5.2 `PaymentPayload`\nschema exactly:\n\n```text\n{ x402Version, scheme, network, payload: { signature, authorization } }\n```\n\nPer spec §5.2.1, `value`, `validAfter`, and `validBefore` are all\n**string-typed** (uint256-as-decimal-string for `value`,\nUnix-timestamp-as-decimal-string for the timestamps). Use `--arg`, not\n`--argjson`, so jq emits JSON strings rather than numbers.\n\n`x402Version` is an integer per spec (not a string-typed field like the\nEIP-3009 timestamps): `--argjson x402Version 1` emits a JSON number,\nwhich is correct. If a future spec revision retypes this field as a\nstring, switch to `--arg` instead.\n\n```bash\nset -euo pipefail\n\nX402_PAYMENT_JSON=$(jq -n \\\n --argjson x402Version 1 \\\n --arg scheme \"$X402_SCHEME\" \\\n --arg network \"$X402_NETWORK\" \\\n --arg from \"$WALLET_ADDRESS\" \\\n --arg to \"$X402_PAY_TO\" \\\n --arg value \"$X402_AMOUNT\" \\\n --arg validAfter \"$X402_VALID_AFTER\" \\\n --arg validBefore \"$X402_VALID_BEFORE\" \\\n --arg nonce \"$X402_NONCE\" \\\n --arg sig \"$X402_SIGNATURE\" \\\n '{\n x402Version: $x402Version,\n scheme: $scheme,\n network: $network,\n payload: {\n signature: $sig,\n authorization: {\n from: $from,\n to: $to,\n value: $value,\n validAfter: $validAfter,\n validBefore: $validBefore,\n nonce: $nonce\n }\n }\n }')\n\n# Base64-encode and strip whitespace (header spec requires no newlines)\nX402_PAYMENT=$(echo \"$X402_PAYMENT_JSON\" | base64 | tr -d '[:space:]')\n```\n\n`value`, `validAfter`, and `validBefore` MUST be strings (`--arg`, not\n`--argjson`): uint256 amounts exceed JSON's safe integer range, and the\nspec's PaymentPayload schema types all three as `string`. Top-level\n`chainId` and `asset` are intentionally omitted: `network` already\nencodes the chain, and the asset is implicit in the requirement\nmatching.\n\n## Step 6: Retry the Original Request\n\nThe freshness gate has already run in Step 4 (refuse-to-sign-when-stale).\nBy the time control reaches this step, the signature is fresh and the\nnonce is committed.\n\n```bash\nset -euo pipefail\n\n# Capture status and headers explicitly. `head -1 | grep -o '[0-9]\\{3\\}'`\n# is fragile (HTTP/2, 100-continue interim responses), so use curl's\n# own status-code writer.\nRETRY_HEADERS=$(mktemp)\nRETRY_BODY_FILE=$(mktemp)\ntrap 'rm -f \"$RETRY_HEADERS\" \"$RETRY_BODY_FILE\"' EXIT\n\nRETRY_STATUS=$(curl -s -o \"$RETRY_BODY_FILE\" -D \"$RETRY_HEADERS\" \\\n -w '%{http_code}' \\\n \"$X402_RESOURCE\" \\\n -H \"X-PAYMENT: $X402_PAYMENT\" \\\n -H \"Content-Type: application/json\")\n\nRETRY_BODY=$(cat \"$RETRY_BODY_FILE\")\n# Use awk for header parsing rather than `cut -d' ' -f2-`: header names\n# may have variable whitespace after the colon, and `cut` mishandles\n# tabs and folded continuations.\nX402_PAYMENT_RESPONSE=$(awk 'BEGIN{IGNORECASE=1} /^x-payment-response:/ { sub(/^[^:]+:[ \\t]*/, \"\"); print }' \\\n \"$RETRY_HEADERS\" | tr -d '\\r\\n')\n\necho \"HTTP status: $RETRY_STATUS\"\n```\n\n### Retry policy (two attempts maximum)\n\nThe 402 path is bounded: at most **one** retry after the initial 402, and\nonly against a freshly re-parsed challenge. The retry budget is tracked\nat the agent level, not via a bash counter. Bash variable state does\nnot survive across separate Bash tool invocations, so a counter would\nsilently reset and permit unbounded retries.\n\n**Agent-level retry instruction:** If the retry returns 402, you may\nattempt **one** more retry, but only after re-deriving `X402_NONCE` and\n`X402_VALID_BEFORE` from a freshly fetched 402 challenge. Do not retry a\nthird time. After the second 402, surface the rejection to the user with\nthe exact body OKX returned and stop.\n\nTo enforce the cap across separate Bash tool invocations (where shell\nvariable state does not persist), use a tmpfile-based stamp and pass\n`X402_ATTEMPT_FILE` through to subsequent invocations so the budget is\nshared:\n\n```bash\n# IMPORTANT: $ would evaluate to a fresh PID on every Bash tool invocation,\n# defeating the cap. The agent MUST export X402_ATTEMPT_FILE explicitly before\n# the first invocation of this block (e.g. /tmp/x402-attempt-${WALLET_ADDRESS}-${X402_NONCE_PREFIX})\n# and reuse the SAME path across retries.\n: \"${X402_ATTEMPT_FILE:?missing: agent must set a stable path so the retry budget persists across Bash invocations}\"\n[ -e \"$X402_ATTEMPT_FILE\" ] && [ ! -r \"$X402_ATTEMPT_FILE\" ] && {\n echo \"ERROR: X402_ATTEMPT_FILE exists but is unreadable: $X402_ATTEMPT_FILE\" >&2\n exit 1\n}\nattempts=$(wc -l \u003c \"$X402_ATTEMPT_FILE\" 2>/dev/null || echo 0)\n[ \"$attempts\" -lt 2 ] || { echo \"Retry budget exhausted (max 2 attempts).\" >&2; exit 1; }\ndate +%s >> \"$X402_ATTEMPT_FILE\"\n```\n\n**Variable lifecycle on retry or re-confirmation.** On any retry, you\nMUST regenerate the following from a freshly fetched 402 challenge body\nbefore re-signing:\n\n- `X402_NONCE` (Step 3): never reuse a prior nonce; the facilitator\n rejects replays and you would burn the new attempt for nothing.\n- `X402_VALID_BEFORE` (Step 3): recompute from the fresh `date +%s` so\n the freshness gate in Step 4 does not trip on an old deadline.\n- `CHALLENGE_FETCHED_AT` (Step 2): re-capture from `date +%s` at the\n moment the new 402 body is read; this is the timestamp the Step 4\n gate compares against.\n\nThe user-confirmation gate (Step 3.5) MUST also re-prompt; do not silently\nre-sign on prior consent.\n\n## Step 7: Interpret the Response\n\n| Status | Meaning | Action |\n| ------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 200 | Payment accepted, resource delivered | If `X402_PAYMENT_RESPONSE` is non-empty, decode it: `echo \"$X402_PAYMENT_RESPONSE\" \\| base64 --decode \\| jq .` and surface the receipt. If empty, report \"Payment accepted but no receipt header returned. The resource was delivered.\" |\n| 402 | Payment rejected | Most common causes: wrong domain `name` / `version`, expired `validBefore`, reused `nonce`, amount mismatch. Re-derive from the **fresh** challenge and try once more (max two attempts total) |\n| 400 | Malformed payload | Verify JSON structure and base64 encoding (no whitespace), confirm `x402Version: 1` and `payload.{signature, authorization}` shape |\n| 5xx | Facilitator or origin error | Surface raw body to the user. Do not auto-retry |\n\nDo not retry indefinitely on 402. Two attempts maximum, then surface\nthe rejection details to the user with the exact message OKX returned.\nEmpty `X402_PAYMENT_RESPONSE` is not an error: skip the decode step\nand report success without a receipt rather than feeding empty input\nto `base64 --decode`.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":25654,"content_sha256":"017e8e2aaf88375d59d6173c9690ff37bdfbbb85bf4da96cd80e842935e9be8c"},{"filename":"references/funding-x-layer.md","content":"# Funding USDT0 on X Layer via the Uniswap Trading API\n\nWhen the wallet lacks the asset required by an APP Pay Per Use 402\nchallenge, acquire it on X Layer (chain 196) using the Uniswap Trading\nAPI. The API supports both same-chain swaps on X Layer and cross-chain\nrouting into X Layer (powered by Across).\n\n## Table of Contents\n\n- [Decide the funding target](#decide-the-funding-target)\n- [Pick the source chain and token](#pick-the-source-chain-and-token)\n- [Phase A: Same-chain swap on X Layer](#phase-a-same-chain-swap-on-x-layer)\n- [Phase B: Cross-chain bridge into X Layer](#phase-b-cross-chain-bridge-into-x-layer)\n- [Verify the destination balance](#verify-the-destination-balance)\n\n## Decide the Funding Target\n\n| Asset on X Layer | Recommended? | Notes |\n| --------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| USDT0 (`0x779Ded0c…`) | ✅ default | Deepest Uniswap v3 liquidity (USDT0/USDG and USDT0/WOKB pools; additional pools at other fee tiers may be deployed, and the Trading API will pick the optimal route across all available pools). Use this if the 402 challenge accepts USDT0. |\n| USDG (`0x4ae46a50…`) | ✅ supported | Reachable via direct Trading API quote, or one-hop USDT0 to USDG. |\n| USDC (`0x74b7F163…`) | ❌ not via Uniswap on X Layer | Trading API does not consistently return routes for USDC swaps on X Layer despite pools at 0.05% and 0.3% existing: TVL is too thin for reliable execution. If the merchant requires USDC, bridge USDC directly from a chain where it is liquid (Base, Arbitrum, Mainnet) using the Trading API rather than attempting a same-chain swap on X Layer. |\n\n> **Default funding target = USDT0.** Override only when the 402\n> challenge demands a different specific asset and that asset is funded\n> by an entry above with ✅.\n\n## Pick the Source Chain and Token\n\nInspect the user's ERC-20 holdings across supported source chains and\nprefer cheapest gas + deepest liquidity to the destination.\n\n```bash\nset -euo pipefail\n\n# USDC on Base (cheapest bridge gas)\ncast call 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \\\n \"balanceOf(address)(uint256)\" \"$WALLET_ADDRESS\" \\\n --rpc-url https://mainnet.base.org\n\n# USDC on Ethereum\ncast call 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \\\n \"balanceOf(address)(uint256)\" \"$WALLET_ADDRESS\" \\\n --rpc-url https://eth.llamarpc.com\n\n# Native ETH on Base / Ethereum (use zero address for the swap input)\ncast balance \"$WALLET_ADDRESS\" --rpc-url https://mainnet.base.org\n```\n\nPath priority for landing **USDT0** on X Layer:\n\n1. Source already holds USDT0 on X Layer, skip funding entirely.\n2. Source holds USDG on X Layer, same-chain swap USDG to USDT0 (Phase\n A, single hop).\n3. Source holds USDT0 on a different chain, Phase B cross-chain\n (likely cheapest).\n4. Wallet has a stablecoin (USDC) on Base / Arbitrum / Mainnet,\n cross-chain route to USDT0 on X Layer (Phase B).\n5. Wallet has native ETH on Base / Mainnet, cross-chain route from\n native (zero address `0x0000000000000000000000000000000000000000`)\n to USDT0 on X Layer (Phase B handles swap + bridge in one quote).\n6. Wallet only holds non-USDT0 tokens on X Layer, same-chain swap\n (Phase A).\n\n## Phase A: Same-Chain Swap on X Layer\n\nUse this when the wallet already holds a token on X Layer (e.g. WOKB or\nUSDG) and needs to convert it to the asset required by the 402\nchallenge. Skip if the wallet has no relevant tokens on X Layer.\n\n> **Confirmation gate** before approval and before broadcast.\n>\n> **Pre-flight: OKB balance check.** Same-chain X Layer swap requires\n> OKB for gas. Confirm the wallet has OKB before proceeding:\n>\n> ```bash\n> set -euo pipefail\n> OKB_BAL=$(cast balance \"$WALLET_ADDRESS\" \\\n> --rpc-url \"${X_LAYER_RPC_URL:-https://rpc.xlayer.tech}\")\n> # Non-zero OKB sanity check. This does not guarantee enough gas to broadcast\n> # a swap; it only catches the common \"wallet has literally zero OKB\" case.\n> # Replace with a real threshold (e.g. 0.001 OKB) if you want to gate on\n> # usable gas.\n> [ \"$OKB_BAL\" != \"0\" ] || {\n> echo \"Same-chain X Layer swap requires OKB for gas. Wallet has 0 OKB. Either acquire OKB first, or route entirely cross-chain (Phase B) which only needs source-chain gas.\" >&2\n> exit 1\n> }\n> ```\n\n```bash\nset -euo pipefail\n\nQUOTE_FETCHED_AT=$(date +%s)\nQUOTE=$(curl -fsS -X POST https://trade-api.gateway.uniswap.org/v1/quote \\\n -H \"Content-Type: application/json\" \\\n -H \"x-api-key: $UNISWAP_API_KEY\" \\\n -H \"x-universal-router-version: 2.1.1\" \\\n -d \"$(jq -n \\\n --arg type \"EXACT_OUTPUT\" \\\n --argjson tokenInChainId 196 \\\n --argjson tokenOutChainId 196 \\\n --arg tokenIn \"$SOURCE_TOKEN_XLAYER\" \\\n --arg tokenOut \"$X402_ASSET\" \\\n --arg amount \"$X402_AMOUNT\" \\\n --arg swapper \"$WALLET_ADDRESS\" \\\n '{\n type: $type,\n tokenInChainId: $tokenInChainId,\n tokenOutChainId: $tokenOutChainId,\n tokenIn: $tokenIn,\n tokenOut: $tokenOut,\n amount: $amount,\n swapper: $swapper,\n urgency: \"normal\"\n }')\") || { echo \"Trading API quote failed\" >&2; exit 1; }\n```\n\nThen `check_approval` (only if `tokenIn` is not native), build the\npermit signature when required, and broadcast via `/swap`. Detailed\n`check_approval` + permit + `/swap` flow is identical to the\n[`pay-with-any-token`](../../pay-with-any-token/references/trading-api-flows.md)\nflow. See that reference and substitute the X Layer chain ID and\naddresses.\n\nBefore broadcasting via `/swap`, gate on quote freshness:\n\n```bash\nset -euo pipefail\n\nELAPSED=$(($(date +%s) - QUOTE_FETCHED_AT))\n[ \"$ELAPSED\" -lt 45 ] || {\n echo \"Quote is $ELAPSED seconds old; refetch before broadcasting.\" >&2\n exit 1\n}\n```\n\n## Phase B: Cross-Chain Bridge into X Layer\n\n> **Important: Across coverage gap (verified 2026-04-27).** The Uniswap\n> Trading API uses Across Protocol for cross-chain routing. **Across\n> does not currently include X Layer (chain 196) in its destination\n> list.** Live `/quote` calls with `tokenOutChainId=196` and a different\n> `tokenInChainId` return `ResourceNotFound: No quotes available` for\n> every source chain we tested (Ethereum, Base, Arbitrum, native ETH).\n> Same-chain X Layer swaps (Phase A) work normally.\n>\n> **What this means for the agent.** Until Across adds X Layer, the\n> Trading API cannot bridge tokens onto X Layer in a single quote. If\n> the user holds funds on a chain other than X Layer, the cross-chain\n> leg must be done through a bridge service that supports X Layer as a\n> destination (the user must run that step outside this skill, then\n> re-invoke for the same-chain swap + 402 settlement).\n>\n> **TODO (research before mid-May launch follow-up):** confirm and\n> document the recommended X-Layer-supporting bridge for Uniswap+OKX\n> co-marketing alignment. Until then, do not prescribe a specific bridge\n> service in user-facing responses; surface the limitation honestly and\n> let the user choose.\n\n### Source-chain shortfall preflight (REQUIRED before quoting)\n\nBefore calling `/quote` or recommending a bridge, **verify the user's\nsource-chain wallet actually has enough of `SOURCE_TOKEN` to cover the\nrequired output amount plus fees**. The most common funding failure is\nnot the route, it is the user's source balance: e.g. user has 5 USDC\non Base but the 402 demands 100 USDT0 on X Layer. Recommending \"bridge\n100.5 USDC from Base\" in that situation is wrong; the user does not\nhave 100.5 USDC to bridge.\n\n```bash\nset -euo pipefail\n\n# X402_AMOUNT is in base units of the X Layer DESTINATION asset (e.g.\n# USDT0 6 decimals). For a same-decimal source token (USDC also 6), the\n# minimum source amount needed is X402_AMOUNT plus buffer. For a\n# different-decimal source token, scale appropriately and consult an\n# oracle or quote for an estimate. The check below uses the same-decimal\n# stablecoin path (USDC -> USDT0, USDG -> USDT0, etc.). For different\n# decimals or non-stable source tokens, fetch a price quote first and\n# use the input-amount it returns to gate this check.\nSOURCE_REQUIRED_BASE_UNITS=$(python3 -c \"print(($X402_AMOUNT * 1005) // 1000)\")\n\nSOURCE_BALANCE=$(cast call \"$SOURCE_TOKEN_ADDRESS\" \\\n \"balanceOf(address)(uint256)\" \"$WALLET_ADDRESS\" \\\n --rpc-url \"$SOURCE_CHAIN_RPC_URL\")\n\n# Strip cast's \"(uint256)\" suffix if present and compare via python (uint256-safe).\nSOURCE_BALANCE_RAW=$(echo \"$SOURCE_BALANCE\" | awk '{print $1}')\nif ! python3 -c \"import sys; sys.exit(0 if int('$SOURCE_BALANCE_RAW') >= int('$SOURCE_REQUIRED_BASE_UNITS') else 1)\"; then\n echo \"ERROR: source-chain shortfall on $SOURCE_CHAIN_NAME.\" >&2\n echo \" needed (base units): $SOURCE_REQUIRED_BASE_UNITS\" >&2\n echo \" have (base units): $SOURCE_BALANCE_RAW\" >&2\n echo \"Cannot fund the 402 from this source. Ask the user for an\" >&2\n echo \"alternative source chain, a smaller payment amount, or to\" >&2\n echo \"top up the source wallet first.\" >&2\n exit 1\nfi\n```\n\nRefusing here is the correct behavior: a bridge instruction the user\ncannot execute is worse than no instruction. If multiple source chains\nare available and one has the funds, suggest that one. If none do,\nsurface the shortfall in plain language and stop, do not auto-pivot\ninto a different funding plan without re-confirming with the user via\n`AskUserQuestion`.\n\n### Quote and bridge\n\nThe block below describes the design intent: a single Trading API quote\nthat handles swap + bridge into X Layer. It is preserved so the skill\nworks without code changes the day Across adds X Layer. Today, expect\nthe quote call to return `ResourceNotFound`. When that happens, fall\nthrough to the user-handoff path documented at the end of this section.\n\nApply a **0.5% buffer** to compute `X402_AMOUNT_WITH_BUFFER` from\n`X402_AMOUNT` to absorb bridge fees. If the shortfall is \u003c $5 worth,\ntop up to $5 to amortize source chain gas.\n\n```bash\nset -euo pipefail\n\n# Apply 0.5% buffer (uint256-safe integer math via python)\nX402_AMOUNT_WITH_BUFFER=$(python3 -c \"print(($X402_AMOUNT * 1005) // 1000)\")\n[[ \"$X402_AMOUNT_WITH_BUFFER\" =~ ^[0-9]+$ ]] || { echo \"buffer math failed\" >&2; exit 1; }\n\nQUOTE_FETCHED_AT=$(date +%s)\n\n# Capture HTTP status and body separately so we can distinguish:\n# (a) HTTP 200 success -> proceed with the original Phase B flow\n# (b) errorCode=ResourceNotFound (Across coverage gap) -> deferred-bridge handoff\n# (c) network errors / 5xx -> surface as transient, advise retry\n#\n# IMPORTANT: do NOT use `curl -f` here. Under `set -e` a non-zero curl\n# exit would terminate the script before we read $? into QUOTE_HTTP_STATUS,\n# making the deferred-bridge branch unreachable on the exact failure\n# path it is designed to handle. We capture status with `-w` instead.\nQUOTE_BODY_FILE=$(mktemp)\ntrap 'rm -f \"$QUOTE_BODY_FILE\"' EXIT\n\nQUOTE_HTTP_STATUS=$(curl -sS -X POST https://trade-api.gateway.uniswap.org/v1/quote \\\n -o \"$QUOTE_BODY_FILE\" \\\n -w '%{http_code}' \\\n -H \"Content-Type: application/json\" \\\n -H \"x-api-key: $UNISWAP_API_KEY\" \\\n -H \"x-universal-router-version: 2.1.1\" \\\n -d \"$(jq -n \\\n --arg type \"EXACT_OUTPUT\" \\\n --argjson tokenInChainId \"$SOURCE_CHAIN_ID\" \\\n --argjson tokenOutChainId 196 \\\n --arg tokenIn \"$SOURCE_TOKEN\" \\\n --arg tokenOut \"$X402_ASSET\" \\\n --arg amount \"$X402_AMOUNT_WITH_BUFFER\" \\\n --arg swapper \"$WALLET_ADDRESS\" \\\n '{\n type: $type,\n tokenInChainId: $tokenInChainId,\n tokenOutChainId: $tokenOutChainId,\n tokenIn: $tokenIn,\n tokenOut: $tokenOut,\n amount: $amount,\n swapper: $swapper,\n urgency: \"normal\"\n }')\") || {\n # curl itself failed (DNS, TLS, network unreachable, etc.) -> case (c)\n echo \"ERROR: Trading API call failed at the network layer (curl exit \\$? = $?).\" >&2\n echo \"This is most likely a transient connectivity issue (DNS, TLS, network).\" >&2\n echo \"Advise the user to retry; do NOT route them to an external bridge for this case.\" >&2\n exit 1\n}\n\nQUOTE=$(cat \"$QUOTE_BODY_FILE\")\nQUOTE_ERROR_CODE=$(echo \"$QUOTE\" | jq -r '.errorCode // empty' 2>/dev/null || echo \"\")\n```\n\nBranch on the result:\n\n```bash\nset -euo pipefail\n\nif [ \"$QUOTE_HTTP_STATUS\" = \"200\" ] && [ -z \"$QUOTE_ERROR_CODE\" ]; then\n : # success -> continue with permitData + /swap on the source chain\nelif [ \"$QUOTE_ERROR_CODE\" = \"ResourceNotFound\" ]; then\n : # case (b): the deferred-bridge handoff below\nelif [ \"$QUOTE_HTTP_STATUS\" -ge 500 ] 2>/dev/null; then\n echo \"ERROR: Trading API returned HTTP $QUOTE_HTTP_STATUS (server error).\" >&2\n echo \"This is most likely transient. Advise the user to retry shortly.\" >&2\n exit 1\nelse\n echo \"ERROR: Trading API returned HTTP $QUOTE_HTTP_STATUS, errorCode=$QUOTE_ERROR_CODE.\" >&2\n echo \"Body: $QUOTE\" >&2\n echo \"Surface raw body to the user; do not route to an external bridge automatically.\" >&2\n exit 1\nfi\n```\n\nIf `errorCode` is `ResourceNotFound`, **the Trading API cannot currently\ndeliver to X Layer cross-chain.** This is the expected state today\n(Across does not list X Layer as a destination). Surface a clear\nmessage to the user via `AskUserQuestion`:\n\n> \"The Uniswap Trading API does not currently support X Layer as a\n> cross-chain destination via Across Protocol (verified\n> 2026-04-27). To pay this APP merchant, please bridge USDT0 (or\n> another stablecoin that lands as USDT0 on X Layer) to your wallet\n> using a bridge service that supports X Layer destinations. Once\n> the funds arrive on X Layer, re-invoke this skill and we will\n> handle the same-chain swap (if needed) and the 402 settlement.\"\n\nDo NOT recommend a specific bridge product to the user in this skill\nversion; the v1.0.0 stance is \"any bridge that supports X Layer is\nfine; pick what you trust.\" A future skill version may add a\nco-marketing-aligned bridge recommendation once verified.\n\nIf the quote call DOES succeed (i.e. Across has shipped X Layer\nsupport since this doc was written), continue with the original flow:\nquote response contains `permitData` (sign with EIP-712), the `/swap`\nendpoint returns the calldata to broadcast on the source chain, and\nAcross handles the X Layer arrival.\n\nBefore broadcasting via `/swap`, gate on quote freshness:\n\n```bash\nset -euo pipefail\n\nELAPSED=$(($(date +%s) - QUOTE_FETCHED_AT))\n[ \"$ELAPSED\" -lt 45 ] || {\n echo \"Quote is $ELAPSED seconds old; refetch before broadcasting.\" >&2\n exit 1\n}\n```\n\nWhen you broadcast the source-chain transaction, capture the resulting\nhash into `SOURCE_TX_HASH` (used in the bridge timeout message below):\n\n```bash\nSOURCE_TX_HASH=$(echo \"$SWAP_RESPONSE\" | jq -r '.transactionHash // empty')\n[[ \"$SOURCE_TX_HASH\" =~ ^0x[a-fA-F0-9]{64}$ ]] || {\n echo \"no tx hash from /swap response\" >&2\n exit 1\n}\n```\n\n> **Bridge recipient.** The Trading API delivers funds to the same\n> `swapper` address on chain 196. If the user wants the funds at a\n> different X Layer address (e.g. an OKX Agentic Wallet they custody\n> separately), an extra transfer transaction on X Layer is required\n> after the bridge confirms.\n>\n> **Quotes expire in ~60 seconds.** Re-fetch if any delay before\n> broadcast (the freshness gate above enforces a 45s ceiling).\n>\n> **Retry hygiene.** On any retry of the quote-then-broadcast cycle,\n> re-derive both `QUOTE_FETCHED_AT` (the freshness timestamp) and\n> `X402_AMOUNT_WITH_BUFFER` (the buffered output amount) from the new\n> quote. Reusing stale values from an earlier attempt will either trip\n> the freshness gate or quote against an outdated buffer.\n\n## Verify the Destination Balance\n\nAfter the bridge or same-chain swap completes, poll for the asset\narrival on X Layer before returning to the EIP-3009 signing step. The\nloop tolerates transient RPC failures and validates that the returned\nbalance is a non-negative integer.\n\nIf after 10 minutes the wallet still has insufficient `$X402_ASSET`,\nthe funds may have arrived at a different token address (rare for\ncurrent Across paths to X Layer) or the bridge may have failed.\nSurface the ambiguity to the user with the source-chain tx hash and\nthe Across explorer link, and ask them to verify on-chain. v1.0.0\ndoes not auto-detect alternate-token arrival on X Layer.\n\n```bash\nset -euo pipefail\n\n# Assert prerequisites are set. SOURCE_TX_HASH must have been captured\n# from the /swap response before entering the polling loop.\n: \"${SOURCE_TX_HASH:?missing, capture from /swap response before polling}\"\n: \"${X402_ASSET:?missing}\"\n: \"${X402_AMOUNT:?missing}\"\n: \"${WALLET_ADDRESS:?missing}\"\n\n# Track successful RPC reads so we can distinguish \"20 RPC failures\" from\n# \"20 successful reads, all under target\" at the end.\nRPC_SUCCESS_COUNT=0\n\nfor i in {1..20}; do\n XLAYER_BAL=$(cast call \"$X402_ASSET\" \\\n \"balanceOf(address)(uint256)\" \"$WALLET_ADDRESS\" \\\n --rpc-url \"${X_LAYER_RPC_URL:-https://rpc.xlayer.tech}\") || {\n echo \"RPC failure on attempt $i, retrying...\" >&2\n sleep 5\n continue\n }\n [[ \"$XLAYER_BAL\" =~ ^[0-9]+$ ]] || {\n echo \"Non-integer balance: $XLAYER_BAL\" >&2\n sleep 5\n continue\n }\n RPC_SUCCESS_COUNT=$((RPC_SUCCESS_COUNT + 1))\n if [ \"$XLAYER_BAL\" -ge \"$X402_AMOUNT\" ]; then\n echo \"Funded. Balance: $XLAYER_BAL\"\n break\n fi\n\n echo \"Waiting for arrival... attempt $i/20 (balance: $XLAYER_BAL base units)\"\n sleep 30\ndone\n\n# If every attempt was an RPC failure, surface that distinctly before the\n# generic \"no usable balance\" check below.\n[ \"$RPC_SUCCESS_COUNT\" -gt 0 ] || {\n echo \"ERROR: all 20 attempts were RPC failures; bridge state unknown.\" >&2\n echo \"Source tx: $SOURCE_TX_HASH. Check https://app.across.to/transactions before re-submitting.\" >&2\n exit 1\n}\n\n# Assert we have a usable balance reading. No `:-0` defaults here, those\n# would defeat `set -u` and silently coerce a missing read into \"below\n# target\".\n[[ -n \"${XLAYER_BAL:-}\" && \"$XLAYER_BAL\" =~ ^[0-9]+$ ]] || {\n echo \"ERROR: bridge polling completed without a successful RPC read.\" >&2\n echo \"20 RPC failures or non-integer responses; cannot determine arrival state.\" >&2\n echo \"Source tx: $SOURCE_TX_HASH. Check https://app.across.to/transactions before re-submitting.\" >&2\n exit 1\n}\n\n[ \"$XLAYER_BAL\" -ge \"$X402_AMOUNT\" ] || {\n echo \"Bridge not confirmed after 10 minutes. Wallet still holds $XLAYER_BAL of $X402_ASSET on X Layer (need $X402_AMOUNT).\" >&2\n echo \"Source tx: $SOURCE_TX_HASH.\" >&2\n echo \"The funds may have arrived at a different token address (rare for current Across paths to X Layer) or the bridge may have failed.\" >&2\n echo \"Verify on-chain via https://app.across.to/transactions and https://www.oklink.com/x-layer/address/$WALLET_ADDRESS before re-submitting.\" >&2\n exit 1\n}\n```\n\nOnce funded, return to\n[`app-x402-flow.md`](app-x402-flow.md) to sign the EIP-3009\nauthorization and retry the original request.\n\n> **Retry target URL.** When the funding flow ends and we return to\n> the EIP-3009 signing in `app-x402-flow.md`, the URL to retry is\n> `accepts[].resource` if present in the original 402 challenge,\n> otherwise the original request URL.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":20125,"content_sha256":"db6b66edb8592dcd03812aee63df634814cbb18ca21eff5687e1b298c234b5ed"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Pay With APP (OKX Agent Payments Protocol on X Layer)","type":"text"}]},{"type":"paragraph","content":[{"text":"Pay HTTP 402 challenges issued by OKX's ","type":"text"},{"text":"Agent Payments Protocol (APP)","type":"text","marks":[{"type":"strong"}]},{"text":" running on ","type":"text"},{"text":"X Layer","type":"text","marks":[{"type":"strong"}]},{"text":" (chain 196). APP's Pay Per Use (OKX product name: Instant Payment) is x402-compatible: a payee server returns HTTP 402 with a payment requirement, the payer signs an EIP-3009 ","type":"text"},{"text":"TransferWithAuthorization","type":"text","marks":[{"type":"code_inline"}]},{"text":" off-chain, and OKX's facilitator verifies and settles the transfer on-chain. Settlement is zero-gas to the payer on X Layer.","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill handles the full happy path:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detect a 402 challenge whose network resolves to X Layer (chain 196)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify the payer wallet has the requested asset (typically USDT0)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If insufficient, route + bridge into USDT0 on X Layer via the Uniswap Trading API","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sign the EIP-3009 authorization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Construct the ","type":"text"},{"text":"X-PAYMENT","type":"text","marks":[{"type":"code_inline"}]},{"text":" payload and retry the original request","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"OKX is launching APP on ","type":"text"},{"text":"2026-04-29","type":"text","marks":[{"type":"strong"}]},{"text":" with Uniswap as the featured DEX rail on X Layer. This skill version (v1.0.0) handles the ","type":"text"},{"text":"exact","type":"text","marks":[{"type":"code_inline"}]},{"text":" scheme (Pay Per Use, OKX product name: Instant Payment) only. Other x402 schemes (","type":"text"},{"text":"upto","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"batch-settlement","type":"text","marks":[{"type":"code_inline"}]},{"text":") and APP-product features OKX is shipping (escrow, session, batch / Batch Payment) are out of scope for this version. The skill refuses any non-","type":"text"},{"text":"exact","type":"text","marks":[{"type":"code_inline"}]},{"text":" scheme cleanly.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Protocol naming in your responses.","type":"text","marks":[{"type":"strong"}]},{"text":" When responding to the user, identify the protocol explicitly as ","type":"text"},{"text":"OKX Agent Payments Protocol (APP)","type":"text","marks":[{"type":"strong"}]},{"text":", not just \"x402\". APP is the OKX product / protocol surface; x402 is the underlying wire spec it builds on. Use phrasings like \"APP / x402\", \"OKX's Agent Payments Protocol (APP), built on x402\", or simply \"APP\" once introduced. Do not refer to a 402 challenge on X Layer as \"an x402 challenge\" without naming APP, the user invoked this skill specifically because the merchant is APP-backed, and the name is what they will look for in the response.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A ","type":"text"},{"text":"PRIVATE_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" env var (","type":"text"},{"text":"export PRIVATE_KEY=0x...","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Never commit or hardcode a private key.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"UNISWAP_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" env var (register at ","type":"text"},{"text":"developers.uniswap.org","type":"text","marks":[{"type":"link","attrs":{"href":"https://developers.uniswap.org/","title":null}}]},{"text":"). Required only if the wallet must be funded via cross-chain routing.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jq","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"cast","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Foundry) installed.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Node 18+","type":"text","marks":[{"type":"strong"}]},{"text":" (LTS). The signing step in ","type":"text"},{"text":"references/app-x402-flow.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/app-x402-flow.md","title":null}}]},{"text":" Step 4 uses ","type":"text"},{"text":"viem","type":"text","marks":[{"type":"code_inline"}]},{"text":" to produce the EIP-3009 typed-data signature.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"viem","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (npm). If the package is not already reachable from the user's working directory, the skill will prompt the user via ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"}]},{"text":" before running ","type":"text"},{"text":"npm install viem","type":"text","marks":[{"type":"code_inline"}]},{"text":" into a cached scratch directory at ","type":"text"},{"text":"~/.cache/uniswap-pay-with-app/signer/","type":"text","marks":[{"type":"code_inline"}]},{"text":". The install adds ~13 packages totaling ~5 MB. If the user declines, the skill stops cleanly before signing. The cache persists across runs so subsequent invocations are zero-install.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Input Validation Rules","type":"text"}]},{"type":"paragraph","content":[{"text":"Before using any value from the 402 response body, the user, or any other external source in API calls or shell commands:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ethereum address fields","type":"text","marks":[{"type":"strong"}]},{"text":" (e.g., ","type":"text"},{"text":"asset","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"payTo","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"WALLET_ADDRESS","type":"text","marks":[{"type":"code_inline"}]},{"text":"): the canonical check is the regex ","type":"text"},{"text":"^0x[a-fA-F0-9]{40}$","type":"text","marks":[{"type":"code_inline"}]},{"text":". If the value fails this regex, reject it. Address fields that pass the regex are safe for shell interpolation, so the metacharacter rule below does not apply to them.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Chain IDs","type":"text","marks":[{"type":"strong"}]},{"text":": MUST be a positive integer from the supported list.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Token amounts","type":"text","marks":[{"type":"strong"}]},{"text":": MUST be non-negative numeric strings matching ","type":"text"},{"text":"^[0-9]+$","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URLs","type":"text","marks":[{"type":"strong"}]},{"text":": MUST start with ","type":"text"},{"text":"https://","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Free-text fields","type":"text","marks":[{"type":"strong"}]},{"text":" (e.g., ","type":"text"},{"text":"description","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"extra.name","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"extra.version","type":"text","marks":[{"type":"code_inline"}]},{"text":", anything used to build EIP-712 domain or shown to the user): REJECT any value containing shell metacharacters: ","type":"text"},{"text":";","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"|","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"&","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"`","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"(","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":")","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":">","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003c","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\\","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"'","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", newlines. Note: the ","type":"text"},{"text":"extra.name","type":"text","marks":[{"type":"code_inline"}]},{"text":" value is signed bit-exact (see Domain warning in Phase 4), so reject the whole challenge if it contains shell metacharacters rather than mutating the value.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Flow","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"402 from X Layer-backed resource\n │\n v\n[1] Parse the x402 challenge (Phase 0 below)\n │\n v\n[2] Confirm network resolves to X Layer (chain 196)\n │ ├─ not chain 196 ──> escalate to pay-with-any-token, STOP\n │ └─ chain 196\n │\n v\n[3] Check wallet balance of the requested asset on X Layer\n │ ├─ sufficient ──> proceed to [5]\n │ └─ insufficient\n │ │\n │ v\n │ [4] Fund: route + bridge into the requested asset on X Layer\n │ (Uniswap Trading API, see references/funding-x-layer.md)\n │\n v\n[5] User Confirmation gate (see Phase 4 / Step 5 below)\n[6] Sign EIP-3009 TransferWithAuthorization\n[7] Construct X-PAYMENT payload, retry the original request\n[8] Verify 200 + Payment-Receipt","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Phase 0, Parse the 402 Challenge","type":"text"}]},{"type":"paragraph","content":[{"text":"The x402 challenge is JSON in the response body. Extract:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"x402Version","type":"text","marks":[{"type":"code_inline"}]},{"text":", confirms x402 protocol version.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].scheme","type":"text","marks":[{"type":"code_inline"}]},{"text":", only ","type":"text"},{"text":"\"exact\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" is supported in v1.0.0.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].network","type":"text","marks":[{"type":"code_inline"}]},{"text":", accept ","type":"text"},{"text":"\"x-layer\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"\"xlayer\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"\"eip155:196\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"196","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].maxAmountRequired","type":"text","marks":[{"type":"code_inline"}]},{"text":", base units of the asset. Must match ","type":"text"},{"text":"^[0-9]+$","type":"text","marks":[{"type":"code_inline"}]},{"text":" AND be ","type":"text"},{"text":"strictly greater than zero","type":"text","marks":[{"type":"strong"}]},{"text":". A challenge with ","type":"text"},{"text":"maxAmountRequired === \"0\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" is semantically broken (HTTP 402 by definition demands a positive payment) and must be refused as merchant misconfiguration. Do not rationalize zero as a \"ping\", \"authentication\", or \"free-tier confirmation\"; OKX's facilitator will not settle a zero-value ","type":"text"},{"text":"TransferWithAuthorization","type":"text","marks":[{"type":"code_inline"}]},{"text":" and any signature you produce is wasted. Surface this to the user and stop.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].asset","type":"text","marks":[{"type":"code_inline"}]},{"text":", token contract on X Layer.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].payTo","type":"text","marks":[{"type":"code_inline"}]},{"text":", recipient address.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].resource","type":"text","marks":[{"type":"code_inline"}]},{"text":", the URL the facilitator binds the payment to. Extract this when present. Use it as the retry target. If the field is absent, fall back to the original request URL.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].extra.name","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"accepts[].extra.version","type":"text","marks":[{"type":"code_inline"}]},{"text":", EIP-712 domain values for the asset.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"accepts[].maxTimeoutSeconds","type":"text","marks":[{"type":"code_inline"}]},{"text":", used for ","type":"text"},{"text":"validBefore","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"x402Version gate.","type":"text","marks":[{"type":"strong"}]},{"text":" Confirm ","type":"text"},{"text":"x402Version === 1","type":"text","marks":[{"type":"code_inline"}]},{"text":" immediately after parsing. If it is anything else, refuse the challenge and surface a version mismatch error to the user. v1.0.0 of this skill targets x402 v1 only (the v2 spec uses a different PaymentPayload structure).","type":"text"}]},{"type":"paragraph","content":[{"text":"Scheme gate.","type":"text","marks":[{"type":"strong"}]},{"text":" Confirm ","type":"text"},{"text":"accepts[].scheme === \"exact\"","type":"text","marks":[{"type":"code_inline"}]},{"text":". The x402 spec defines ","type":"text"},{"text":"exact","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"upto","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"batch-settlement","type":"text","marks":[{"type":"code_inline"}]},{"text":" schemes. v1.0.0 of this skill supports ","type":"text"},{"text":"exact","type":"text","marks":[{"type":"code_inline"}]},{"text":" only. If the chosen entry uses any other scheme, refuse cleanly. (OKX's product surface uses its own vocabulary including ","type":"text"},{"text":"charge","type":"text","marks":[{"type":"code_inline"}]},{"text":" for their Instant Payment primitive; that is OKX product marketing, not a wire scheme value. The wire-level scheme on the ","type":"text"},{"text":"accepts[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry is what you check, and it must be ","type":"text"},{"text":"\"exact\"","type":"text","marks":[{"type":"code_inline"}]},{"text":".)","type":"text"}]}]},{"type":"paragraph","content":[{"text":"If multiple ","type":"text"},{"text":"accepts","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries are present, prefer the one whose ","type":"text"},{"text":"asset","type":"text","marks":[{"type":"code_inline"}]},{"text":" the wallet already holds on X Layer. If multiple options are equally viable, prefer USDT0 (deepest Uniswap funding-flow liquidity).","type":"text"}]},{"type":"paragraph","content":[{"text":"WOKB / native OKB likely not eligible as APP settlement assets.","type":"text","marks":[{"type":"strong"}]},{"text":" We have not seen OKX publish WOKB or OKB as settlement assets; current public dev docs list USDT0, USDG, and USDC as the supported stablecoin settlement assets. If a 402 challenge ever surfaces a non-stablecoin ","type":"text"},{"text":"asset","type":"text","marks":[{"type":"code_inline"}]},{"text":" (for example ","type":"text"},{"text":"WOKB","type":"text","marks":[{"type":"code_inline"}]},{"text":"), refuse the challenge and ask the user to verify the merchant configuration.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Phase 1, Confirm Network is X Layer","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"case \"$X402_NETWORK\" in\n x-layer|xlayer|\"eip155:196\"|196) X402_CHAIN_ID=196 ;;\n *)\n echo \"Network is not X Layer. Use pay-with-any-token instead.\"\n exit 1\n ;;\nesac","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"If the network is not X Layer, ","type":"text"},{"text":"stop","type":"text","marks":[{"type":"strong"}]},{"text":" and escalate to the ","type":"text"},{"text":"pay-with-any-token","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill, which handles 402 challenges on Ethereum, Base, Arbitrum, Tempo, and the other chains the Trading API supports.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Phase 2, Check Wallet Balance on X Layer","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"REQUIRED:","type":"text","marks":[{"type":"strong"}]},{"text":" You must have the user's source wallet address. Use ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"}]},{"text":" if not provided. Store as ","type":"text"},{"text":"WALLET_ADDRESS","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"ASSET_BALANCE=$(cast call \"$X402_ASSET\" \\\n \"balanceOf(address)(uint256)\" \"$WALLET_ADDRESS\" \\\n --rpc-url https://rpc.xlayer.tech)\n\nif [ \"$ASSET_BALANCE\" -lt \"$X402_AMOUNT\" ]; then\n echo \"Insufficient $X402_TOKEN_NAME on X Layer. Funding required.\"\n # Proceed to Phase 3 (funding)\nfi","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Phase 3, Fund USDT0 on X Layer (only if needed)","type":"text"}]},{"type":"paragraph","content":[{"text":"When the wallet lacks the requested asset, acquire it via the Uniswap Trading API: ","type":"text"},{"text":"EXACT_OUTPUT","type":"text","marks":[{"type":"code_inline"}]},{"text":" quote with ","type":"text"},{"text":"tokenOutChainId=196","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"tokenOut","type":"text","marks":[{"type":"code_inline"}]},{"text":" set to the X Layer asset address. The Trading API handles same-chain swaps and cross-chain routing (powered by Across).","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Across coverage gap (verified 2026-04-27).","type":"text","marks":[{"type":"strong"}]},{"text":" Across Protocol does not currently list X Layer (chain 196) as a supported destination. As a result, cross-chain ","type":"text"},{"text":"/quote","type":"text","marks":[{"type":"code_inline"}]},{"text":" calls into chain 196 return ","type":"text"},{"text":"ResourceNotFound: No quotes available","type":"text","marks":[{"type":"code_inline"}]},{"text":" regardless of source chain. Same-chain X Layer swaps (Phase A in ","type":"text"},{"text":"references/funding-x-layer.md","type":"text","marks":[{"type":"code_inline"}]},{"text":") are unaffected and work normally.","type":"text"}]},{"type":"paragraph","content":[{"text":"What this means for the agent in v1.0.0.","type":"text","marks":[{"type":"strong"}]},{"text":" If the user holds funds on a chain other than X Layer, the cross-chain leg must be done through a bridge service that supports X Layer (the user runs that step outside this skill, then re-invokes for the same-chain swap and 402 settlement). Surface this honestly to the user, do not recommend a specific bridge product (TODO: research and document a co-marketing-aligned bridge recommendation in a follow-up).","type":"text"}]},{"type":"paragraph","content":[{"text":"When you defer the bridge to the user, your response must still describe the FULL end-to-end flow","type":"text","marks":[{"type":"strong"}]},{"text":", not just the bridge step. After identifying the Across gap and asking the user to bridge externally, walk through what happens when they return: (1) re-check ","type":"text"},{"text":"balanceOf","type":"text","marks":[{"type":"code_inline"}]},{"text":" of the requested asset on X Layer, (2) if a same-chain swap is needed (e.g. USDG to USDT0), describe the Trading API EXACT_OUTPUT call and its Confirmation Gate, (3) construct the EIP-3009 ","type":"text"},{"text":"TransferWithAuthorization","type":"text","marks":[{"type":"code_inline"}]},{"text":" typed-data using ","type":"text"},{"text":"extra.name","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"extra.version","type":"text","marks":[{"type":"code_inline"}]},{"text":" from the challenge, with chainId 196 and ","type":"text"},{"text":"verifyingContract","type":"text","marks":[{"type":"code_inline"}]},{"text":" = the asset address, (4) sign with the user's private key (Confirmation Gate before signing), (5) build the ","type":"text"},{"text":"X-PAYMENT","type":"text","marks":[{"type":"code_inline"}]},{"text":" JSON wrapper (x402Version 1, scheme \"exact\", network \"x-layer\", payload with signature + authorization), base64-encode it with no whitespace, and retry the original request URL with the ","type":"text"},{"text":"X-PAYMENT","type":"text","marks":[{"type":"code_inline"}]},{"text":" header. Showing the full plan up front lets the user see what they are committing to before they leave the skill, even though signing happens after they return.","type":"text"}]}]},{"type":"paragraph","content":[{"text":"Default funding target = USDT0.","type":"text","marks":[{"type":"strong"}]},{"text":" If the 402 challenge requests a different asset, fund into that asset directly only when it has reliable Uniswap routing on X Layer:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Asset","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Address","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Decimals","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Funding","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"USDT0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0x779Ded0c9e1022225f8E0630b35a9b54bE713736","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"6","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Direct via Trading API","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"USDG","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0x4ae46a509F6b1D9056937BA4500cb143933D2dc8","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"6","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Direct, or one-hop USDT0 to USDG","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"USDC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0x74b7F16337b8972027F6196A17a631aC6dE26d22","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"6","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"⏳ No reliable Uniswap v3 routing on X Layer. The Trading API does not consistently return routes for USDC swaps on X Layer; available pool liquidity is too thin for reliable execution. If the merchant requires USDC, bridge USDC directly from a chain where it is liquid (Base, Arbitrum, Mainnet) using the Trading API rather than attempting a same-chain swap on X Layer.","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Detailed scripts and parameters: see ","type":"text"},{"text":"references/funding-x-layer.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/funding-x-layer.md","title":null}}]},{"text":".","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Bridge buffer.","type":"text","marks":[{"type":"strong"}]},{"text":" Apply a 0.5% buffer to account for bridge fees. Quotes expire in ~60 seconds, re-fetch if any delay before broadcast.","type":"text"}]},{"type":"paragraph","content":[{"text":"Minimum bridge recommendation.","type":"text","marks":[{"type":"strong"}]},{"text":" If the shortfall is \u003c $5, top up to $5 to amortize bridge gas on the source chain.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Gas and Routing Caveats","type":"text"}]},{"type":"paragraph","content":[{"text":"Surface these to the user before proceeding to fund, and ","type":"text"},{"text":"call ","type":"text","marks":[{"type":"strong"}]},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (not an echoed bash prompt) before acting if any apply:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OKB on X Layer for same-chain swaps.","type":"text","marks":[{"type":"strong"}]},{"text":" OKX gas-sponsors only the facilitator's settlement transfer. Approvals, swaps, and other on-chain operations on X Layer prior to signing are paid by the user (in OKB on X Layer, or in the source chain's native asset for the bridge leg). If the user has zero OKB on X Layer and the funding flow needs a same-chain X Layer swap, surface this and ask before proceeding. Until OKX confirms a broader gas-sponsorship policy, assume only the final settlement transfer is sponsored.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bridge destination token.","type":"text","marks":[{"type":"strong"}]},{"text":" If the wallet still lacks ","type":"text"},{"text":"$X402_ASSET","type":"text","marks":[{"type":"code_inline"}]},{"text":" after the funding flow's polling loop completes, surface the source-chain tx hash and the Across explorer link to the user. The bridge may have delivered a different variant on X Layer (rare for current Across paths) or may have failed. v1.0.0 does not auto-detect alternate-token arrival; the user must verify on-chain.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Phase 4, EIP-3009 Signing and X-PAYMENT Submission","type":"text"}]},{"type":"paragraph","content":[{"text":"OKX's APP Instant Payment uses x402's ","type":"text"},{"text":"\"exact\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" scheme: the payer signs a ","type":"text"},{"text":"TransferWithAuthorization","type":"text","marks":[{"type":"code_inline"}]},{"text":" typed-data message bound to the ","type":"text"},{"text":"token's own","type":"text","marks":[{"type":"strong"}]},{"text":" EIP-712 domain. The signed authorization travels in the ","type":"text"},{"text":"X-PAYMENT","type":"text","marks":[{"type":"code_inline"}]},{"text":" header on retry; OKX's facilitator settles the transfer on-chain (zero gas to the payer on X Layer).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5, User Confirmation","type":"text"}]},{"type":"paragraph","content":[{"text":"Every","type":"text","marks":[{"type":"strong"}]},{"text":" transaction this skill touches requires a separate ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"}]},{"text":" gate, with no exceptions for funding legs. \"Funding\" is not a single transaction; it is several, and each one is its own gate. The required gates, in order they typically fire:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Source-chain ERC-20 approval","type":"text","marks":[{"type":"strong"}]},{"text":" to Permit2 / Universal Router (only if ","type":"text"},{"text":"tokenIn","type":"text","marks":[{"type":"code_inline"}]},{"text":" is not native and the allowance is insufficient).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Same-chain swap","type":"text","marks":[{"type":"strong"}]},{"text":" on the source chain (e.g. UNI to USDC on Ethereum), if the funding plan includes a source-chain leg.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bridge submission","type":"text","marks":[{"type":"strong"}]},{"text":" to the cross-chain rail (Across, OKX bridge, etc.). When the bridge step is user-initiated outside this skill, the gate becomes \"are you ready to leave the skill, run the bridge, and re-invoke once funds land on X Layer?\".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Same-chain swap on X Layer","type":"text","marks":[{"type":"strong"}]},{"text":" (e.g. USDG to USDT0), if the funding plan includes a destination-chain leg.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"EIP-3009 ","type":"text","marks":[{"type":"strong"}]},{"text":"TransferWithAuthorization","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" signature","type":"text","marks":[{"type":"strong"}]},{"text":" for the 402 settlement.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Use the ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" agent tool","type":"text","marks":[{"type":"strong"}]},{"text":" for each gate (not ","type":"text"},{"text":"read -p","type":"text","marks":[{"type":"code_inline"}]},{"text":", not ","type":"text"},{"text":"echo","type":"text","marks":[{"type":"code_inline"}]},{"text":" to a bash prompt, not a printed \"(yes/no)\" line in your response) and ","type":"text"},{"text":"block on the user's reply","type":"text","marks":[{"type":"strong"}]},{"text":" before moving on. The summary you present at every gate must cover:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Action (approve, swap, bridge, sign EIP-3009 authorization).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Amount and token (input AND output amounts for swaps and bridges).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Source chain and destination chain (where they differ).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Recipient (","type":"text"},{"text":"payTo","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the EIP-3009 step).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resource URL the payment is bound to.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Estimated gas (where applicable).","type":"text"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Common failure mode.","type":"text","marks":[{"type":"strong"}]},{"text":" When describing a multi-step funding plan in your response, do NOT collapse multiple transactions into a single confirmation question like \"Proceed with funding and payment?\". Each transaction needs its own gate at the moment it is about to fire. A consolidated upfront \"yes\" is not consent for later transactions; the user has not seen the live amounts, gas, and recipient at the time those transactions execute.","type":"text"}]}]},{"type":"paragraph","content":[{"text":"Obtain explicit confirmation per gate. Each gate is independent. ","type":"text"},{"text":"Never","type":"text","marks":[{"type":"strong"}]},{"text":" auto-submit even if the user previously pre-authorized the session, the call, or the wallet. A \"yes\" earlier in the flow does not carry forward, and a \"yes\" to a multi-step plan is not a \"yes\" to the individual transactions inside it.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"What a correct gate looks like in your response.","type":"text","marks":[{"type":"strong"}]},{"text":" Whether you are executing the skill in real time or describing a plan in text, every transaction step MUST appear as its own labelled \"Confirmation Gate\" block, with both the structured summary and an explicit ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"}]},{"text":" invocation. Reproduce this template literally for each gate. Do not collapse the gate into a single line. Do not describe the gate in passive voice (\"we will confirm before signing\"). Show the gate as a discrete action.","type":"text"}]},{"type":"paragraph","content":[{"text":"Template (use for every gate, even when there is only one):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"### Confirmation Gate N: \u003cAction name>\n\n| Field | Value |\n| ------------ | ---------------------------------------- |\n| Action | \u003capprove / swap / bridge / sign EIP-3009>|\n| Amount in | \u003camount + symbol on source chain> |\n| Amount out | \u003camount + symbol on destination chain> |\n| Source chain | \u003cchain name + id> |\n| Dest chain | \u003cchain name + id> |\n| Recipient | \u003caddress> |\n| Resource URL | \u003curl the payment is bound to> |\n| Est. gas | \u003camount + token> |\n\nThen call AskUserQuestion(\"Proceed with \u003caction>?\")\nand BLOCK on the user's reply. If anything other than an explicit\n\"yes\", stop and report.","type":"text"}]},{"type":"paragraph","content":[{"text":"A response that only lists fields without the ","type":"text"},{"text":"AskUserQuestion(\"Proceed?\") + block","type":"text","marks":[{"type":"code_inline"}]},{"text":" step is not a gate, even if it looks comprehensive. The model's natural inclination is to summarize and continue; resist that inclination, name the gate, and stop.","type":"text"}]}]},{"type":"paragraph","content":[{"text":"\u003c!-- markdownlint-disable-next-line -->","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"What does NOT count as a confirmation gate.","type":"text","marks":[{"type":"strong"}]},{"text":" Emitting a bash script that prints ","type":"text"},{"text":"\"⚠️ CONFIRMATION REQUIRED\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"\"(yes/no)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" to stdout and then continues with ","type":"text"},{"text":"echo \"Signing...\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" a gate, because the script proceeds regardless of user input. A correct gate uses the ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"}]},{"text":" tool (or, if the user explicitly opts into shell-only mode, an actual blocking ","type":"text"},{"text":"read -p","type":"text","marks":[{"type":"code_inline"}]},{"text":" followed by an explicit ","type":"text"},{"text":"yes","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"no","type":"text","marks":[{"type":"code_inline"}]},{"text":" branch in the script). When in doubt, prefer ","type":"text"},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"paragraph","content":[{"text":"\u003c!-- markdownlint-disable-next-line -->","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Shared-wallet race.","type":"text","marks":[{"type":"strong"}]},{"text":" If the wallet is shared (for example, multiple agents running concurrently against the same key), the balance can be drained between balance check and submit. Re-check ","type":"text"},{"text":"balanceOf","type":"text","marks":[{"type":"code_inline"}]},{"text":" at the moment of confirmation as well.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 6, Sign and Submit","type":"text"}]},{"type":"paragraph","content":[{"text":"Detailed steps including domain construction, nonce generation, signing with viem, payload assembly, and retry: see ","type":"text"},{"text":"references/app-x402-flow.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/app-x402-flow.md","title":null}}]},{"text":". On retry, target the URL from ","type":"text"},{"text":"accepts[].resource","type":"text","marks":[{"type":"code_inline"}]},{"text":" if it was present in the challenge, otherwise the original request URL.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Domain warning.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"verifyingContract","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the ","type":"text"},{"text":"token contract","type":"text","marks":[{"type":"strong"}]},{"text":", not a separate verifier. Use ","type":"text"},{"text":"name","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" from the challenge's ","type":"text"},{"text":"extra","type":"text","marks":[{"type":"code_inline"}]},{"text":" field, do not assume defaults. Different tokens have different domain values. An incorrect domain produces a signature the facilitator will reject with another 402.","type":"text"}]},{"type":"paragraph","content":[{"text":"Bit-exact UTF-8 for ","type":"text","marks":[{"type":"strong"}]},{"text":"extra.name","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" The EIP-712 domain hash is byte-exact. The ","type":"text"},{"text":"name","type":"text","marks":[{"type":"code_inline"}]},{"text":" field in ","type":"text"},{"text":"extra","type":"text","marks":[{"type":"code_inline"}]},{"text":" must be passed through unchanged from the challenge bytes. Do not normalize it, do not lowercase it, do not substitute ASCII ","type":"text"},{"text":"T","type":"text","marks":[{"type":"code_inline"}]},{"text":" (U+0054) for Unicode ","type":"text"},{"text":"₮","type":"text","marks":[{"type":"code_inline"}]},{"text":" (U+20AE), do not collapse Unicode forms (NFC vs NFD). For example, a challenge that returns ","type":"text"},{"text":"\"USD₮0\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" must be signed as ","type":"text"},{"text":"\"USD₮0\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"; reading it as ","type":"text"},{"text":"\"USDT0\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" will produce a signature the facilitator rejects with another 402. Pass the raw bytes of ","type":"text"},{"text":"extra.name","type":"text","marks":[{"type":"code_inline"}]},{"text":" straight into the EIP-712 domain.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Situation","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"402 challenge has no ","type":"text"},{"text":"network","type":"text","marks":[{"type":"code_inline"}]},{"text":" field","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Inspect challenge body; if ","type":"text"},{"text":"chainId","type":"text","marks":[{"type":"code_inline"}]},{"text":" resolves to 196 use this skill, otherwise escalate to ","type":"text"},{"text":"pay-with-any-token","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Network is not chain 196","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Escalate to ","type":"text"},{"text":"pay-with-any-token","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x402Version !== 1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Refuse cleanly; surface a version mismatch error. v1.0.0 of this skill targets x402 v1 only.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"accepts[].scheme !== \"exact\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Refuse cleanly; v1.0.0 supports the ","type":"text"},{"text":"exact","type":"text","marks":[{"type":"code_inline"}]},{"text":" scheme only. Other x402 schemes (","type":"text"},{"text":"upto","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"batch-settlement","type":"text","marks":[{"type":"code_inline"}]},{"text":") are out of scope.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"accepts[].maxAmountRequired === \"0\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Refuse cleanly as merchant misconfiguration. HTTP 402 demands a positive payment; OKX's facilitator will not settle a zero-value transfer. Do not sign and do not rationalize zero as a ping, auth check, or free tier.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"APP requests USDC on X Layer","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Surface a clear caveat: USDC has no reliable Uniswap v3 routing on X Layer. Suggest bridging USDC directly from a chain where it is liquid (Base, Arbitrum, Mainnet), or asking about USDT0.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Insufficient asset on X Layer","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trigger funding flow (Phase 3)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trading API returns 400","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Log request/response; check amount formatting and address checksums","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trading API returns 429","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Back off and retry with exponential delay","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quote expired","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-fetch quote; do not reuse old ","type":"text"},{"text":"permitData","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bridge times out","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check Across bridge explorer; do not re-submit","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EIP-3009 signature rejected (402 on retry)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Verify domain ","type":"text"},{"text":"name","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" from ","type":"text"},{"text":"extra","type":"text","marks":[{"type":"code_inline"}]},{"text":" (byte-exact, including any non-ASCII characters), check ","type":"text"},{"text":"validBefore","type":"text","marks":[{"type":"code_inline"}]},{"text":" is fresh, confirm ","type":"text"},{"text":"nonce","type":"text","marks":[{"type":"code_inline"}]},{"text":" was unused","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Amount mismatch on retry","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recompute base units using on-chain ","type":"text"},{"text":"decimals()","type":"text","marks":[{"type":"code_inline"}]},{"text":" of the actual asset; do not assume 6","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"On-chain settlement reverts (","type":"text"},{"text":"transferFrom","type":"text","marks":[{"type":"code_inline"}]},{"text":" failed)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-check ","type":"text"},{"text":"balanceOf","type":"text","marks":[{"type":"code_inline"}]},{"text":" at retry time; the balance may have been drained between sign and submit (shared wallet, concurrent agent, manual transfer). Surface to the user before retrying.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User asks about escrow / session / batch / ","type":"text"},{"text":"upto","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Inform that this skill version covers Instant Payment (","type":"text"},{"text":"exact","type":"text","marks":[{"type":"code_inline"}]},{"text":" scheme) only. Other primitives are out of scope for v1.0.0; a v1.x follow-up will track them as OKX ships.","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Key Addresses and References","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"X Layer (chain 196)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Chain ID","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"196","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Public RPC","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"https://rpc.xlayer.tech","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"USDT0","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x779Ded0c9e1022225f8E0630b35a9b54bE713736","type":"text","marks":[{"type":"code_inline"}]},{"text":" (decimals 6)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"USDG","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x4ae46a509F6b1D9056937BA4500cb143933D2dc8","type":"text","marks":[{"type":"code_inline"}]},{"text":" (decimals 6)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"USDC","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x74b7F16337b8972027F6196A17a631aC6dE26d22","type":"text","marks":[{"type":"code_inline"}]},{"text":" (decimals 6, no reliable Uniswap routing on X Layer; bridge from a liquid chain)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"WOKB","type":"text","marks":[{"type":"strong"}]},{"text":" (wrapped native): ","type":"text"},{"text":"0xe538905cf8410324e03A5A23C1c177a474D59b2b","type":"text","marks":[{"type":"code_inline"}]},{"text":" (decimals 18)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Uniswap V3 Factory","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x4B2ab38DBF28D31D467aA8993f6c2585981D6804","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SwapRouter02","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x4f0C28f5926AFDA16bf2506D5D9e57Ea190f9bcA","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Universal Router 2.1","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0xDa00aE15d3A71466517129255255db7c0c0956d3","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"QuoterV2","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0xD1b797D92d87B688193A2B976eFc8D577D204343","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Permit2","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x000000000022D473030F116dDEE9F6B43aC78BA3","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"USDT0/USDG pool","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x0cBe0dBE1400e57f371a38BD3b9bC80F7C3676dA","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"USDT0/WOKB pool","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"0x63d62734847E55A266FCa4219A9aD0a02D5F6e02","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Uniswap Trading API","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Base URL: ","type":"text"},{"text":"https://trade-api.gateway.uniswap.org/v1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Header: ","type":"text"},{"text":"x-api-key: $UNISWAP_API_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Header: ","type":"text"},{"text":"x-universal-router-version: 2.1.1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supported chains include 1, 8453, 42161, 10, 137, 130, ","type":"text"},{"text":"196","type":"text","marks":[{"type":"strong"}]},{"text":", and more (see Trading API supported-chains docs).","type":"text"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"The on-chain Universal Router contract on X Layer is labeled ","type":"text"},{"text":"2.1","type":"text","marks":[{"type":"strong"}]},{"text":" (deployed at ","type":"text"},{"text":"0xDa00aE15d3A71466517129255255db7c0c0956d3","type":"text","marks":[{"type":"code_inline"}]},{"text":" above). The Trading API expects the header value ","type":"text"},{"text":"2.1.1","type":"text","marks":[{"type":"code_inline"}]},{"text":", which is the API's internal version-string for the routing path that targets the same Universal Router 2.1 contract. The two version strings refer to related but distinct things; do not substitute one for the other.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"OKX APP","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"APP overview / dev docs","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"https://web3.okx.com/onchainos/dev-docs/payments/x402-introduction","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OKX onchainos-skills repo","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"https://github.com/okx/onchainos-skills","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"x402 spec","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"https://github.com/coinbase/x402","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"pay-with-any-token","type":"text","marks":[{"type":"link","attrs":{"href":"../pay-with-any-token/SKILL.md","title":null}}]},{"text":", sibling skill for HTTP 402 challenges on chains other than X Layer (Ethereum, Base, Arbitrum, Tempo, etc.). Use that skill for non-X-Layer challenges.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"swap-integration","type":"text","marks":[{"type":"link","attrs":{"href":"../swap-integration/SKILL.md","title":null}}]},{"text":", full Uniswap swap integration reference (Trading API, Universal Router, Permit2).","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"pay-with-app","model":"opus","author":"@skillopedia","source":{"stars":211,"repo_name":"uniswap-ai","origin_url":"https://github.com/uniswap/uniswap-ai/blob/HEAD/packages/plugins/uniswap-trading/skills/pay-with-app/SKILL.md","repo_owner":"uniswap","body_sha256":"c38acfb35515a912cd2ff3f447c59542f0fdd1a66aee63910bad98caf50edee3","cluster_key":"ab14f281a14cd82e5ab3f902a070f2461f4d733a9d6a774d82133b95d07df8f3","clean_bundle":{"format":"clean-skill-bundle-v1","source":"uniswap/uniswap-ai/packages/plugins/uniswap-trading/skills/pay-with-app/SKILL.md","attachments":[{"id":"a13b5ea3-132f-587c-817e-2beb8571a1f3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a13b5ea3-132f-587c-817e-2beb8571a1f3/attachment.md","path":"references/app-x402-flow.md","size":25654,"sha256":"017e8e2aaf88375d59d6173c9690ff37bdfbbb85bf4da96cd80e842935e9be8c","contentType":"text/markdown; charset=utf-8"},{"id":"658e48dd-b65f-55ed-bebc-370088033e5b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/658e48dd-b65f-55ed-bebc-370088033e5b/attachment.md","path":"references/funding-x-layer.md","size":20125,"sha256":"db6b66edb8592dcd03812aee63df634814cbb18ca21eff5687e1b298c234b5ed","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"01f7a31efc57caae6c14c29243d56cccd9896e1b8b08d7ba3540e94a1f507fdb","attachment_count":2,"text_attachments":2,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"packages/plugins/uniswap-trading/skills/pay-with-app/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"integrations-apis","metadata":{"author":"uniswap","version":"1.0.0"},"import_tag":"clean-skills-v1","description":"Pay HTTP 402 payment challenges issued by OKX's Agent Payments Protocol (APP) on X Layer using tokens from any chain via the Uniswap Trading API. Use this skill whenever the user encounters a 402 challenge whose network resolves to X Layer (chain 196), mentions \"APP\", \"Agent Payments Protocol\", \"OKX agent payment\", \"OKX Onchain OS\", \"OKX agentic wallet\", \"x402 on X Layer\", \"USDT0\", \"x42\", \"Instant Payment\", \"Batch Payment\", \"pay for X Layer API\", or wants to pay an OKX-backed merchant. Even when the user does not explicitly say APP, prefer this skill for any 402 challenge whose network resolves to X Layer (chain 196). For 402 challenges on other chains (Ethereum, Base, Arbitrum, Tempo) use pay-with-any-token instead.\n","allowed-tools":"Read, Glob, Grep, Bash(curl:*), Bash(jq:*), Bash(cast:*), Bash(openssl:*), Bash(npx:*), Bash(node:*), WebFetch, AskUserQuestion"}},"renderedAt":1782981075182}

Pay With APP (OKX Agent Payments Protocol on X Layer) Pay HTTP 402 challenges issued by OKX's Agent Payments Protocol (APP) running on X Layer (chain 196). APP's Pay Per Use (OKX product name: Instant Payment) is x402-compatible: a payee server returns HTTP 402 with a payment requirement, the payer signs an EIP-3009 off-chain, and OKX's facilitator verifies and settles the transfer on-chain. Settlement is zero-gas to the payer on X Layer. This skill handles the full happy path: 1. Detect a 402 challenge whose network resolves to X Layer (chain 196) 2. Verify the payer wallet has the requested…