// Buy a seven-day Machine Inbox with x402 v2: USDC on Base mainnet. // Requires: bun add @x402/fetch @x402/evm viem // Environment: X402_PRIVATE_KEY=0x... (a wallet holding at least $2.00 USDC on Base) import { x402Client, wrapFetchWithPayment } from '@x402/fetch'; import { registerExactEvmScheme } from '@x402/evm/exact/client'; import { privateKeyToAccount } from 'viem/accounts'; const origin = (process.env.MACHINE_INBOX_ORIGIN || 'https://machineinbox.com').replace(/\/$/, ''); const NETWORK = 'eip155:8453'; // Base mainnet const MAX_PRICE = 2_000_000n; // $2.00 in 6-decimal USDC units const privateKey = process.env.X402_PRIVATE_KEY; if (!privateKey) { console.error('Set X402_PRIVATE_KEY to a funded Base mainnet wallet key.'); process.exit(1); } const signer = privateKeyToAccount(privateKey as `0x${string}`); const client = new x402Client(); registerExactEvmScheme(client, { signer }); // Never sign a requirement other than the one this service publishes. client.registerPolicy((_version, requirements) => requirements.filter(item => item.scheme === 'exact' && item.network === NETWORK && BigInt(item.amount) <= MAX_PRICE, )); const paidFetch = wrapFetchWithPayment(globalThis.fetch, client); // x402 purchases require an Idempotency-Key. Keep it secret and reuse it only // to retry this same purchase; a retry can then never buy twice. Print it // before paying: a 503 payment_activation_pending response means the charge // settled and only this key can recover the paid inbox. const idempotencyKey = process.env.MACHINE_INBOX_IDEMPOTENCY_KEY || crypto.randomUUID() + crypto.randomUUID(); console.error(`Idempotency-Key (save until the purchase completes): ${idempotencyKey}`); // While the on-chain receipt confirms, the service answers 503 // payment_activation_pending (the charge settled, activation is catching up) // or 409 payment_in_progress (another worker holds the activation claim). // Both mean: wait Retry-After seconds and retry with the SAME key. The retry // never pays twice; an unfinished payment attempt is cancelled unused. const RETRY_BUDGET_MS = 30_000; const started = Date.now(); let response: Response; for (;;) { response = await paidFetch(`${origin}/api/v1/inboxes`, { method: 'POST', headers: { 'Idempotency-Key': idempotencyKey }, }); const pending = response.status === 503 || response.status === 409; if (!pending || Date.now() - started >= RETRY_BUDGET_MS) break; const retryAfter = Number.parseInt(response.headers.get('Retry-After') || '2', 10) || 2; console.error(`HTTP ${response.status}: activation pending, retrying in ${retryAfter}s with the same key.`); await new Promise(resolve => setTimeout(resolve, retryAfter * 1_000)); } if (!response.ok) { console.error(`Purchase failed: HTTP ${response.status}`, await response.text()); if (response.status === 503 || response.status === 409) { console.error('The payment settled; rerun with the same key to collect the inbox:'); console.error(` MACHINE_INBOX_IDEMPOTENCY_KEY='${idempotencyKey}' X402_PRIVATE_KEY=... bun run create-inbox-x402.ts`); } process.exit(1); } const created = await response.json() as { inbox: { id: string; address: string; expiresAt: string }; token: string }; console.log(JSON.stringify({ inboxId: created.inbox.id, address: created.inbox.address, expiresAt: created.inbox.expiresAt, token: created.token, // shown only once; store it as a secret }, null, 2));