Inspiration
We were building live expert guidance for field service — technician points a phone, remote expert drops annotation markers, session sealed on-chain. Then we read the track rules: submissions must meaningfully implement the x402 payment flow. A relayer-signed attestation isn't a payment flow.
Bolting on payments made us ask a better question: what does an attestation add to a rail that already returns a receipt? x402 proves the money moved. It proves nothing about whether the service was delivered. A human buying one API call can eyeball the result; an agent making hundreds an hour cannot.
What it does
Proofwork is a proof-of-delivery layer for x402 — one certificate showing paid and delivered, both independently verifiable on-chain. Running today: Complete x402 v2 round trip at /api/paid-ping — Challenge → Sign → Retry → Settle, $0.001 USDC on Base Sepolia via the GoPlausible facilitator. Successful responses carry the settlement receipt.
Real settlements on-chain. Two of them, openable in a block explorer: 0xcd128117…d423db (block 44860620) and 0x7a27f9c5…ac6c1d (block 44860699). Distinct hashes — fresh EIP-3009 nonces, not a replay.
A reproducible buyer. scripts/x402-buyer-test.ts plays the paying agent — signs the EIP-3009 authorization, retries with the X-PAYMENT header, prints the settlement. Anyone can run it.
On-chain attestation, live. Attestation.sol is deployed to Base Sepolia at 0x74082F5697f2804a1100bc927644F8Df6545a8B4. Tapping Step complete hashes the session record, writes it through a pre-funded relayer — no wallet popup, no gas prompt — and renders a Proof-of-Service certificate with a live explorer link. First attestation: 0x582f8a43…e54d2b, block 44862309.
The service being metered, verified end to end in two browsers: live two-way video and audio (LiveKit) with a real-time annotation overlay that syncs over the data channel.
A WebXR world-anchored AR beta ships at /technician/ar — device-gated, and unverified on hardware.
How we built it
Next.js 14 + TypeScript. x402 v2 via @x402/core/server + ExactEvmScheme, driven through @x402/next's withX402 with our own resource server, so facilitator, network and scheme stay explicit in our code. USDC on Base Sepolia, scheme exact. LiveKit for media and the annotation data channel. A Solidity attestation contract written by an app-owned relayer, so the demo has no wallet popup and can't fail on gas.
Two choices we'd defend hardest: Base Sepolia over Algorand — the rubric scores the x402 flow, not the chain underneath it, and mainnet is a config change (eip155:8453 is up). 2D screen-space annotation over WebXR for the golden path — WebXR has no iOS support and conflicts with streaming the camera, so it's isolated to a device-gated beta route.
Challenges we ran into
The peer-dependency wall. @x402/next demands Next ≥16.2.6; we run 14.2, and a major upgrade mid-hackathon breaks working demos. We read the package's compiled output — it imports only NextRequest/NextResponse, stable since Next 13 — installed with --legacy-peer-deps, and supplied our own resource server. Build stayed green. The ETH that was never needed. We hunted Base Sepolia ETH faucets for the buyer before realising EIP-3009 means the payer signs off-chain and never broadcasts — the facilitator submits and pays gas (confirmed on-chain). Our buyer settled holding 0 ETH. One device can't do AR passthrough and WebRTC at once. So WebXR became an isolated, feature-detected route instead of a risk to the golden path. Auditing our own attestation. It hashes hardcoded strings, and the preimage is never persisted — a hash with no retrievable preimage can't be verified by anyone, including us. We logged both as defects rather than shipping them as features. It's why ADR-0011 makes bundle storage irreducible. The first two-browser run broke three ways at once. React StrictMode double-invoked the connect effect, and because both mounts used the same LiveKit identity they evicted each other — leaving annotation publishes throwing on a closed peer connection. Separately, facingMode: "environment" requests a rear camera a laptop doesn't have, and that failure aborted the rest of session setup, so audio worked and video never appeared. All three were invisible to every check we'd run: green build, clean types, every route returning 200. Only two real browsers found them.
Accomplishments that we're proud of
The x402 flow genuinely works and anyone can check — real settlements, real hashes, live facilitator. Even our failure mode was evidence: an unfunded buyer got back invalid_exact_evm_insufficient_balance, a verdict requiring a real on-chain balance check. Working out that the payer needs no gas — the property that makes agentic micropayments viable at all. Twelve ADRs with context and rejected alternatives, including one self-correction. It's why the pivot to this track took hours instead of days. Publishing our own defects above the fold in the README.
What we learned
Payment rails prove settlement, not satisfaction — and the gap widens the more autonomous the buyer is. Gaslessness changes who can participate, not just what it costs. Canonical JSON is load-bearing: JSON.stringify key order is insertion-dependent, so a bundle that re-serialises differently hashes differently and verification silently fails. Read a package's compiled output, not its peer-dependency range.
What's next for Proofwork
- Make the contract delivery-aware — attestDelivery(bundleHash, provider, paymentRef), Delivered indexed by provider.
- Ship the x402-proof middleware — canonical-JSON bundle, keccak256, provider signature, on-chain write, proof id returned beside the receipt.
- Bundle storage keyed by proof id — without it the proof is worthless.
- Public verifier — recompute, compare against the chain, ecrecover, render ✓/✗.
- Paywall the live session itself, so the evidence bundle becomes visual.
- Provider reputation, queryable before an agent picks whom to pay.
Built With
- base
- blockchain
- eip-3009
- ethereum
- ethers.js
- hardhat
- livekit
- next.js
- node.js
- react
- smart-contracts
- solidity
- three.js
- typescript
- usdc
- viem
- web3
- webrtc
- webxr
- x402
Log in or sign up for Devpost to join the conversation.