Local development
Local EVM chains that emulate the public testnets, demo data through the real pipeline, and the two acceptance suites.
Last updated Oct 27, 2026
You need Docker (Postgres + Redis), Bun and Foundry for anvil (brew install foundry, or curl -L https://foundry.paradigm.xyz | bash && foundryup).
Local chains: bun run chain:dev
apps/server/scripts/chain/dev.ts starts one anvil per test network with the real chainId (Polygon Amoy 80002 on :18002, BSC Testnet 97 on :18097, Base Sepolia 84532 on :18532) and plants a mock stablecoin at each canonical token address with anvil_setCode. The server runs the same registry and code paths it uses on the public testnets; only the RPC URL changes.
- anvil's default accounts #1–#3 are the buyers: each holds 1,000,000 of every token on every chain.
- The hot wallet (index 0 of
CRYPTO_MASTER_MNEMONIC) is funded with native gas. - Flags:
--block-time 2(default, interval mining),--block-time 0(a block per transaction; tests mine withanvil_mine),--mainnets(adds chainIds 137 / 56 / 8453 on:18137/:18056/:18453for local live-mode demos),--fresh(wipe the persisted state inscripts/chain/.state/).
Point the server at them in apps/server/.env:
CRYPTO_MASTER_MNEMONIC="depend mechanic screen brush dust raven sure found body super chronic parrot" # public dev mnemonic
CRYPTO_RPC_POLYGON_AMOY=http://127.0.0.1:18002
CRYPTO_RPC_BSC_TESTNET=http://127.0.0.1:18097
CRYPTO_RPC_BASE_SEPOLIA=http://127.0.0.1:18532and, optionally, give the checkout's wallet button the same RPCs (apps/client/.env.local: NEXT_PUBLIC_CRYPTO_RPC_POLYGON_AMOY=http://127.0.0.1:18002, …), so a browser wallet can add the local chain.
Then run the API (bun run dev) and the worker (bun run --cwd apps/server worker): without the worker nothing is scanned, swept or sent.
Demo data: bun run crypto:demo
apps/server/scripts/crypto-demo.ts fills a running stack with the Glyphyard story through the real pipeline (merchant API, public checkout API and real ERC-20 transfers from the buyer wallets): sellers Halden Type, Kiso Foundry, Pampa Type, Ribbon & Rule, Osaro Studio, partner Kerning Weekly, two payment links, and invoices in every state (paid across networks, overpaid with a claimable refund, underpaid, pending, expired, refunded, one still confirming) plus a completed payout. It is idempotent and takes about 2.5 minutes. It runs on the seeded acme org (log in as [email protected] / password123).
Its defaults are --api http://localhost:3405 --checkout http://localhost:3404; with the default ports pass --api http://localhost:3005 --checkout http://localhost:3004. --webhook-url <url> also registers a webhook endpoint.
Acceptance: bun run --cwd apps/server crypto:e2e
The phase-1 definition of done, API-level, about 1:35. It boots its own isolated stack (a throwaway Postgres schema, Redis DB 9, three anvil chains on ports +100 with one block per transaction, the API on :3105 and the worker) and verifies every result on chain and in the ledger: idempotent invoice creation, public view, payment, DETECTED → PAID, the 90 / 10 / 2 / 0.50 ledger lines, a signed webhook, a wrong-token transfer escrowed and refunded in its own token, the sweep, a payout, and underpaid → expired → refund claimed (contract address rejected). It prints ALL PASSED and exits 0 only if every step passed.
Browser suite: bun run e2e:crypto
Five Playwright specs (tests/e2e/crypto-*.spec.ts) drive the real UI against local chains:
| Spec | Covers |
|---|---|
crypto-checkout | pick a pair → Pay with wallet (an injected EIP-1193 provider backed by anvil; starts on another chain, so the switch runs) → DETECTED → PAID receipt with the split bar → printable invoice; exact amount left the buyer on chain |
crypto-refund | underpaid → expired → refund held on the public page (no link) → claim link from the merchant API → portal rejects a contract, pays an EOA on chain |
crypto-dashboard | overview in Test mode → invoice detail: summary, ledger balanced, Technical tab with claim_url redacted |
crypto-payout | enable 2FA in Settings → New payout with a TOTP code → COMPLETED, net amount on the destination on chain |
crypto-payment-link | create a link in the dashboard → /l/:slug → checkout → PAID, sales count 1 |
tests/scripts/run-e2e.ts with E2E_CRYPTO=1 creates and migrates an isolated schema, seeds it, starts the chains (ports +200, one block per transaction) and the worker on Redis DB 10, and runs the API on :3205 and the client on :3204, so it can run next to bun run dev and chain:dev. The specs mine confirmations themselves. If another next dev already runs in apps/client (Next.js allows one per directory), use E2E_CLIENT_MODE=start to serve a fresh next build instead (E2E_SKIP_CLIENT_BUILD=1 reuses the last one). Playwright runs on Node, not Bun: put a real Node first in PATH.
Public testnets
Set CRYPTO_RPC_<NETWORK> to public RPCs (see apps/server/.env.example) to run the read path against Amoy, BSC Testnet and Base Sepolia. Amoy has no canonical USDT (the Amoy USDT contract exists only on the local chains), so pay Amoy invoices in USDC there. The scanner's defaults (ranges of 200 blocks, halving on provider limits) are sized for those providers; the evidence is in docs/superpowers/specs/2026-10-27-public-testnet-smoke.md.