mcpField
C
cryptorefills-x402

cryptorefills-x402

github.com/cryptorefillsOther094 installsUpdated 9w ago
Repository

About cryptorefills-x402

cryptorefills-x402 is a skill for AI agents that purchase digital products through Cryptorefills using USDC, without an account or API key. It supports Base and Solana payment flows, with payment-attestation checks and safeguards for signing and spending.

Key features

  1. 01Browse brands and product catalog
  2. 02Place orders using USDC
  3. 03Choose Base or Solana settlement
  4. 04Verify signed payment requirements
  5. 05Track order delivery

Use cases

  1. 01Autonomously procure gift cards
  2. 02Provision travel eSIMs
  3. 03Send programmatic rewards
  4. 04Make registry-driven Solana purchases

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/cryptorefills/agents --skill cryptorefills-x402

After installing, type/cryptorefills-x402in Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
endpoints.md
protocol.md
troubleshooting.md

Details

From the project README

Cryptorefills x402 — Autonomous Agent Commerce

Fully autonomous purchasing for AI agents with crypto wallets. No accounts, no OAuth, no API keys — just stablecoin and a signing key.

Built on the x402 protocol — HTTP 402 Payment Required as a machine-to-machine payment standard. Two settlement rails are supported: Base (USDC on Ethereum L2, default) and Solana (USDC SPL on Solana mainnet, opt-in).

For human-guided purchases with multiple payment methods, follow cryptorefills-buy instead. For browse-only catalog exploration, follow cryptorefills-catalog instead.

Choose your rail

RailHostPhase-1 headerWalletAssetNative gas
Base (default)x402.cryptorefills.comnone, or X-Preferred-Network: baseEVM, EIP-712 capableUSDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, chain 8453None — Cryptorefills relays
Solanax402.cryptorefills.comX-Preferred-Network: solanaSolana keypairUSDC SPL mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vNone — CDP facilitator pays SOL
Solana (host-pinned)solana.x402.cryptorefills.comomit, or solana onlySolana keypairsameNone

