We shipped three pull requests to a live production site. All three broke the build. A TypeScript strict-mode error, a BigInt syntax incompatibility, and a DNS failure in a price API we depended on — any one of which would have been caught by a 5-second automated test run.
A test suite that runs in 1.3 seconds, covers 39 scenarios across every adapter and every known attack vector, and requires zero external dependencies — no real blockchain, no real wallets, no API keys, no browser binaries.
$ npx vitest run
✓ __tests__/x402-client.test.js (10 tests)
✓ __tests__/adapters/solana.test.js (13 tests)
✓ __tests__/adapters/evm.test.js (7 tests)
✓ __tests__/adapters/walletconnect.test.js (9 tests)
Test Files 4 passed (4)
Tests 39 passed (39)
Duration 1.32s
The X402Client class — creation with valid/invalid config, adapter registry (register, chain, reject incomplete adapters), event emitter (subscribe, emit, unsubscribe), wallet detection aggregation across multiple chains, and pay-path error handling for unsupported chains and missing amounts.
Wallet detection (Phantom installed, multiple wallets, no wallet). USDC verification with 8 attack scenarios drawn directly from our security threat model:
| Test | Attack Blocked |
|---|---|
| Non-existent txHash | Fake payment submission |
| Failed transaction (meta.err) | Reverted tx used as proof |
| No transfer to expected recipient | Payment sent to wrong wallet |
| Partial payment ($0.50 for a $12 item) | Underpayment fraud |
| Correct amount (positive test) | Proves legitimate payments work |
| Net-delta refund attack | Jupiter multi-hop: credit $10, claw back $9.50 in same tx |
| Wrong token (non-USDC program) | Scam token impersonation |
| Stale transaction (older than order) | Reusing an ancient unrelated payment |
Same attack coverage for Base/Polygon/Arbitrum USDC: valid transfer, not-found, failed, wrong recipient, partial payment, wrong token contract, and the EVM version of the net-delta refund attack (two Transfer events in one receipt — credit then debit).
Deep link URL generation for Phantom and Solflare (including edge cases: query params, fragments, localhost). Mobile vs desktop user-agent detection. WalletConnect project ID handling. Deep-link redirect throw behavior. WC relay error messaging.
Vitest + jsdom — the test runner executes in Node with a simulated browser environment. No real browser binaries, no Playwright for the unit tests. Wallet providers are mocked via reusable factory functions:
// From __tests__/helpers/mock-providers.js
function mockPhantomProvider(opts = {}) {
return {
isPhantom: true,
connect: async () => ({
publicKey: { toString: () => opts.pubkey || 'FakePubkey...' }
}),
signTransaction: async (tx) => {
if (opts.rejectSign) throw new Error('User rejected')
return { serialize: () => new Uint8Array(256) }
},
}
}
Verification tests use real transaction JSON as fixtures. The $0.02 USDC payment from April 14 (the first live x402 sale) is captured as a fixture file. Attack scenarios are crafted variants of that real data. Because the fixtures are raw Solana/EVM RPC response JSON, they only break if the blockchain protocol itself changes — which is a governance-level event, not a routine update.
Had this test suite existed earlier, three bugs would have been caught before they reached a production site:
0n) would have failed the test run before we pushedGoing forward, every PR to the framework runs npx vitest run before push. If it doesn't pass, it doesn't ship.