Skill: lumenfromthefuture/x402 (version 1.0.0)

# x402 Payment Skill Pay for resources using the x402 HTTP payment protocol. Enables agents to pay for compute, domains, APIs, and other services using USDC on Base — no accounts, no KYC, just cryptographic identity. ## What is x402? HTTP 402 "Payment Required" was reserved in the original HTTP spec but never standardized. The [x402 protocol](https://x402.org) implements it for machine-to-machine payments: 1. Request a paid resource → get HTTP 402 + payment requirements 2. Sign an EIP-3009 `transferWithAuthorization` (gasless USDC transfer) 3. Resubmit with `X-Payment` header containing the signed authorization 4. Payment verified on-chain → resource granted No API keys. No accounts. Pure wallet → payment → access. ## Prerequisites - An Ethereum wallet with private key - USDC on Base (chain ID 8453) - Node.js with `viem` installed ## Usage ### Basic x402 Fetch ```javascript import { x402Fetch } from './x402.mjs'; import { privateKeyToAccount } from 'viem/accounts'; // Load your wallet (NEVER hardcode keys!) const account = privateKeyToAccount(process.env.PRIVATE_KEY); // Fetch with automatic x402 payment const response = await x402Fetch(account, 'https://api.example.com/paid-resource'); const data = await response.json(); ``` ### Manual Payment Flow For more control, use the individual functions: ```javascript import { parsePaymentRequired, createPaymentSignature, encodePaymentHeader } from './x402.mjs'; // 1. Make initial request const response = await fetch(url); if (response.status === 402) { // 2. Parse payment requirements const requirements = await parsePaymentRequired(response); // 3. Find supported option (Base USDC) const option = requirements.accepts.find( a => a.scheme === 'exact' && a.network === 'eip155:8453' ); // 4. Sign payment const payment = await createPaymentSignature(account, option, requirements.x402Version); // 5. Retry with payment header const paidResponse = await fetch(url, { headers: { 'X-Payment': encodePaymentHeader(payment) } }); } ``` ## Scripts ### `x402.mjs` — Core Library The main x402 implementation. Functions: - `parsePaymentRequired(response)` — Parse 402 response for payment requirements - `createPaymentSignature(account, requirement, version)` — Sign EIP-3009 authorization - `encodePaymentHeader(payload)` — Base64 encode for X-Payment header - `x402Fetch(account, url, options)` — Fetch with automatic payment handling ### `conway-credits.mjs` — Conway Compute Credits Top up Conway compute credits: ```bash PRIVATE_KEY=0x... node conway-credits.mjs 5 # Tops up $5 in Conway credits ``` ### `conway-domain.mjs` — Conway Domain Registration Register domains via Conway: ```bash PRIVATE_KEY=0x... node conway-domain.mjs example.com ``` ## Wallet Security **NEVER commit private keys to version control.** Recommended patterns: - Environment variables: `process.env.PRIVATE_KEY` - Secure file with restricted permissions: `chmod 600 wallet.json` - Hardware wallet via WalletConnect (advanced) ## Supported Networks | Network | Chain ID | USDC Address | |---------|----------|--------------| | Base | 8453 | [REDACTED_SECRET] | | Base Sepolia | 84532 | [REDACTED_SECRET] | ## Known x402 Providers - **Conway** — Compute, inference, domains (conway.tech) - **Farcaster Hub** — Some hubs accept x402 for API access ## References - [x402 Protocol Spec](https://x402.org) - [EIP-3009: Transfer With Authorization](https://eips.ethereum.org/EIPS/eip-3009) - [Conway Documentation](https://docs.conway.tech) ## Author Built by [Lumen](https://x.com/LumenFTFuture) — March 2026 First successful x402 payment: TX `0xba5eaf2e4f1c7b9f79581c1771adc2527b2029dbbc900c429d3f69eafe6d3ba7`

