Skills clawhub-x402-payments
Implements USDC x402 payments via PayAI (EIP-3009) and DHM x402 payments via EVVM native (signed pay). Use when adding x402 payment flows, PayAI Echo integration, EVVM pay() for DHM, agent-to-agent payments with Privy, or when the user asks how to do USDC/DHM x402 in the ClawHub/NHS EVVM app.
git clone https://github.com/openclaw/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/arunnadarasa/digitalhealth" ~/.claude/skills/openclaw-skills-clawhub-x402-payments && rm -rf "$T"
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/arunnadarasa/digitalhealth" ~/.openclaw/skills/openclaw-skills-clawhub-x402-payments && rm -rf "$T"
skills/arunnadarasa/digitalhealth/SKILL.mdClawHub x402 Payments (USDC via PayAI + DHM via EVVM)
This skill documents the two x402 payment flows in the NHS EVVM / ClawHub app: USDC via PayAI Echo and DHM via EVVM native. Reference implementation lives in this repo.
Reference paths
| Flow | Client UI | Server / config |
|---|---|---|
| USDC (PayAI) | | Config: (X402_USDC_ECHO_URL, USDC_BASE_SEPOLIA) |
| DHM (EVVM) | | (GET 402, POST /payments/evvm/dhm) |
| EVVM sign | | — |
Chain: Base Sepolia (chainId 84532).
Flow 1: USDC x402 via PayAI Echo
PayAI returns 402 with an
accepts array (not options). Client picks a USDC option, builds EIP-3009 TransferWithAuthorization, signs EIP-712, sends signature in PAYMENT-SIGNATURE header, retries the same URL; server returns 200 and may set PAYMENT-RESPONSE header with result (e.g. transaction hash).
Client steps
-
Request resource
(e.g.GET <Echo URL>
).https://x402.payai.network/api/base-sepolia/paid-content -
Parse 402
- Prefer
response header (base64-encoded JSON).PAYMENT-REQUIRED - Fallback: response body may be JSON with
array.accepts - Type:
.{ x402Version?, error?, resource?, accepts: Array<{ scheme, network, amount, asset, payTo, maxTimeoutSeconds?, extra? }> }
- Prefer
-
Pick USDC option
- From
, choose entry whereaccepts
matches USDC on Base Sepolia orasset
.extra.name === "USDC" - Use
,amount
,asset
,payTo
/extra.name
for EIP-712.extra.version
- From
-
Build EIP-3009 authorization
- Domain:
=name
,extra?.name ?? "USDC"
=version
,extra?.version ?? "2"
= 84532,chainId
=verifyingContract
.asset - Type:
:TransferWithAuthorization
,from
,to
,value
(0),validAfter
(e.g. now + 300s),validBefore
(32 random bytes as hex).nonce - Sign with
(EIP-712).signTypedData
- Domain:
-
Send payment and retry
- Build payload:
.{ x402Version: 2, scheme, network, accepted: { scheme, network, amount, asset, payTo, maxTimeoutSeconds, extra? }, payload: { signature, authorization: message }, extensions: {} }
= base64(JSON.stringify(payload)).PAYMENT-SIGNATURE- Same URL:
with headerGET
.PAYMENT-SIGNATURE: <base64>
- Build payload:
-
Read result
- On 200: body is content. Optional
orPAYMENT-RESPONSE
header (base64 JSON) may containX-PAYMENT-RESPONSE
(tx hash) etc.transaction
- On 200: body is content. Optional
Config
: PayAI Echo endpoint (default:VITE_X402_USDC_ECHO_URL
).https://x402.payai.network/api/base-sepolia/paid-content- USDC on Base Sepolia:
.0x036CbD53842c5426634e7929541eC2318f3dCF7e
Flow 2: DHM x402 via EVVM native
Server returns 402 with
PAYMENT-REQUIRED: 1 and a JSON body containing options (EVVM pay options with to, suggestedNonce, etc.). Client signs an EVVM pay message (personal_sign), POSTs to server’s payment endpoint; server executes pay() on EVVM Core and returns content + txHash.
Server (402 + payment endpoint)
-
Protected resource
(or similar): if not paid, respond withGET /clinical/mri-slot
,402
, and body:PAYMENT-REQUIRED: 1
,resource
,description
(recipient address),tosuggestedNonce
: array with at least one option:options
,id
,type: "evvm_pay"
,chainId
,evvmId
,coreAddress
(DHM),token
,to
,suggestedNonce
,amount
,priorityFee
(or null),executor
.isAsyncExec
-
Payment execution
body:POST /payments/evvm/dhm
,from
,to
,toIdentity
,token
,amount
,priorityFee
,executor
,nonce
,isAsyncExec
.signature
Server calls EVVM Core
with executor key, waits for receipt, returnspay(...)
.{ status, txHash, content }
Client steps
-
Request resource
.GET <X402_SERVER_URL>/clinical/mri-slot -
Detect 402
orres.status === 402
. Parse body as JSON:res.headers.get("PAYMENT-REQUIRED") === "1"
.{ resource, description?, to, suggestedNonce?, options } -
Pick option
. Ensureoptions.find(o => o.type === "evvm_pay" || o.id === "dhm-evvm") ?? options[0]
andto
are present.suggestedNonce -
Build EVVM pay message
- Hash payload for Core:
.keccak256(encodeAbiParameters("string, address, string, address, uint256, uint256", ["pay", to, toIdentity, token, amount, priorityFee])) - Message string:
(comma-separated).evvmId, coreAddress, hashPayload, executor, nonce, isAsyncExec - Use
frombuildEvvmPayMessageCoreDoc
with: evvmId, coreAddress, to, "", token, amount, priorityFee, executor, nonce, isAsyncExec.frontend/src/lib/evvmSign.ts
- Hash payload for Core:
-
Sign and submit
(personal_sign) the message string.signMessage- POST to
with JSON body:POST <X402_SERVER_URL>/payments/evvm/dhm
,from
,to
,toIdentity: ""
,token
,amount
,priorityFee
,executor
,nonce
,isAsyncExec
.signature - Response 200:
(unlocked resource),content
.txHash
Config
: DHM x402 server (e.g.VITE_X402_SERVER_URL
or localhost).https://evvm-x402-dhm.fly.dev- Server env:
,EXECUTOR_PRIVATE_KEY
,RPC_URL
,RECIPIENT_ADDRESS
,EVVM_ID
,EVVM_CORE_ADDRESS
(seeDHM_TOKEN_ADDRESS
).server/.env.example
Checklist for adding or debugging
USDC (PayAI)
- 402 parsed from header or body;
used (notaccepts
).options - EIP-712 domain and
match USDC contract (name/version fromTransferWithAuthorization
or "USDC"/"2").extra -
is base64 JSON; same URL retried with GET + header.PAYMENT-SIGNATURE -
decoded when present for tx hash / receipt.PAYMENT-RESPONSE
DHM (EVVM)
- 402 body has
andoptions[].to
; client uses them in the signed message.suggestedNonce - Message built with
+hashDataForPayCore
(see evvmSign.ts).buildEvvmMessageV3 - POST body matches server expectation (from, to, token, amount, nonce, executor, isAsyncExec, signature).
- Server has
and RPC to submitEXECUTOR_PRIVATE_KEY
.pay()
Quick copy-paste (types)
PayAI 402 (accepts):
type PaymentRequirement = { scheme: string; network: string; amount: string; asset: string; payTo: string; maxTimeoutSeconds?: number; extra?: { name?: string; version?: string; [k: string]: unknown }; }; // 402 body: { x402Version?, error?, resource?, accepts: PaymentRequirement[] }
EVVM 402 (options):
type PaymentOption = { id: string; type: string; chainId: number; evvmId: string; coreAddress: string; token: string; to?: string; suggestedNonce?: string; amount: string; priorityFee: string; executor: string | null; isAsyncExec: boolean; }; // 402 body: { resource, description?, to?, suggestedNonce?, options: PaymentOption[] }
For full code, see the reference paths at the top of this skill.
Homework for hackathon: agent-to-agent with Privy
The flows above use a browser wallet (human-in-the-loop). Participants can extend the app so an agent can pay autonomously using the Privy Agentic Wallets skill.
Leverage the Privy skill
- Skill: privy-io/privy-agentic-wallets-skill — create server wallets that AI agents control with policy guardrails; sign and send transactions via the Privy API (no user click).
- Install in project:
git clone https://github.com/privy-io/privy-agentic-wallets-skill.git .cursor/skills/privy
(or into
for OpenClaw). Add~/.openclaw/workspace/skills/privy
andPRIVY_APP_ID
from dashboard.privy.io.PRIVY_APP_SECRET
Homework tasks
-
Same protocol, different signer
Keep the x402 protocol (402 → build payload → sign → POST) unchanged. The only change is who signs: instead of
/signMessageAsync
in the browser, the agent path uses the Privy API to sign with a Privy server wallet (same message / typed data).signTypedDataAsync -
DHM agent payer
- Create a Privy server wallet on Base Sepolia (via Privy skill) with a policy that limits spending (e.g. max amount, or only EVVM Core + your x402 server).
- Implement an agent path: GET 402 from
→ build EVVM pay message (reuse/clinical/mri-slot
) → sign the message via Privy’s sign API (see Privy skill references) → POST tobuildEvvmPayMessageCoreDoc
with the same body./payments/evvm/dhm - Expose this as a small backend route or script that the agent calls (e.g. “pay for MRI slot as agent”), so the same resource can be unlocked without a connected browser wallet.
-
USDC agent payer (optional)
- Same idea for PayAI Echo: GET 402 → pick USDC option → build EIP-3009
→ sign via Privy’s sign typed data API (EIP-712) → sendTransferWithAuthorization
and retry.PAYMENT-SIGNATURE - Use a Privy server wallet with a policy that restricts to the PayAI/USDC flow if desired.
- Same idea for PayAI Echo: GET 402 → pick USDC option → build EIP-3009
-
Dual mode (stretch)
- In the UI or API, support both “Pay as me” (current wallet) and “Pay as agent” (Privy server wallet). Shared: 402 parsing and payload building; only the signer (browser vs Privy) differs.
Why this fits the skill
- The protocol (x402, EVVM pay, EIP-3009) stays the same; the skill above is the single source of truth for payloads and endpoints.
- The Privy skill adds how to get an agent-owned wallet and how to sign with it. Combining both skills gives hackathon participants a clear path: learn x402 from this skill, add autonomous payers using the Privy skill.