정보
이 스킬은 초기 402 응답부터 서명 검증 및 온체인 결제에 이르는 전체 결제 흐름을 실행하여 x402 결제 클라이언트의 종단간 테스트를 수행합니다. 개발자가 새로운 클라이언트를 검증하고, 결제 문제를 디버깅하며, 테스트넷 페이실리테이터를 대상으로 CI에서 적합성 테스트를 실행할 수 있도록 설계되었습니다. 실제 가치를 라우팅하기 전에 결제 엔드포인트가 올바르게 작동하는지 확인하는 데 사용하세요.
빠른 설치
Claude Code
추천npx skills add pjt222/agent-almanac -a claude-code/plugin add https://github.com/pjt222/agent-almanacgit clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/test-x402-payment-clientClaude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요
문서
Test an x402 Payment Client
Validate that an x402 payment client completes the full protocol loop against a
live endpoint — not just that the endpoint answers a 402, but that a signed
payment can actually be built, submitted, verified, settled on chain, and (where
offered) checked for offer/receipt conformance. A well-formed challenge is a
claim; a completed settlement is the proof.
The header names, scheme semantics, and network identifiers below are from the
x402 v2 specification and its transport and scheme specs
(specs/x402-specification-v2.md, specs/transports-v2/http.md,
specs/transports-v2/mcp.md, specs/schemes/exact/scheme_exact_evm.md in
coinbase/x402). Treat those specs as the
source of truth over any single vendor's docs.
When to Use
- Validating a new x402 client or SDK before shipping it
- Debugging a payment that never completes even though the endpoint returns a
well-formed
402(the failure is usually in the header the client did not read, or the scheme it did not recognize) - Running x402 conformance in CI against a testnet facilitator on every build
- Verifying an endpoint actually accepts payment — not just that it answers — before routing real value to it
- Checking that signed offers or receipts an endpoint serves conform to the spec
Inputs
- Required: The endpoint URL under test (an HTTP resource that answers
402). - Required: A funded test wallet and its signer. For EVM start on Base
Sepolia (
eip155:84532); for SVM start on Solana Devnet. Testnet USDC is free from a faucet. - Optional: A facilitator base URL if the client uses one directly (default: whatever the endpoint's challenge names).
- Optional:
mainnet_optin(boolean, defaultfalse) — only whentruedoes the procedure move real funds. The final mainnet step states its own cost. - Optional: Output format for the conformance report (
json,markdown).
Procedure
Step 1: Request the resource unpaid and capture the challenge
Send a plain request and read the 402. The machine-readable terms ride the
PAYMENT-REQUIRED response header as base64-encoded JSON; the body is
human-oriented and must not be the client's source of truth.
curl -sD challenge.txt -o /dev/null https://x402.example.testnet/resource
# base64-decode the PAYMENT-REQUIRED header value into terms.json
grep -i '^payment-required:' challenge.txt | sed 's/^[^:]*:[[:space:]]*//' | tr -d '\r' | base64 -d | jq . > terms.json
The decoded PaymentRequired object carries x402Version (must be 2), one
or more accepts entries (each a PaymentRequirements), and any advertised
extensions. Each accepts entry carries scheme, network (CAIP-2, e.g.
eip155:84532), the token asset, the amount in atomic units, payTo, and
maxTimeoutSeconds.
Expected: HTTP 402; a PAYMENT-REQUIRED header that base64-decodes to JSON
in terms.json with x402Version: 2 and at least one accepts entry carrying
scheme, network, asset, amount, payTo, and maxTimeoutSeconds.
On failure: If there is no PAYMENT-REQUIRED header, the endpoint is not
serving v2 terms a client can act on — stop and report that (a 402 body with
no header is the single most common silent break). If the header is present but
does not base64-decode to JSON, record it as malformed and stop.
Step 2: Select an accepts entry the client actually supports
Pick the entry whose scheme and network the client implements. The exact
scheme is the baseline (EIP-3009 TransferWithAuthorization on EVM;
TransferChecked for SPL tokens on SVM). A challenge may also offer other
schemes (for example upto for variable amounts, or a batch-settlement scheme);
a client built only for exact must skip those entries rather than misread them.
Then gate the network: unless mainnet_optin is true, the selected entry must
be on a testnet the spec's network list names (Base Sepolia eip155:84532,
Avalanche Fuji eip155:43113, Solana Devnet
solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1). This is the step that makes
testnet-first a procedure rather than a preference.
# pick the first `exact` entry on a network the client implements ($supported: edit to
# match the client), then gate it: a mainnet network passes only with MAINNET_OPTIN=true
jq -e --arg optin "${MAINNET_OPTIN:-false}" '
["eip155:84532","eip155:43113","solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"] as $testnets
| ["eip155:","solana:"] as $supported
| [.accepts[] | select(.scheme=="exact" and (.network as $n | any($supported[]; . as $p | $n|startswith($p))))] as $ok
| if ($ok|length)==0 then error("unsupported-scheme: " + ([.accepts[] | .scheme+"@"+.network]|join(",")))
else ([$ok[] | select((.network|IN($testnets[])) or $optin=="true")][0]
// error("mainnet-not-opted-in: " + $ok[0].network)) end' terms.json > selected.json
Expected: Exactly one supported accepts entry in selected.json; its
scheme, network, asset, amount, payTo, and maxTimeoutSeconds read
into the signing step. A mainnet network appears there only when
mainnet_optin is true.
On failure: If no offered entry matches a scheme+network the client
supports, that is a real interop result — record unsupported-scheme with the
schemes offered, and stop. Do not coerce an upto or batch entry into an
exact signature. If the only matching entry is on a mainnet network and
mainnet_optin is false, record mainnet-not-opted-in and stop — that is the
gate working, not a defect.
Step 3: Build and sign the payment authorization
Construct the scheme-specific authorization over the selected entry and sign it.
For exact on EVM the recommended mechanism is an EIP-3009
TransferWithAuthorization (EIP-712 typed-data signature) for the exact amount
to payTo, with a fresh random nonce and a validBefore inside the entry's
maxTimeoutSeconds — but it is not the only one. The scheme spec
(specs/schemes/exact/scheme_exact_evm.md) also defines Permit2 as the
universal fallback for tokens without EIP-3009 and ERC-7710 for smart accounts,
selected by assetTransferMethod in the payload (default order: EIP-3009, then
Permit2). Echo every extension the server advertised: the client must include
at least the info it received and may append, but may not delete or overwrite
it. Write the result to payload.json for the next step.
Expected: A PaymentPayload in payload.json with x402Version: 2, the
selected entry under accepted, and a scheme-specific payload carrying the
signature and authorization; advertised extensions echoed intact.
On failure: If signing throws on the address or amount, check the asset
checksum and that the amount is an atomic-unit string, not a decimal. A
decimal-typed amount underpays by a factor of the token's decimals and is a
frequent silent bug.
Step 4: Retry with the signed payment
Base64-encode the PaymentPayload and resend the same request with it in the
PAYMENT-SIGNATURE header. The endpoint (or its facilitator) verifies the
signature and settles.
curl -s -H "PAYMENT-SIGNATURE: $(base64 -w0 payload.json)" \
https://x402.example.testnet/resource -D headers.txt -o body.json
Expected: HTTP 200. The response carries a settlement result in the
base64-encoded PAYMENT-RESPONSE header (SettleResponse: success: true, a
non-empty transaction hash, and the network; amount and payer are
optional and may be omitted).
On failure: A repeated 402 means verification refused the payment — inspect
the reason. A common cause is the client sending the legacy X-PAYMENT header
name where the endpoint only reads PAYMENT-SIGNATURE (or the reverse); try the
other name once and record which the endpoint accepts. If the response is 200
but carries no settlement transaction, treat it as settled-unverified and
proceed to Step 5 before trusting it.
Step 5: Verify the settlement independently on chain
Do not trust the endpoint's own word that money moved. Take the transaction
hash from the SettleResponse and confirm it on chain: correct payTo, correct
asset, amount within what was authorized, and finality.
# EVM: fetch the receipt from a Base Sepolia RPC and read the Transfer log
curl -s -X POST https://sepolia.base.org -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["<transaction>"]}' \
| jq '{status: .result.status, block: .result.blockNumber, logs: .result.logs}'
# status 0x1 = success; the ERC-20 Transfer log's topics[2] is the recipient
# (must equal payTo, zero-padded) and its data is the value in atomic units.
# Finality: compare .result.blockNumber against eth_blockNumber for the
# confirmations you require on that network.
Expected: The on-chain transfer matches payTo, asset, and the authorized
amount, and has reached finality on the stated network.
On failure: If the hash is absent, unconfirmed, or pays a different address
or amount than authorized, record settlement-mismatch — the endpoint claimed a
settlement the chain does not show. This is the failure the whole procedure
exists to catch.
Step 6 (variant): MCP transport
If the client speaks x402 over MCP rather than HTTP, the same loop applies with
different envelopes (specs/transports-v2/mcp.md): the payment terms arrive in
a tool result with isError: true, in result.structuredContent (primary)
and result.content[0].text (JSON fallback), instead of a PAYMENT-REQUIRED
header; the signed payment is sent in the request's _meta["x402/payment"]
instead of a PAYMENT-SIGNATURE header; and the settlement comes back in the
result's _meta["x402/payment-response"]. The scheme, signing, and on-chain
verification steps are unchanged. Treat this as a transport variant, not the
primary path.
Expected: Terms parsed from result.structuredContent of an isError: true
tool result; payment sent in _meta["x402/payment"]; settlement read from
_meta["x402/payment-response"] and verified on chain as in Step 5.
On failure: If an isError: true result carries no structuredContent or
content[0].text that decodes to PaymentRequired terms, the server is not
advertising x402 over this transport — fall back to the HTTP path or stop.
Validation
- The
402carried aPAYMENT-REQUIREDheader that base64-decoded to JSON withx402Version: 2and at least one completeacceptsentry. - The client selected a scheme+network it supports and refused the rest rather than misreading them.
- The signed authorization used an atomic-unit amount and echoed all advertised extensions.
- The retry with
PAYMENT-SIGNATUREreturned200with aSettleResponsecarrying a real transaction hash. - The settlement was confirmed independently on chain (address, asset, amount, finality) — not taken on the endpoint's word.
- Any signed offers in the challenge or receipts in the response were
checked against the
offer-receiptextension (specs/extensions/extension-offer-and-receipt.md): offers live inextensions["offer-receipt"].info.offers[], the receipt inextensions["offer-receipt"].info.receipt. For an EIP-712 signature, recover the signer from the signature; for a JWS compact serialization, resolve the header'skid(a DID URL) to the issuer's key and verify over the complete payload. In both cases confirm the signer is authorised forresourceUrl, the required fields are present (offer:version,resourceUrl,scheme,network,asset,payTo,amount; receipt:version,network,resourceUrl,payer,issuedAt), and anyvalidUntilhas not passed.
Common Pitfalls
- Reading the body, not the header: the actionable terms are in the
base64
PAYMENT-REQUIREDheader; the402body is human text. A client that parses the body will miss or mis-handle the realaccepts. - Header-name drift:
PAYMENT-SIGNATURE(v2) versus the legacyX-PAYMENTname breaks otherwise-correct clients silently — a well-signed payload under the wrong header just yields another402. Test both names and record which the endpoint honors. - Decimal amounts: x402 amounts are atomic-unit strings. Signing a decimal-typed value underpays by the token's decimal factor with no visible error until settlement.
- Trusting a 200: a
200with no settlement transaction, or with a hash that pays the wrong address, is not a completed payment. Always verify on chain before treating the resource as paid for. - Coercing an unsupported scheme: forcing an
uptoor batch-settlement entry into anexactsignature produces an invalid payment. Skip unsupported schemes; do not reshape them. - Testing on mainnet first: default to a testnet (Base Sepolia
eip155:84532, Solana Devnet). Step 2's gate refuses a mainnetnetworkunlessmainnet_optinistrue; only set it once the full loop passes on testnet.
Examples
Testnet (primary). Point the client at the coinbase/x402 reference example
server, or any facilitator's testnet endpoint, on Base Sepolia
(eip155:84532). Fund the test wallet from a testnet USDC faucet and run
Steps 1–5. This exercises the entire loop end to end with no real value at risk
and is the example to wire into CI.
Mainnet settlement (explicit opt-in). Only with mainnet_optin: true: run
the same loop against a live mainnet endpoint to confirm real settlement.
scvd.store is one such endpoint: its lowest-priced resource settles for
$0.001 USDC on Base or Solana and returns a signed receipt
(GET https://scvd.store/api/buy/spot_check, verify at
https://scvd.store/api/verify/{id}), which makes it a known-good mainnet
target when you want to prove the client against real money. The same operator
runs a free conformance checker for offer/receipt artifacts
(POST https://scvd.store/api/conformance/v1) and a challenge-shape preflight
(POST https://scvd.store/api/preflight/v1); both are optional and accept any
issuer's artifact. This step moves real funds; run it deliberately, not in CI.
Related Skills
test-a2a-interop— protocol-conformance testing for A2A agents; same posture (drive the real loop, validate against the spec) on a different rail.build-custom-mcp-server— relevant when the client under test speaks x402 over the MCP transport variant in Step 6.
GitHub 저장소
자주 묻는 질문
test-x402-payment-client Skill이란 무엇인가요?
test-x402-payment-client은(는) pjt222이(가) 만든 Claude Skill입니다. Skill은 Claude가 필요할 때 불러오는 지침과 리소스를 묶어 추가 프롬프트 없이 test-x402-payment-client 관련 작업을 수행할 수 있게 합니다.
test-x402-payment-client은(는) 어떻게 설치하나요?
이 페이지의 설치 명령을 사용하세요. test-x402-payment-client을(를) Claude Code 플러그인으로 추가하거나 저장소를 skills 디렉터리에 복제한 다음 Claude를 다시 시작해 Skill을 불러옵니다.
test-x402-payment-client은(는) 어떤 카테고리에 속하나요?
test-x402-payment-client은(는) 테스팅 카테고리에 속합니다.
test-x402-payment-client은(는) 무료로 사용할 수 있나요?
네. test-x402-payment-client은(는) AIMCP에 등록되어 있으며 무료로 설치할 수 있습니다.
연관 스킬
이 Claude Skill은 MMLU, GSM8K를 포함한 60개 이상의 표준화된 학술 과제에서 LLM 성능을 벤치마크하기 위해 lm-evaluation-harness를 실행합니다. 개발자들이 모델 품질을 비교하고, 학습 진행 상황을 추적하거나 학술 결과를 보고할 수 있도록 설계되었습니다. 이 도구는 HuggingFace와 vLLM 모델을 포함한 다양한 백엔드를 지원합니다.
이 스킬은 cron 표현식을 사용하여 Worker를 스케줄링하기 위한 Cloudflare Cron Triggers 구현에 관한 포괄적인 지식을 제공합니다. 주기적 작업, 유지보수 작업, 자동화된 워크플로우 설정 방법을 다루며, 잘못된 cron 표현식이나 시간대 문제 같은 일반적인 이슈들을 해결하는 방법을 포함합니다. 개발자들은 이를 통해 스케줄된 핸들러 구성, cron 트리거 테스트, Workflows 및 Green Compute와의 연동 작업을 수행할 수 있습니다.
이 Claude Skill은 Python 스크립트를 통해 로컬 웹 애플리케이션을 테스트하기 위한 Playwright 기반 툴킷을 제공합니다. 프론트엔드 검증, UI 디버깅, 스크린샷 캡처, 로그 확인 기능을 지원하며 서버 라이프사이클을 관리합니다. 브라우저 자동화 작업에 사용하되 컨텍스트 오염을 방지하기 위해 소스 코드를 읽지 않고 스크립트를 직접 실행하세요.
이 스킬은 테스트 통과를 확인한 후 체계적인 통합 옵션을 제시하여 개발자가 완성된 작업을 마무리하도록 돕습니다. 구현이 완료된 후 머지, PR 생성, 브랜치 정리와 같은 워크플로우를 안내합니다. 코드가 준비되고 테스트가 완료되었을 때 개발 프로세스를 체계적으로 마무리하기 위해 사용하세요.