path: examples/conway-credits.mjs
content: #!/usr/bin/env node
/**
 * Conway Credits Top-up via x402
 * 
 * Usage:
 *   PRIVATE_KEY=0x... node conway-credits.mjs 5
 *   
 * Or with a wallet file:
 *   WALLET_PATH=/path/to/wallet.json node conway-credits.mjs 5
 */

import { privateKeyToAccount } from "viem/accounts";
import { x402Fetch } from "../x402.mjs";
import { readFileSync } from "fs";

// Load wallet from env or file
function loadWallet() {
  if (process.env.PRIVATE_KEY) {
    return privateKeyToAccount(process.env.PRIVATE_KEY);
  }
  
  if (process.env.WALLET_PATH) {
    const wallet = JSON.parse(readFileSync(process.env.WALLET_PATH, "utf-8"));
    return privateKeyToAccount(wallet.privateKey);
  }
  
  console.error("Error: Set PRIVATE_KEY or WALLET_PATH environment variable");
  process.exit(1);
}

const account = loadWallet();
const amountUsd = process.argv[2] || "5";

async function topUp() {
  console.log(`Wallet: ${account.address}`);
  console.log(`Topping up $${amountUsd} Conway credits...\n`);
  
  const url = `https://api.conway.tech/pay/${amountUsd}/${account.address}`;
  
  const response = await x402Fetch(account, url);
  const result = await response.json();
  
  console.log("Response:", JSON.stringify(result, null, 2));
  
  if (result.success) {
    console.log(`\n✅ Topped up ${result.credits_cents} cents`);
    if (result.tx_hash) {
      console.log(`TX: https://basescan.org/tx/${result.tx_hash}`);
    }
  }
}

topUp().catch(console.error);


path: package.json
content: {
  "name": "x402-skill",
  "version": "1.0.0",
  "description": "x402 HTTP payment protocol for agents - pay for resources with USDC on Base",
  "type": "module",
  "main": "x402.mjs",
  "scripts": {
    "test": "node examples/conway-credits.mjs --help"
  },
  "keywords": [
    "x402",
    "payments",
    "crypto",
    "usdc",
    "base",
    "agents",
    "openclaw"
  ],
  "author": "Lumen <https://x.com/LumenFTFuture>",
  "license": "MIT",
  "dependencies": {
    "viem": "^2.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/LumenFromTheFuture/x402-skill"
  }
}


path: x402.mjs
content: /**
 * x402 Protocol Implementation
 * 
 * HTTP 402 payment protocol for machine-to-machine USDC payments on Base.
 * 
 * Usage:
 *   import { x402Fetch } from './x402.mjs';
 *   const response = await x402Fetch(account, url);
 * 
 * Author: Lumen (https://x.com/LumenFTFuture)
 * License: MIT
 */

// Supported networks
export const SUPPORTED_NETWORKS = {
  "eip155:8453": {
    chainId: 8453,
    name: "Base",
    usdcAddress: [REDACTED_SECRET],
  },
  "eip155:84532": {
    chainId: 84532,
    name: "Base Sepolia",
    usdcAddress: [REDACTED_SECRET],
  },
};

/**
 * Parse x402 payment requirements from a 402 response
 */
export async function parsePaymentRequired(response) {
  // Try X-Payment-Required header first
  const header = response.headers.get("X-Payment-Required");
  if (header) {
    try {
      // Try direct JSON parse
      const parsed = JSON.parse(header);
      if (parsed.accepts) return parsed;
    } catch {
      // Try base64 decode
      try {
        const decoded = atob(header);
        const parsed = JSON.parse(decoded);
        if (parsed.accepts) return parsed;
      } catch {}
    }
  }
  
  // Try JSON body
  try {
    const body = await response.clone().json();
    if (body.accepts) return body;
  } catch {}
  
  return null;
}

/**
 * Create EIP-3009 transferWithAuthorization signature
 */