Default-rail recommendation. If your agent has wallets on both rails and no external constraint, use Base — broader EIP-712 tooling, longer payment window (~15 min vs Solana's ~60 s), and the same byte-identical contract that EVM-only agents have used since v1.0. Use Solana when the agent already runs there, when SOL avoidance matters, or when a registry / marketplace (e.g., pay-skills) demands a Solana-mainnet 402 challenge by default — in that case use the solana.x402.cryptorefills.com host so the rail is host-pinned and unambiguous.

When to Activate

  • Agent has a wallet on Base (USDC, EIP-712) or on Solana (USDC SPL, can partial-sign v0 transactions)
  • Task requires autonomous purchasing without human payment interaction
  • No account setup, OAuth flow, or API key management is desired
  • Keywords: "buy with USDC", "USDC SPL", "autonomous purchase", "agent commerce", "x402 payment", "programmatic gift card", "Solana mainnet-beta", "partial-sign", "VersionedTransaction", "compute-budget", "pay-skills registry", "no SOL needed", "no ETH needed"
  • Use case: automated gift card procurement, travel eSIM provisioning, programmatic rewards, registry-driven Solana payments

Requirements

Base rail
RequirementDetails
USDC on BaseChain 8453, contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
EIP-712 signingSign typed data for transferWithAuthorization (EIP-3009)
ETH for gasNot required — Cryptorefills relays the authorization on-chain
HTTP clientStandard REST API calls
AccountNone required
Solana rail
RequirementDetails
USDC SPL on Solana mainnetMint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v, 6 decimals
Solana keypair64-byte Ed25519 secret (Phantom / Solflare export format works)
Solana RPC accessFor fresh recentBlockhash. Default https://api.mainnet-beta.solana.com; any reliable RPC works
v0 transaction signingBuild a VersionedTransaction with the canonical 3-instruction shape and partial-sign — see protocol.md
SOL for gasNot required — the CDP facilitator pays SOL gas from its pool
HTTP clientStandard REST API calls
AccountNone required

Spending Safeguards

Autonomous agents spending real money require strict controls:

  • Set a per-session spending limit before starting (default: $100 max per session, $50 max per transaction — increase only if the user explicitly requests it).
  • Verify the order amount from the 402 response before signing — never sign blindly.
  • Use a dedicated wallet per rail with only the necessary stablecoin balance.
  • Never log, expose, or share the wallet's private key / secret — use environment variables or a secret manager.
  • Log every transaction: order_id, product, amount, wallet address, rail, tx signature/hash.
  • Never sign for more than maxAmountRequired in the 402 response.
  • Authenticate the 402 before signing anything. payTo is a per-session deposit wallet — the manifest does not list deposit wallets, so an allowlist is impossible. Instead every 402 carries X-Payment-Required-Signature, a JWS (ES256) signed by the gateway. Verify it with the keys from the manifest's attestation.keys / attestation.jwks_uri (same origin as the manifest) and require: iss == the origin you POSTed to, sid == the X-Session-Id header, pr_sha256 == base64url(sha256(PAYMENT-REQUIRED header value exactly as received)), pay_to/network == the decoded accepts[0] values, exp in the future. If the header is absent or any check fails, reject the 402 and do not sign. Full recipe + verify402() snippet: references/protocol.md → Verifying the 402 attestation.

Solana-specific:

  • payTo is the OWNER pubkey, NEVER an ATA. Derive the recipient ATA from (payTo, mint). Signing a transfer treating payTo as an ATA will fail.
  • Verify extra.feePayer matches the value from PAYMENT-REQUIRED before partial-signing — and use it as the transaction's payerKey.
  • Re-fetch recentBlockhash immediately before partial-signing. The validity window is ~60 s; the gateway clamps maxTimeoutSeconds to 60 to flag this. Do not insert user prompts between Phase 1 and Phase 2 on Solana.
  • Assert the built transaction has exactly 3 instructions (compute-unit limit, compute-unit price, TransferChecked) before serializing.
  • Use SPL TransferChecked (tag 0x0c), not Transfer (0x03). Plain Transfer is rejected by CDP with unknown_error.

General:

  • Gift card codes (voucher_code) are cash-like — store securely, never share publicly or write to logs.
  • Digital goods are non-refundable once delivered.
  • Terms: https://www.cryptorefills.com/terms/

Manifests are served at /.well-known/x402.json on each host and carry an attestation block (signing keys, jwks_uri, verify procedure). Read the manifest at the same host you used for Phase 1 — the attestation's iss must equal that origin, and jwks_uri must be same-origin with it.

Always send Phase 2 to the same host as Phase 1. The session backing X-Session-Id is host-affine; cross-host POST risks a session lookup miss.

Core Workflow

GET  /v1/brands          → discover brands
GET  /v1/catalog         → get products and prices
POST /v1/orders          → 402 + PAYMENT-REQUIRED + X-Session-Id + X-Payment-Required-Signature
   ↓ verify the attestation (JWS) — reject the 402 if it is missing or invalid
   ↓ sign (EIP-712 for Base, partial-sign v0 tx for Solana)
POST /v1/orders          → submit with PAYMENT-SIGNATURE [+ X-Session-Id] → 200
GET  /v1/orders/{id}     → poll until delivered

The flow, REST contract, and order/delivery model are identical on both rails. Only Phase-1 header selection, the extra block on PAYMENT-REQUIRED, and the signing primitive differ.

Step-by-Step

1. Browse Brands
GET /v1/brands?country_code=us

Returns available brands for the country. Each entry includes brand_name, family, category, min, max.

Country codes are lowercase ISO 3166-1 Alpha-2 (us, it, br) regardless of rail. If receiving a country code from the catalog or buy skills (which use uppercase), convert to lowercase before using x402 endpoints.

2. Get Catalog
GET /v1/catalog?country_code=us&brand_name=Amazon.com

Returns products with product_id, is_range, currency, price_usdc, and either denomination (fixed) or min_value/max_value (range).

  • Fixed products (is_range: false): use product_id directly. Omit product_value. Face value is denomination in currency units.
  • Range products (is_range: true): set items[].product_value to the desired amount, in the brand's local currency (NOT USD). E.g. for a €5 Amazon.nl card with currency: "EUR", send product_value: 5. The 402 response will quote the USDC amount to actually settle (typically price_usdc ± a small spread).
Pre-flight: Check stablecoin balance

Before ordering, verify your wallet has sufficient stablecoin on the chosen rail:

  • Base: call balanceOf(walletAddress) on 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913.
  • Solana: call getTokenAccountBalance on the sender ATA — getAssociatedTokenAddress(mint=EPjF…WEGGkZwyTDt1v, owner=agent). No SOL balance needed.

The price_usdc from the catalog is indicative; the exact amount is confirmed in the 402 response.

3. Create Order — Phase 1 (Get Payment Requirements)

Base (default):

curl -X POST https://x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "items": [{
      "product_id": "abc123",
      "beneficiary_account": "[email protected]",
      "product_value": 25
    }]
  }'
