Per-second video billing on Base. A buyer-side AI agent authorizes "up to $5" via x402's
uptoscheme, opens a Daily.co video room with a human expert, and settles the actual call duration in one on-chain Permit2 USDC transfer. The buyer never holds ETH; the facilitator pays gas; the unspent allowance never moves.
🎥 Watch the demo → https://youtu.be/V8pga7SQjxE · 🔗 Live app → https://main.d3vbs5akc8zis2.amplifyapp.com
- TL;DR
- Live demo
- The pitch
- Quick start
- Architecture
- Tech stack
- On-chain proof
- Product tour
- How it works
- Why x402 +
uptospecifically - Repository layout
- Scripts
- Testing
- Future work / known limitations
- Acknowledgments
- License
An AI agent authorizes a per-second-billed expert call. One signature, one on-chain settlement, gas-sponsored. Stripe physically can't do this.
- What: PayPhone bills per-second video sessions with human experts and settles the actual call duration in a single on-chain USDC transfer on Base.
- Why this is interesting: Stripe's 30¢ + 2.9% minimum makes per-second
billing impossible. x402's
uptoscheme + Permit2 lets a buyer sign for "up to $5" once, and the chain enforces "actual amount used ≤ $5" without trust. - Who pays gas: the CDP facilitator, via EIP-2612 +
erc20ApprovalGasSponsoring. The buyer never holds ETH on Base. - Demo proof: 6 real on-chain settlements (2 on Base mainnet), the latest a 31-second call settling $0.31 in one tx, gas-free for the buyer.
- Built for: EasyA Consensus Miami 2026, Agentic Track (Coinbase + AWS).
Sign in with Cognito, get a per-user CDP wallet, drip-fund it from the in-app Sepolia faucet, and run a real two-tab call that settles on-chain.
🔗 https://main.d3vbs5akc8zis2.amplifyapp.com
The deployed app runs on Base Sepolia (production safe-default — no real money moved by visitors). Mainnet is reachable from a local laptop env-flip; see the On-chain proof section for the two real-money mainnet settlements.
To try a session yourself:
- Sign up at the live URL via Cognito Hosted UI (email + password, you'll get a verification code).
- After sign-in, your per-user CDP wallet is provisioned lazily on first action. The marketplace shows a Wallet panel with your address + USDC balance.
- If your balance is $0.00, click Fund my wallet — the CDP faucet drips 10 Sepolia USDC. (CDP has a 24h project-wide quota; if it's exhausted, the panel surfaces the Circle faucet URL as a manual fallback.)
- Click Talk to ... on any expert card. A Daily room opens. Open the
same
/session/<id>URL in a second tab/incognito to act as the expert. - Talk for ~30 seconds, click End call & settle. Within ~10s the recap page renders with the AI summary, follow-up chat, and a working BaseScan link to the on-chain settlement.
Three lines. Don't drift.
1. Stripe physically can't do this. Stripe's per-transaction minimum is 30¢ + 2.9%. A 12-second call billed at $0.12 would lose money before the merchant sees a cent. Per-second billing isn't a UX problem — it's a payment-rail problem.
2. x402 + upto turns per-second billing into one on-chain settlement.
The buyer signs ONE EIP-712 witness saying "I authorize up to $5.00, valid
for 30 minutes." The proxy contract enforces amount ≤ permitted.amount
on-chain via AmountExceedsPermitted. When the call ends, the server
settles for floor(active_window_seconds) × $0.01. The unspent allowance
never moves — no refund path, no leftover state.
3. The video call is the demo. The rail is the product. Anyone can
build "a video app." The novel surface is the autonomous payment that
authorizes ahead of time, settles after, and never charges more than the
user agreed to. The agent shows up with an API key, not an ETH-funded
EOA. CDP's facilitator pays the gas via eip2612GasSponsoring and
erc20ApprovalGasSponsoring extensions declared in the 402 challenge.
Clone, install, env, run. ~5 minutes if you have AWS + CDP + Daily creds in hand.
git clone https://github.com/Achyut21/payphone.git
cd payphone
pnpm installCreate .env.local at the project root with the following keys (placeholder
values shown — get real ones from the dashboards listed):
# Coinbase Developer Platform — https://portal.cdp.coinbase.com
CDP_API_KEY_ID=<your_cdp_api_key_id>
CDP_API_KEY_SECRET=<your_cdp_api_key_secret>
CDP_WALLET_SECRET=<your_cdp_wallet_secret>
# AWS DynamoDB (after `terraform apply` in infra/terraform/)
APP_AWS_REGION=us-east-1
APP_AWS_ACCESS_KEY_ID=<terraform output app_access_key_id>
APP_AWS_SECRET_ACCESS_KEY=<terraform output app_secret_access_key>
DYNAMODB_TABLE_NAME=payphone-sessions
USERS_TABLE_NAME=payphone-users
# AWS Cognito — populated from `terraform output` in infra/terraform/cognito.tf
COGNITO_CLIENT_ID=<terraform output cognito_user_pool_client_id>
COGNITO_CLIENT_SECRET=<terraform output cognito_user_pool_client_secret>
COGNITO_ISSUER=<terraform output cognito_issuer>
# NextAuth v5
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<openssl rand -base64 32>
# Daily.co — https://dashboard.daily.co
DAILY_API_KEY=<your_daily_api_key>
DAILY_WEBHOOK_SECRET=<set after running register-daily-webhook.ts>
# Anthropic — https://console.anthropic.com
ANTHROPIC_API_KEY=<your_anthropic_api_key>
# Network selection — defaults to sepolia (safe). Set to "mainnet" for real-money runs.
NEXT_PUBLIC_ACTIVE_NETWORK=sepolia
INTERNAL_API_URL=http://localhost:3000One-time AWS infra:
brew install terraform awscli
aws configure # paste a temporary AdministratorAccess key
cd infra/terraform
terraform init
terraform apply # provisions DDB + Cognito + scoped IAM
terraform output # copy values into .env.localRun it:
pnpm dev # http://localhost:3000For the full local flow including Daily webhook tunneling (you'll need ngrok), see the How it works section below.
Browser → NextAuth+Cognito → per-user CDP wallet signs an x402
uptowitness → CDP facilitator settles on Base via Permit2 (gas sponsored). Daily.co handles video; DynamoDB stores session state; Anthropic Haiku 4.5 powers the AI suggester and the post-call summary.
For an animated, scroll-driven version of this diagram, visit the /docs page in the live deploy — SVG paths self-draw on viewport entry with a "data flowing" pulse on each edge.
The static fallback (rendered by GitHub):
flowchart TD
A["🌐 Browser<br/>Next.js 16 + NextAuth + Cognito"]
B["🪪 Per-user CDP Wallet<br/>(provisioned lazily on first session)"]
C["✍️ x402 upto witness<br/>signed for $5 max, 30-min validity"]
D["🏦 CDP Facilitator<br/>verify → settle"]
E["⛓️ Base mainnet / Sepolia<br/>USDC.permit + Permit2.permitTransferFrom"]
F["💸 USDC transferred<br/>floor(active_seconds) × $0.01"]
G["📞 Daily.co room<br/>WebRTC + realtime transcription"]
H["🪝 participant.joined / left webhook<br/>HMAC-SHA256 verified"]
I["🗄️ DynamoDB<br/>session state + active-window math"]
J["🤖 Haiku 4.5<br/>AI suggester + recap summary + chat"]
A --> B
B --> C
C -->|POST /api/sessions| D
D --> E
E --> F
A -.opens.-> G
G -->|webhook| H
H --> I
I -->|first leave fires settle| D
A -.recap.-> J
J -.streamed summary.-> A
The settlement amount is floor(active_window_seconds) × $0.01, capped at
the signed upto.amount. Active window = the time both participants
were in the room — buyer wait time before the expert joined doesn't count.
See lib/billing.ts for the pure-function math.
Coinbase + AWS + Anthropic, glued together with Next.js 16 and TypeScript strict.
| Layer | Technology | Role |
|---|---|---|
| App runtime | Next.js 16 App Router, React 19.2, TypeScript strict | SSR + edge proxy + API routes (all runtime = 'nodejs' for CDP SDK) |
| Styling | Tailwind v4, shadcn/ui, Lucide React, Aceternity components | Dark-mode design system, Aurora + BeamsWithCollision + Spotlight |
| Animation | motion (rebranded framer-motion v12) |
The scroll-driven /docs flowchart, in-call pulse animations |
| Auth | AWS Cognito Hosted UI + NextAuth v5 | Sign-up + sign-in via Cognito User Pool; JWT session strategy |
| Wallets | Coinbase CDP Server Wallets v2 | One CDP wallet per Cognito user, provisioned lazily on first action |
| Payments | x402 v2 via CDP facilitator + Permit2 + EIP-2612 | upto scheme; gas-sponsored via eip2612GasSponsoring extension |
| Settlement | Base mainnet / Base Sepolia, USDC | One on-chain Transfer event per call, the actual duration × $0.01 |
| Video | Daily.co rooms + Deepgram realtime transcription | WebRTC video; client-captured transcript POSTed to the server |
| Persistence | AWS DynamoDB (Terraform-managed) | Two tables: payphone-sessions, payphone-users (cognito_sub → wallet) |
| AI | Anthropic Claude Haiku 4.5 via Vercel AI SDK v6 | AI expert suggester (one-shot); recap summary + follow-up chat (streaming) |
| Hosting | AWS Amplify Hosting | Production deploy with amplify.yml build spec; runtime IAM scoped to DDB |
| IaC | Terraform (Cognito + DDB + IAM) | One terraform apply provisions the auth + persistence layer |
PayPhone uses Coinbase Developer Platform end-to-end on the agent side:
- CDP Server Wallets v2 — every user gets their own server-managed
wallet via
cdp.evm.getOrCreateAccount({ name }). Idempotent — calling twice with the same name returns the same address, which makes our race-safe lazy provisioning trivial. - CDP x402 facilitator — the
/verifyand/settleendpoints are hosted by CDP. We hand-roll the 402 → verify → settle handshake (rather than usingpaymentMiddleware) because settlement must wait until call hangup. @x402/core,@x402/evm,@x402/fetch— the protocol-v2 packages withUptoEvmSchemefor the buyer-side signing andeip2612GasSponsoringfor gas-free first-time Permit2 approval.- USDC on Base — Circle's production USDC at
0x833589fC...02913. We validated against both Sepolia and mainnet contracts; the EIP-712 domainnamediffers ("USDC"Sepolia vs"USD Coin"mainnet) and is keyed per-network inlib/constants.ts.
Three AWS services, all Terraform-provisioned:
- Amazon Cognito — User Pool + Hosted UI domain + confidential web app client. NextAuth v5's Cognito provider handles the OAuth code flow.
- Amazon DynamoDB —
payphone-sessions(hash keysession_id, with TTL onexpires_at) andpayphone-users(hash keycognito_sub). On-demand billing. - AWS Amplify Hosting — production deploy. The
amplify.ymlpins Node 22 + pnpm 10.33 + writes a.env.productionat build time so SSR runtime can read Console-set env vars (Amplify doesn't auto-forward env vars to Next's runtime, and reserves theAWS_*prefix for itself; we useAPP_AWS_*aliases in the app code).
Claude Haiku 4.5 powers two distinct features:
- AI expert suggester (
app/api/experts/suggest/route.ts) — one-shot non-streaming JSON response. The user types "I'm stuck on a Solidity gas issue", Haiku picks the best-matching seeded expert + writes a one-line reason. Direct@anthropic-ai/sdkcall. - Streaming recap + follow-up chat (
lib/haiku.ts) — Vercel AI SDK'sstreamTextfor token-by-token rendering. The system prompt pins the call transcript so chat answers are grounded in what was actually said. Falls back to a specialty-only summary if the transcript is empty (e.g., the second tab never joined).
Six real on-chain settlements. Two on Base mainnet, four on Sepolia. Same code path. Buyer paid no gas on any of them — the CDP facilitator did.
| # | Milestone | Network | Function | Amount | What it proved | BaseScan |
|---|---|---|---|---|---|---|
| 1 | M1 | Sepolia | exact |
$0.10 | x402 round-trip on the CDP facilitator works end-to-end | 0xc09fa4bf…7c47bc |
| 2 | M2 | Sepolia | upto |
$0.30 of $5.00 | Asymmetric verify/settle, contract-enforced via AmountExceedsPermitted |
0x3b2625f0…4f09cd1 |
| 3 | M3 | Sepolia | upto (88s × $0.01) |
$0.88 | Daily.co + DDB + duration-derived settle (no UI yet) | 0xfc70cf0c…29e781 |
| 4 | M4 | Sepolia | upto (84.84s × $0.01) |
$0.84 | Browser flow end-to-end: marketplace → call → recap, 61 transcript lines | 0x47dab9fe…39d6e33a |
| 5 | M6 warm-up | Mainnet | Settle With Permit (5s) |
$0.05 | EIP-2612 gas sponsorship works first-try on Base mainnet | 0x59ab4719…4d4dd8 |
| 6 | M6 rehearsal | Mainnet | settle (plain, 31s) |
$0.31 | Residual-allowance settle path — architecture self-optimizes between the two flavors | 0x95b4eba8…d14116 |
M4.9 active-window QA (no canonical hash — multiple ad-hoc settles): a
9-scenario QA pass on Sepolia validated active-window billing math (buyer
wait time isn't billed; ticker freezes when either party leaves; idempotent
under at-least-once webhook delivery). Each scenario produced its own
settle tx; all matched floor(active_window_seconds) × $0.01.
Buyer wallet (consistent across all six):
0xE01669A01E28E905055Ac6cD33c19ced7e10d870
— Achyut's CDP wallet, funded with $5 USDC at M0.
Seller wallet: 0x5c15772fd9132F2EaaCe0c55638fB674b0BaFC71 —
payphone-seller, the demo recipient.
CDP facilitator (paid all the gas):
0x8F5cB67B49555E614892b7233CFdDEBFB746E531.
x402UptoPermit2Proxy: 0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002 —
the ...0002 vanity address, same on every EVM chain.
The on-chain function alternates depending on the buyer's residual Permit2 allowance for USDC at the moment of settle:
Settle With Permitfires when residual Permit2 allowance <upto.amount. The CDP facilitator bundles a USDC EIP-2612 permit (which tops the allowance up byupto.amount) plus aPermit2.permitTransferFrom(which takes the actual settle amount) into a single tx. Both calls facilitator-paid.settle(plain) fires when the residual allowance is already enough. Just thePermit2.permitTransferFrom. Same single facilitator-paid tx, cheaper gas.
From the buyer's UX, both flavors are identical — one signature, no
manual approve, no buyer ETH. Once cumulative spend drains the residual
allowance below the upto.amount, the next settle fires Settle With Permit again to top up.
Eight surfaces, one flow. Sign-out → sign-in → marketplace → AI suggester → live call → recap. Mobile and desktop.
Public marketing page with BackgroundBeamsWithCollision, how-it-works
3-card breakdown, a server-fetched live last-call widget showing the
most recent settled session, and a built-on-by badge row.
Cognito Hosted UI sign-in card with the Aceternity Spotlight background
and PayPhone-tuned gradients. Click "Sign in with Cognito" → bounces to
Cognito Hosted UI for email + password (or sign-up if new) → returns to
/marketplace.
Cognito-gated. Four seeded experts in a responsive grid (1/2/3 col across
sm/md/lg). The wallet panel above the grid polls
/api/users/me/balance every 8s and conditionally renders "Fund my
wallet" when the balance < $5 on Sepolia. Network badge in the navbar
(orange = Sepolia, green = mainnet).
The free-form chat input above the expert grid sends to
/api/experts/suggest, which calls Haiku 4.5 to pick the best-matching
seeded expert. The chosen card gets a payphone-orange border tint, a
"Suggested" badge with the model's reason, and the page smooth-scrolls
to the match.
Daily.co iframe rendering, live billing ticker counting up at $0.01/sec in the sticky sidebar (desktop) or a sticky mini-bar (mobile), in-call transcript streaming both speakers, a pulsing ON AIR badge in the top-right of the video pane.
Settle status card with a pulsing "Settled on-chain" badge, the big
mono $X.XX · m:ss display, a BaseScan link in a payphone-orange chip,
and the Haiku-streamed AI summary in markdown. Below: a follow-up chat
box that answers questions grounded in the captured transcript.
The animated architecture flowchart self-draws on viewport entry. Same content as the Architecture section above plus the Why-These-Choices callouts and the on-chain proof grid.
Single-column marketplace, wallet panel stacked above the expert cards,
and the in-call sticky $X.XX mini-bar that floats over the iframe when
scrolled. 44×44 hamburger button (Apple HIG-compliant), 1.5px
custom-themed scrollbars.
| Marketplace | Live session |
|---|---|
![]() |
![]() |
Three steps. Browse → Talk → Settle. The agent does the signing; the chain does the enforcement; the human does the talking.
The user types a free-form prompt ("I'm stuck on a Solidity gas
optimization issue"). POST /api/experts/suggest calls Haiku 4.5 with
the four seeded experts as context; the model returns
{ expertId, reason }. The matching card highlights, the page scrolls.
If the user prefers to pick manually, they just click any card. The AI suggester is opt-in.
Clicking Talk to ... runs the startSession server action:
Browser
└─► server action (Next.js 16)
└─► lib/agent.ts: requestSession({ userId, expertId, topic })
└─► getUserBuyerAccount(userId) (lazy CDP wallet)
└─► POST /api/sessions
├─► verify (CDP facilitator: x402 upto, $5 max)
├─► createRoom (Daily REST, 30-min exp, max 2 participants)
├─► createSession (DynamoDB, status: AUTHORIZED)
└─► return { sessionId, roomUrl, maxAuthorized }
└─► redirect to /session/<id>
The session page mints a Daily meeting token with
canAdmin: 'transcription' and renders the iframe. Realtime
transcription captures both speakers; the local participant POSTs each
line to /api/sessions/[id]/transcript (with a de-dup filter — Daily
broadcasts to all tabs, only the local-participant tab persists). The
ticker counts from the moment participant.joined for the SECOND
participant lands — buyer wait time isn't billed.
When either participant leaves, Daily fires participant.left to
/api/webhooks/daily. The handler:
- Verifies HMAC-SHA256 against
DAILY_WEBHOOK_SECRET. - Appends the event to the session's
participant_events[]. - Recomputes the active-window via
lib/billing.tspure functions. - On first window-close, fires
settleWithRetry(3 attempts: 2s, 5s, 10s) withamount = floor(active_seconds) × M3_PER_SECOND_RATE_ATOMIC. - The CDP facilitator submits the on-chain tx (one of
Settle With Permitor plainsettledepending on residual Permit2 allowance). markSessionCompletedflips DDB conditionally onstatus = AUTHORIZED ∨ ACTIVEso duplicate webhooks no-op.- The buyer's
/statuspoll seesCOMPLETED→router.push('/recap').
The recap page streams the Haiku summary and exposes the BaseScan link. Total round-trip from "End call" click to recap render: ~5–15 seconds.
Four design choices that surprised us. Each is a "we tried the obvious alternative first, here's why it didn't work" story.
Stripe's per-transaction minimum is 30¢ + 2.9%. A 12-second call billed at $0.12 would cost the merchant 32.6¢ in fees against a 12¢ revenue — margin negative before the merchant sees a cent. Per-second billing isn't a UX problem. It's a payment-rail problem. The only way to fix it is to batch (charge once for an estimated amount) or to settle on a rail with sub-cent fees. PayPhone does the latter — Base mainnet gas is fractions of a cent, and the CDP facilitator pays it.
The exact x402 scheme requires the buyer to know the price in advance.
We don't — duration is whatever the call ends up being. With upto, the
buyer signs an "I'll spend at most $5" Permit2 witness; the
x402UptoPermit2Proxy contract enforces amount <= permitted.amount
on-chain via AmountExceedsPermitted. Unspent allowance never moves;
the chain doesn't care that we asked for less than we authorized. M2's
on-chain proof: signed for $5, settled $0.30, $4.70 simply expired.
CDP's paymentMiddleware / withX402 middleware settles at request
time. We need to defer settlement until the call hangs up — that's the
entire pitch. Hand-rolling the 402 → verify → settle dance lets verify
run when the buyer signs (synchronous, fail-fast on bad payment) but
settle run from the Daily.co participant.left webhook (asynchronous,
with the actual duration in hand). The middleware is great for static
paywalls; it's wrong for time-metered services.
Two USDC schemes are available on EVM: EIP-3009 (transferWithAuthorization)
and Permit2 (signatureTransfer). EIP-3009 is fixed-amount: signing for
$5 means $5 moves, period. Permit2 supports underspend: the witness says
"up to $5," the actual transfer can be any value $0 ≤ x ≤ $5. There is
no other primitive on EVM that lets a single signature authorize a range
and the chain enforce the cap.
The buyer-side agent signs without holding ETH because CDP's facilitator
declares eip2612GasSponsoring + erc20ApprovalGasSponsoring extensions
in the 402 challenge. The buyer signs a USDC EIP-2612 permit + the
Permit2 witness, and the facilitator combines them into a single
sponsored transaction. The buyer wallet stays gas-free. This is what
makes "no-wallet AI agents" work for the consumer side — they show up
with an API key, not a funded EOA.
app/ Next.js 16 App Router
page.tsx Public marketing landing
marketplace/page.tsx Cognito-gated expert browse + wallet panel
docs/page.tsx Public technical writeup w/ animated flowchart
login/page.tsx NextAuth Cognito sign-in card
session/[id]/page.tsx Live call page (Daily iframe + ticker)
session/[id]/recap/page.tsx Post-call summary + chat
session/[id]/timeout/page.tsx No-expert-joined timeout page
api/auth/[...nextauth]/route.ts NextAuth handlers
api/sessions/route.ts x402-protected session creation
api/sessions/[id]/{status,recap,
chat,transcript,retry-settle}/ Per-session API routes (ownership-gated)
api/users/me/{balance,faucet}/ Wallet panel API
api/experts/suggest/route.ts Haiku 4.5 expert picker
api/webhooks/daily/route.ts Daily participant.{joined,left,ended} → settle
lib/
constants.ts ALL chain/contract constants — keyed per-network
cdp.ts CDP client singleton
x402.ts facilitator client + retry settle
daily.ts Daily REST + webhook HMAC + meeting tokens
db.ts DynamoDB doc client + session/user CRUD
agent.ts requestSession — the buyer flow
user-wallet.ts Lazy per-user CDP wallet provisioning
session-auth.ts requireSessionOwner ownership guards
billing.ts Active-window math (pure, idempotent, commutative)
haiku.ts Anthropic streaming summary + chat
seed.ts Seeded experts (DEMO_EXPERTS)
components/ UI components (Navbar, SessionRoom, Recap, etc.)
infra/terraform/ AWS infra-as-code (Cognito + DDB + IAM)
scripts/ Diagnostic + admin scripts
pnpm dev # next dev (Turbopack)
pnpm build # next build
pnpm lint # eslint
pnpm format # prettier --write
pnpm format:check # prettier --check (CI-friendly)
pnpm test # tsx --test scripts/test-active-window.tsThe pre-commit drill is pnpm lint && pnpm format && pnpm build && pnpm test —
keep those green before pushing.
Diagnostic scripts under scripts/:
| Script | What it does |
|---|---|
wallet-setup.ts |
Resolves the buyer CDP wallet (idempotent) |
fund-check.ts |
Reads buyer USDC + ETH balance; --top-up --target=N faucet loop |
buyer-agent.ts |
Drives the x402 round-trip end-to-end from the CLI |
register-daily-webhook.ts |
Idempotent register/replace of the Daily webhook (deletes any stale webhooks pointing at our path) |
inspect-session.ts |
Reads a DDB session row by id; prints BaseScan URL when settled; --transcript/-t dumps captured transcript lines |
migrate-achyut.ts |
One-time DDB row linking the demo Cognito sub → existing M0-funded wallet |
probe-supported.ts |
Lists the (scheme, network) pairs the CDP facilitator advertises |
probe-usdc.ts |
Reads name/version/DOMAIN_SEPARATOR etc. from USDC on-chain |
verify-tx.ts |
Inspects a tx receipt + decodes USDC Transfer events |
test-active-window.ts |
Unit tests for lib/billing.ts active-window math + settle-amount cap. Run via pnpm test. |
8 unit tests under Node 22's built-in test runner. Zero new runtime deps. ~100ms total. Plus a 9-scenario manual QA matrix (M4.9) covering all participant-leave permutations.
| # | Test | Verifies |
|---|---|---|
| 1 | empty events → window never opens | An empty event log returns {start_ms: undefined, end_ms: undefined} |
| 2 | single participant only → window never opens | One participant joining alone never opens the window — buyer is not billed for waiting |
| 3 | two participants normal flow → opens at 2nd join, closes at 1st leave | Canonical case. Pre-expert wait time is not billed |
| 4 | out-of-order events → identical result | Daily's webhooks are at-least-once and unordered — feeding events reverse must produce the same window math |
| 5 | three participants → 1st pair opens, 1st-below-2 closes | Window opens at count first reaching 2 (not 3); closes when count first drops below 2 (not when room empties) |
| 6 | settle amount cap → duration × rate clamps at upto MAX | computeSettleAmount(durationSec) clamps at M2_UPTO_MAX_ATOMIC ($5) regardless of duration |
| 7 | idempotent under duplicate events | Re-applying the same joined event twice is a no-op (Set-replay) — duplicates do not inflate duration |
| 8 | open but not closed → uses nowMs as running end |
Drives the live client ticker — the running duration uses the supplied nowMs while the window is still open |
pnpm test # 8/8 should pass in ~100msWhat we'd build next if the hackathon had more hours.
- Federated Cognito sign-out. Our
signOut()clears the NextAuth session cookie but doesn't kill the Cognito Hosted UI session. A user who clicks "Sign out" then "Sign in" sees themselves auto-signed-back-in. Fix: append Cognito's/logout?logout_uri=...redirect into the sign-out flow. - Per-expert variable rates. Every expert currently bills $0.01/sec
uniformly. The seed data has a
displayRatefield that's purely marketing copy. To make rates real, plumb aper_second_rate_atomicthroughseed.ts → agent.ts → /api/sessions verify → settle. About 3 hours of work. - Bootstrap IAM cleanup (post-hackathon). Terraform was bootstrapped
with an
AdministratorAccessIAM userpayphone-bootstrap. The runtime userpayphone-appis already DDB-only-scoped. Deleting the bootstrap user's access key revokes the leak surface. - Real expert side. Currently a single user logs in and pretends to be both buyer and expert across two tabs. A real expert side would need expert sign-up, expert availability, and a marketplace seeding flow — out of scope for the hackathon, in scope for v1.
- Dual-network architecture today is a one-way valve. Local laptop
flips to mainnet via
NEXT_PUBLIC_ACTIVE_NETWORK=mainnetin.env.local. Production Amplify stays on Sepolia. A more production-grade approach would use separate Amplify branches (mainfor Sepolia,mainnetfor mainnet) with branch-scoped env vars. SETTLE_FAILEDrecovery is manual. A "Retry settlement" button on the recap re-fires fromSETTLE_FAILED→COMPLETEDif the user clicks. Automatic retry-on-cron would close the loop but adds a background scheduler we don't currently have. Acceptable for a demo.
- EasyA — for hosting Consensus Miami 2026 and the Agentic Track.
- Coinbase Developer Platform — for CDP Server Wallets v2, the x402 facilitator, and the gas-sponsorship extensions that make the buyer-side agent ETH-free.
- AWS — for Cognito, DynamoDB, and Amplify Hosting; the entire production stack runs on AWS infra.
- Anthropic — for Claude Haiku 4.5, which powers the AI expert suggester and the streaming recap + follow-up chat.
- Daily.co — for the WebRTC video rooms and the Deepgram-powered realtime transcription that grounds the recap.
MIT — see LICENSE (if present) or the standard MIT terms.
Built in 72 hours for a hackathon. Don't paste a real private key into
this codebase. Ever.