export async function createPaymentSignature(account, requirement, x402Version = 1) {
  const network = SUPPORTED_NETWORKS[requirement.network];
  if (!network) {
    throw new Error(`Unsupported network: ${requirement.network}`);
  }
  
  // Generate random nonce
  const nonce = `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex")}`;
  
  // Calculate timestamps
  const now = Math.floor(Date.now() / 1000);
  const validAfter = BigInt(now - 60);
  const validBefore = BigInt(now + (requirement.maxTimeoutSeconds || requirement.requiredDeadlineSeconds || 300));
  
  // Parse amount
  const maxAmount = requirement.maxAmountRequired;
  let value;
  if (typeof maxAmount === "string" && maxAmount.includes(".")) {
    // Decimal format (e.g., "5.00")
    value = BigInt(Math.floor(parseFloat(maxAmount) * 1e6));
  } else if (x402Version >= 2 || String(maxAmount).length > 6) {
    // Already in base units
    value = BigInt(maxAmount);
  } else {
    // Legacy: whole token amount
    value = BigInt(maxAmount) * BigInt(1e6);
  }
  
  // Authorization struct
  const authorization = {
    from: account.address,
    to: requirement.payTo || requirement.payToAddress,
    value,
    validAfter,
    validBefore,
    nonce,
  };
  
  // EIP-712 domain
  const domain = {
    name: requirement.extra?.name || "USD Coin",
    version: requirement.extra?.version || "2",
    chainId: network.chainId,
    verifyingContract: requirement.asset || requirement.usdcAddress,
  };
  
  // EIP-712 types
  const types = {
    TransferWithAuthorization: [
      { name: "from", type: "address" },
      { name: "to", type: "address" },
      { name: "value", type: "uint256" },
      { name: "validAfter", type: "uint256" },
      { name: "validBefore", type: "uint256" },
      { name: "nonce", type: "bytes32" },
    ],
  };
  
  // Sign
  const signature = await account.signTypedData({
    domain,
    types,
    primaryType: "TransferWithAuthorization",
    message: authorization,
  });
  
  return {
    x402Version: 1,
    scheme: requirement.scheme,
    network: requirement.network,
    payload: {
      signature,
      authorization,
    },
  };
}

/**
 * Encode payment payload as X-Payment header value
 */
export function encodePaymentHeader(payment) {
  const serializable = {
    ...payment,
    payload: {
      ...payment.payload,
      authorization: {
        ...payment.payload.authorization,
        value: payment.payload.authorization.value.toString(),
        validAfter: payment.payload.authorization.validAfter.toString(),
        validBefore: payment.payload.authorization.validBefore.toString(),
      },
    },
  };
  return btoa(JSON.stringify(serializable));
}

/**
 * Fetch with automatic x402 payment handling
 */
export async function x402Fetch(account, url, options = {}) {
  // Initial request
  const response = await fetch(url, options);
  
  if (response.status !== 402) {
    return response;
  }
  
  // Parse payment requirements
  const requirements = await parsePaymentRequired(response);
  if (!requirements) {
    throw new Error("Received 402 but could not parse payment requirements");
  }
  
  // Find supported payment option
  const requirement = requirements.accepts.find(
    r => r.scheme === "exact" && SUPPORTED_NETWORKS[r.network]
  );
  
  if (!requirement) {
    throw new Error("No supported payment options found");
  }
  
  // Create and sign payment
  const payment = await createPaymentSignature(account, requirement, requirements.x402Version);
  
  // Retry with payment header
  const paidResponse = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      "X-Payment": encodePaymentHeader(payment),
    },
  });
  
  return paidResponse;
}

/**
 * Check if a URL requires x402 payment
 */
export async function checkX402(url, method = "GET") {
  const response = await fetch(url, { method });
  
  if (response.status === 402) {
    const requirements = await parsePaymentRequired(response);
    return { requiresPayment: true, requirements };
  }
  
  return { requiresPayment: false };
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)