# → HTTP 402 with PAYMENT-REQUIRED, X-Session-Id and X-Payment-Required-Signature headers

Solana (header opt-in):

curl -X POST https://x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "X-Preferred-Network: solana" \
  -d '<same body>'

Solana (host-pinned):

curl -X POST https://solana.x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -d '<same body>'

product_value is required for range products; omit for fixed-denomination products. Optional: add "callback_url": "https://..." for webhook delivery notifications instead of polling.

Capture three response headers:

  • PAYMENT-REQUIRED — base64url-encoded JSON of payment requirements (per-rail shape below).
  • X-Session-Id — UUID. Echo on Phase 2 (required for Solana, optional for Base).
  • X-Payment-Required-Signature — JWS attesting the two headers above (iss, sid, pr_sha256, pay_to, network, exp). Verify before signing — see references/protocol.md. Treat a missing header as a failed 402.
PAYMENT-REQUIRED — Base example
{
  "x402Version": 2,
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "maxAmountRequired": "25000000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "extra": { "name": "USD Coin", "version": "2", "decimals": 6 },
    "payTo": "0x...",
    "description": "$25.00 Amazon Gift Card"
  }],
  "expiresAt": 1830000000
}
Solana deltas vs Base (full Solana example in protocol.md)
BaseSolana
networkeip155:8453solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (full CAIP-2)
assetEVM contractSPL mint, base58 (EPjF…WEGGkZwyTDt1v for USDC)
extra{name, version, decimals}{feePayer} — use as payerKey
payTorecipient walletrecipient OWNER pubkey (ATA derived facilitator-side)
expiresAt window~15 minutes~60 seconds
4. Sign Payment
Base — EIP-712 / EIP-3009

Sign typed data for transferWithAuthorization.

Domain: USDC contract on Base. Types: TransferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce).

FieldValue
fromYour wallet address
topayTo from 402 response
valuemaxAmountRequired from 402 response
validAfter0
validBeforeexpiresAt from the 402 response (Unix seconds)
nonceRandom 32-byte hex

See references/protocol.md for exact EIP-712 typed data structure.

Solana — partial-sign v0 transaction

Build a v0 VersionedTransaction with exactly 3 instructions, in order: ComputeBudget set-limit (200_000 units) → ComputeBudget set-price (1000 microLamports) → SPL createTransferCheckedInstruction (tag 0x0c, NOT Transfer 0x03). Use mint = asset, senderAta = getAssociatedTokenAddress(mint, signer), recipientAta = getAssociatedTokenAddress(mint, payTo) (where payTo is the owner), payerKey = extra.feePayer, fresh recentBlockhash from a Solana RPC. Partial-sign with tx.sign([signer]) — leave the feePayer slot for CDP. Serialize ≤ 1232 bytes.

