Merchant phone signs a payment request (EIP-712, nonce + expiry + ENS name) and shows it by NFC/QR. Customer phone verifies signature + ENS (forward and reverse) before it can confirm, then pays USDC, or ETH via a Uniswap swap that delivers the exact signed USDC amount and refunds unused ETH.
contracts/:PaymentSettlement.sol, deploy script, Foundry tests (test/)packages/shared/: signing, wire format, verification, settlement calls, payment state machine (TypeScript)apps/mobile/: Android app (Expo dev build; merchant + customer screens, NFC via HCE, QR fallback)web/: the two real portals (/merchanttill-side,/pay-side) driven by MetaMask viawindow.ethereum. The merchant dashboard (Tailwind v4,web/src/dash/) reads live USDC balance +PaymentSettledhistory, groups payers, and signs EIP-712 charges into QR/links with a 2-minute countdown.?demo=1previews labelled sample data;/simkeeps the earlier two-phone browser simulation (fallback demo).test-e2e/: local end-to-end test (real contract + mock USDC/WETH/router on a local chain)
forge testincontracts/: 5/5 pass (OpenZeppelin v5.1.0 is vendored incontracts/lib).test-e2e: 13/13 pass (cd test-e2e && npm i && npm test).packages/sharedandwebtype-check;webbuilds. NOT run (needs your wallet / network / phones): Base Sepolia deploy, swap-pool liquidity check, Android app, NFC, real ENS lookups.
- Web portals (fastest to try):
cd web && npm i && npm run dev→ open/merchanton one device and/on another (MetaMask on both, same Wi-Fi). Add?demo=1to/merchantto rehearse the dashboard with labelled sample data before the contract is deployed. Seeweb/README.md. cd contracts && forge test(forge-std and OpenZeppelin are already inlib/).- Deploy:
export PK=0x...; forge script script/Deploy.s.sol:Deploy --rpc-url https://sepolia.base.org --broadcast - Put the deployed address in
packages/shared/src/config.ts(SETTLEMENT). - ENS (on Ethereum Sepolia, app.ens.domains): with a wallet that has free Sepolia ETH, register a name, point its address record to that same wallet, and set it as that wallet's primary name. Then on the merchant phone, open the Merchant screen and use "Use my own wallet" to import that wallet's private key (testnet-only wallet!). The merchant signer then equals the ENS owner, so verification passes.
- Fund the customer wallet with Base Sepolia ETH and test USDC (faucet.circle.com).
cd apps/mobile && npm i && npx expo prebuild && npx expo run:androidon both phones.
- The WETH/USDC 0.3% pool on Base Sepolia needs liquidity for the ETH path (otherwise change
POOL_FEEand the deploy fee). USDC path is independent. react-native-hcecalls inapps/mobile/src/nfc.tswere written from memory; confirm against your installed version. QR always works as fallback.- Keys are generated on-device, testnet only.