Digital Health
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 integra...
Description
name: clawhub-x402-payments description: 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.
ClawHub 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) | frontend/src/components/sections/USDCX402TestSection.tsx |
Config: frontend/src/config/contracts.ts (X402_USDC_ECHO_URL, USDC_BASE_SEPOLIA) |
| DHM (EVVM) | frontend/src/components/sections/X402TestSection.tsx |
server/src/index.ts (GET 402, POST /payments/evvm/dhm) |
| EVVM sign | frontend/src/lib/evvmSign.ts |
— |
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
GET <Echo URL>(e.g.https://x402.payai.network/api/base-sepolia/paid-content). -
Parse 402
- Prefer
PAYMENT-REQUIREDresponse header (base64-encoded JSON). - Fallback: response body may be JSON with
acceptsarray. - Type:
{ x402Version?, error?, resource?, accepts: Array<{ scheme, network, amount, asset, payTo, maxTimeoutSeconds?, extra? }> }.
- Prefer
-
Pick USDC option
- From
accepts, choose entry whereassetmatches USDC on Base Sepolia orextra.name === "USDC". - Use
amount,asset,payTo,extra.name/extra.versionfor EIP-712.
- From
-
Build EIP-3009 authorization
- Domain:
name=extra?.name ?? "USDC",version=extra?.version ?? "2",chainId= 84532,verifyingContract=asset. - Type:
TransferWithAuthorization:from,to,value,validAfter(0),validBefore(e.g. now + 300s),nonce(32 random bytes as hex). - Sign with
signTypedData(EIP-712).
- Domain:
-
Send payment and retry
- Build payload:
{ x402Version: 2, scheme, network, accepted: { scheme, network, amount, asset, payTo, maxTimeoutSeconds, extra? }, payload: { signature, authorization: message }, extensions: {} }. PAYMENT-SIGNATURE= base64(JSON.stringify(payload)).- Same URL:
GETwith headerPAYMENT-SIGNATURE: <base64>.
- Build payload:
-
Read result
- On 200: body is content. Optional
PAYMENT-RESPONSEorX-PAYMENT-RESPONSEheader (base64 JSON) may containtransaction(tx hash) etc.
- On 200: body is content. Optional
Config
VITE_X402_USDC_ECHO_URL: PayAI Echo endpoint (default: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
GET /clinical/mri-slot(or similar): if not paid, respond with402,PAYMENT-REQUIRED: 1, and body:resource,description,to(recipient address),suggestedNonceoptions: array with at least one option:id,type: "evvm_pay",chainId,evvmId,coreAddress,token(DHM),to,suggestedNonce,amount,priorityFee,executor(or null),isAsyncExec.
-
Payment execution
POST /payments/evvm/dhmbody:from,to,toIdentity,token,amount,priorityFee,executor,nonce,isAsyncExec,signature.
Server calls EVVM Corepay(...)with executor key, waits for receipt, returns{ status, txHash, content }.
Client steps
-
Request resource
GET <X402_SERVER_URL>/clinical/mri-slot. -
Detect 402
res.status === 402orres.headers.get("PAYMENT-REQUIRED") === "1". Parse body as JSON:{ resource, description?, to, suggestedNonce?, options }. -
Pick option
options.find(o => o.type === "evvm_pay" || o.id === "dhm-evvm") ?? options[0]. EnsuretoandsuggestedNonceare present. -
Build EVVM pay message
- Hash payload for Core:
keccak256(encodeAbiParameters("string, address, string, address, uint256, uint256", ["pay", to, toIdentity, token, amount, priorityFee])). - Message string:
evvmId, coreAddress, hashPayload, executor, nonce, isAsyncExec(comma-separated). - Use
buildEvvmPayMessageCoreDocfromfrontend/src/lib/evvmSign.tswith: evvmId, coreAddress, to, "", token, amount, priorityFee, executor, nonce, isAsyncExec.
- Hash payload for Core:
-
Sign and submit
signMessage(personal_sign) the message string.- POST to
POST <X402_SERVER_URL>/payments/evvm/dhmwith JSON body:from,to,toIdentity: "",token,amount,priorityFee,executor,nonce,isAsyncExec,signature. - Response 200:
content(unlocked resource),txHash.
Config
VITE_X402_SERVER_URL: DHM x402 server (e.g.https://evvm-x402-dhm.fly.devor localhost).- Server env:
EXECUTOR_PRIVATE_KEY,RPC_URL,RECIPIENT_ADDRESS,EVVM_ID,EVVM_CORE_ADDRESS,DHM_TOKEN_ADDRESS(seeserver/.env.example).
Checklist for adding or debugging
USDC (PayAI)
- 402 parsed from header or body;
acceptsused (notoptions). - EIP-712 domain and
TransferWithAuthorizationmatch USDC contract (name/version fromextraor "USDC"/"2"). -
PAYMENT-SIGNATUREis base64 JSON; same URL retried with GET + header. -
PAYMENT-RESPONSEdecoded when present for tx hash / receipt.
DHM (EVVM)
- 402 body has
options[].toandsuggestedNonce; client uses them in the signed message. - Message built with
hashDataForPayCore+buildEvvmMessageV3(see evvmSign.ts). - POST body matches server expectation (from, to, token, amount, nonce, executor, isAsyncExec, signature).
- Server has
EXECUTOR_PRIVATE_KEYand RPC to submitpay().
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~/.openclaw/workspace/skills/privyfor OpenClaw). AddPRIVY_APP_IDandPRIVY_APP_SECRETfrom dashboard.privy.io.
Homework tasks
-
Same protocol, different signer
Keep the x402 protocol (402 → build payload → sign → POST) unchanged. The only change is who signs: instead ofsignMessageAsync/signTypedDataAsyncin the browser, the agent path uses the Privy API to sign with a Privy server wallet (same message / typed data). -
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
/clinical/mri-slot→ build EVVM pay message (reusebuildEvvmPayMessageCoreDoc) → sign the message via Privy’s sign API (see Privy skill references) → POST to/payments/evvm/dhmwith the same body. - 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
TransferWithAuthorization→ sign via Privy’s sign typed data API (EIP-712) → sendPAYMENT-SIGNATUREand retry. - 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.
Reviews (0)
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!