See references/protocol.md for the full recipe.

5. Complete Order — Phase 2

Re-POST the same body with PAYMENT-SIGNATURE. Echo X-Session-Id from Phase 1 (required for Solana).

Base
curl -X POST https://x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "PAYMENT-SIGNATURE: <base64url-encoded JSON>" \
  -d '<same body as phase 1>'
# → HTTP 200

PAYMENT-SIGNATURE payload (then base64url-encode the JSON):

{
  "x402Version": 2,
  "scheme": "exact",
  "network": "eip155:8453",
  "payload": {
    "signature": "0x...",
    "authorization": {
      "from": "0xYourWallet",
      "to": "0xPayToAddress",
      "value": "25000000",
      "validAfter": "0",
      "validBefore": "1830000000",
      "nonce": "0xRandomBytes32"
    }
  }
}
Solana
curl -X POST https://x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "PAYMENT-SIGNATURE: <base64url-encoded JSON>" \
  -H "X-Session-Id: <UUID from Phase 1 response>" \
  -d '<same body as phase 1>'
# → HTTP 200

PAYMENT-SIGNATURE payload (base64url-encode the outer JSON; the inner payload.transaction is plain base64):

{
  "x402Version": 2,
  "scheme": "exact",
  "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  "payload": {
    "transaction": "<plain-base64 of serialized v0 transaction>"
  }
}

Encoding rule. The outer PAYMENT-SIGNATURE wrapper is base64url for both rails. Only the inner payload.transaction field on Solana is plain base64 (Solana ecosystem convention; opaque pass-through to the facilitator). Don't unify them.

Server responds with HTTP 200:

{
  "order_id": "cr_abc123",
  "status": "processing",
  "estimated_delivery_seconds": 60,
  "poll_url": "/v1/orders/cr_abc123"
}
6. Track Delivery
GET /v1/orders/{order_id}

Poll every 5–10 seconds until status is completed or failed. Stop polling after 10 minutes and report the order as potentially stuck — contact [email protected] with the order_id.

When completed, the deliveries array contains:

  • voucher_code — gift card redemption code
  • pin — PIN if required
  • url — redemption URL if applicable
  • brand_name, product_name — what was purchased

Worked Example: €5 Amazon.nl gift card on Solana

End-to-end check that the per-step instructions compose. Real numbers, anonymized wallet:

# 1. Discover Amazon.nl
curl 'https://x402.cryptorefills.com/v1/brands?country_code=nl' | jq '.[] | select(.family|test("Amazon"))'
#   → {"brand_name":"Amazon.nl","family":"Amazon.nl","category":"e-commerce", ...}

# 2. Find a range product priced in EUR
curl 'https://x402.cryptorefills.com/v1/catalog?country_code=nl&brand_name=Amazon.nl' | jq '.[0]'
#   → {"product_id":"amzn-nl-range","is_range":true,"currency":"EUR",
#      "min_value":5,"max_value":500,"price_usdc":"5.42", ...}
#   1 EUR isn't offered (min is 5 EUR) — pick 5 EUR.

# 3. Phase 1 — opt into Solana
curl -i -X POST https://x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "X-Preferred-Network: solana" \
  -d '{"email":"[email protected]",
       "items":[{"product_id":"amzn-nl-range",
                 "beneficiary_account":"[email protected]",
                 "product_value":5}]}'
#   → HTTP/1.1 402
#      X-Session-Id: 3f9c1a8b-...           ← capture, echo on Phase 2
#      PAYMENT-REQUIRED: <base64url JSON>   ← decode for accepts[0]
#      X-Payment-Required-Signature: <JWS>  ← verify first (protocol.md → Verifying the 402 attestation)
#
#   accepts[0] decoded:
#     network:           solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
#     asset:             EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
#     maxAmountRequired: "5420000"          ← 5.42 USDC
#     payTo:             <owner pubkey>     ← derive recipient ATA from this
#     extra.feePayer:    <CDP pubkey>       ← use as transaction.payerKey
// 4. Decode PAYMENT-REQUIRED, verify budget, build the partial-signed v0 tx
import {ComputeBudgetProgram, Connection, Keypair, PublicKey,
        TransactionMessage, VersionedTransaction} from '@solana/web3.js';
import {createTransferCheckedInstruction, getAssociatedTokenAddress} from '@solana/spl-token';
import bs58 from 'bs58';

// 4a. Parse the 402 response
const paymentRequired = JSON.parse(
  Buffer.from(phase1Headers['payment-required'], 'base64url').toString());
const sessionId = phase1Headers['x-session-id'];
const {payTo, asset, maxAmountRequired, extra} = paymentRequired.accepts[0];

// 4b. Spending-safeguard checks BEFORE signing
const SPEND_LIMIT_ATOMIC = BigInt(50_000_000);              // $50 = 50e6 atomic USDC
if (BigInt(maxAmountRequired) > SPEND_LIMIT_ATOMIC) throw new Error('over budget');
if (asset !== 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') throw new Error('wrong mint');

// 4c. Build the transaction
const signer       = Keypair.fromSecretKey(bs58.decode(process.env.SOLANA_SIGNER_KEY));
const conn         = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
const mint         = new PublicKey(asset);
const owner        = new PublicKey(payTo);                  // OWNER, not an ATA
const feePayer     = new PublicKey(extra.feePayer);         // CDP signs this slot

const senderAta    = await getAssociatedTokenAddress(mint, signer.publicKey);
const recipientAta = await getAssociatedTokenAddress(mint, owner);
const {blockhash}  = await conn.getLatestBlockhash('finalized');  // 'finalized' is older but accepted by all validators — reduces timing sensitivity

const message = new TransactionMessage({
  payerKey:        feePayer,
  recentBlockhash: blockhash,
  instructions: [
    ComputeBudgetProgram.setComputeUnitLimit({units: 200_000}),
    ComputeBudgetProgram.setComputeUnitPrice({microLamports: 1000}),
    createTransferCheckedInstruction(senderAta, mint, recipientAta,
                                     signer.publicKey, BigInt(maxAmountRequired), 6),
  ],
}).compileToV0Message();

const tx = new VersionedTransaction(message);
tx.sign([signer]);                                    // partial — leaves feePayer slot
// Assert on the pre-compilation array — MessageV0 exposes `compiledInstructions`, not `instructions`.
// Reading `tx.message.instructions` throws TypeError. Use the source array passed to TransactionMessage.
if (message.version !== 0) throw new Error('expected v0 message');
if (tx.message.compiledInstructions.length !== 3) throw new Error('expected exactly 3 instructions');
if (tx.serialize().length > 1232) throw new Error('tx too large');

const txB64       = Buffer.from(tx.serialize()).toString('base64');     // inner: plain base64
const inner       = {x402Version: 2, scheme: 'exact',
                     network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
                     payload: {transaction: txB64}};
const headerValue = Buffer.from(JSON.stringify(inner)).toString('base64url');  // outer: base64url
# 5. Phase 2 — same host as Phase 1, echo X-Session-Id from step 3
curl -X POST https://x402.cryptorefills.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "PAYMENT-SIGNATURE: <headerValue from step 4c>" \
  -H "X-Session-Id: <sessionId from step 4a>" \
  -d '<same body as Phase 1>'
#   → HTTP/1.1 200
#      {"order_id":"cr_xxx","status":"processing","poll_url":"/v1/orders/cr_xxx", ...}

# 6. Poll
curl https://x402.cryptorefills.com/v1/orders/cr_xxx
#   → {"status":"completed",
#      "deliveries":[{"voucher_code":"XXXX-YYYY-ZZZZ","brand_name":"Amazon.nl", ...}]}

If steps 1–6 don't compose for your scenario, the failure is documented in references/troubleshooting.md.

Field Name Mapping (x402 vs MCP)

If switching between x402 and MCP skills, note these field name differences:

Conceptx402 (this skill)MCP (cryptorefills-buy)
Gift card codevoucher_codedeliverable.pin_code
PINpin (separate field)included in pin_code
Order statusstatus (single field)order_state + payment_state
Order ID formatcr_ prefixedUUID

Critical Gotchas

Both rails
  • Country codes: lowercase for x402 endpoints (us, not US). MCP skills use uppercase — different endpoint, different rule.
  • Brand names: case-sensitive — use exact values from /v1/brands response.
  • X-Preferred-Network value: case-sensitive lowercase — base or solana only. Base or Solana (capitalized) returns 400 invalid_network on the multi-rail host.
  • Amount precision: use the exact maxAmountRequired from 402 — do not round or modify.
  • Outer encoding: PAYMENT-SIGNATURE outer wrapper is base64url for both rails (matches the gateway's decoder).
  • No rail mixing: a session is pinned to the rail chosen on Phase 1. Don't try to switch rails on Phase 2.
  • Same-host Phase 2: POST Phase 2 to the same host you used for Phase 1. Sessions are host-affine.
  • Unsigned 402: a 402 without X-Payment-Required-Signature, or whose signature fails any check, must be rejected — never fall back to "the TLS connection looked fine". Hash the PAYMENT-REQUIRED header string as received; do not re-serialise the decoded JSON.
Base
  • USDC contract: must be exactly 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 on Base.
  • Chain ID: must be 8453 (Base mainnet). Wrong chain = invalid signature.
  • Payment timeout: the 402 expiresAt field gives the deadline (~15 min). Sign and resubmit before it expires.
  • Nonce: must be unique per transaction. Cryptographically random 32 bytes.
  • EIP-712 domain: must match the USDC contract's domain separator on Base.
Solana
  • Network string: must be the full CAIP-2 solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp, not "solana" or "solana-mainnet".
  • payTo is the OWNER pubkey, not an ATA. The facilitator derives the recipient ATA from (owner, mint).
  • Use TransferChecked (0x0c), not Transfer (0x03). CDP rejects the latter with unknown_error.
  • Exactly 3 instructions, in order: ComputeBudget set-limit, ComputeBudget set-price, TransferChecked. CDP enforces a 3-to-6 range.
  • payerKey = extra.feePayer from PAYMENT-REQUIRED. CDP signs that slot at settle and pays SOL gas.
  • Tx packet limit: 1232 bytes. The canonical 3-instruction shape fits comfortably.
  • Blockhash window: ~60 s. Re-fetch immediately before partial-signing; do not insert user prompts between Phase 1 and Phase 2.
  • Echo X-Session-Id from the Phase 1 response on Phase 2 — required (the SPL transaction is opaque so the gateway has no other way to look up the session).
  • Inner encoding: payload.transaction inside the PAYMENT-SIGNATURE JSON is plain base64 (Solana convention) — only the outer wrapper is base64url.

References

Load references only when you need deeper detail than what's in this skill file.

FileLoad when...Content
references/protocol.mdYou need the exact signing recipe — EIP-712 domain/types for Base, or the full VersionedTransaction build for Solana — or the 402 attestation verifierTwo-phase flow, headers, per-rail PAYMENT-REQUIRED shape, X-Payment-Required-Signature verification (verify402), Base EIP-712 / EIP-3009 detail, Solana 3-instruction recipe
references/endpoints.mdYou need exact request/response schemas for an API callREST API reference, Hosts table, full headers list, error-response codes
references/troubleshooting.mdA request fails or returns an unexpected statusPer-rail error catalogue and recovery patterns

Comments

Markdown supported