Tempo on/off-ramp for AcmeUSD, built on the Tempo L1 with passkey wallets, a bank-like UX, and scripted TIP-20 setup.
- Getting Started
- Features
- Architecture Overview
- Scripts
- App Scripts
- Backing & Fee Liquidity Model
- Production-readiness Notes
- Node 20+
- Postgres database (Supabase or local Postgres)
- Access to Tempo testnet RPC (defaults are provided)
npm installCopy the example file and edit values for your environment:
cp .env.example .env.localDo not commit real secrets. The example values are placeholders for local development.
Run Prisma migrations against your Postgres database:
npx prisma migrate deployFor local iterating you can also use:
npx prisma migrate devStart the dev server:
npm run devThen open http://localhost:3000 in your browser.
- Deposit USD into an AcmeUSD balance via a mock card/bank provider (onramp).
- Withdraw AcmeUSD back to an attached payment method (offramp), with 1:1 backing.
- Send dollars between Tempo wallets and view all activity in a unified History view.
- Pay Tempo network fees using AcmeUSD via a fee liquidity pool (AcmeUSD/alphaUSD).
-
Web app (Next.js App Router)
- Pages under
src/app: home,onramp,offramp,transfer,history. - Client-side wagmi hooks for Tempo passkey wallets and AcmeUSD transfers.
- Pages under
-
Tempo integration
tempo.ts/ viem client insrc/lib/tempoClient.tsfor backend/scripts.- wagmi config in
src/lib/wagmiConfig.tsxfor passkeys and fee token defaults. - Uses TIP-20 AcmeUSD token deployed via Tempo’s TIP-20 factory (see scripts).
-
Data / persistence (Postgres + Prisma)
- Prisma schema in
prisma/schema.prismawith models for:User,PaymentMethod– wallet address + attached card/bank method.AuthChallenge,Session– challenge-response auth and session tracking.OnrampOrder,OfframpRequest,Payout– fiat rails and off-ramp lifecycle.Transfer– peer-to-peer AcmeUSD transfers (for History).BackingSnapshot– snapshots of fiat vs on-chain supply for backing checks.IndexerState,ProcessedOfframpLog– off-ramp indexer cursor and logs.IdempotencyKey– request idempotency records for sensitive operations.
- Prisma schema in
-
Offramp flow (client notify + on-chain verification)
- Primary path: client sends an AcmeUSD transfer to the treasury address, waits for Tempo’s fast finality, then calls
/api/offramp/notifywith the transaction hash and log index. - Backend independently verifies the transaction and log on-chain before updating the
OfframpRequest, recording aPayout, and burning supply via backing snapshots. - A cursor-based scanner in
src/lib/indexer/offrampIndexer.tsis kept as a safety/backfill mechanism but is no longer the main correctness path.
- Primary path: client sends an AcmeUSD transfer to the treasury address, waits for Tempo’s fast finality, then calls
-
Payment provider & API routes
- API handlers in
src/app/apiimplement:- Onramp creation, payment success, and minting.
- Offramp request creation,
notifyendpoint for client-submitted tx receipts, and payouts.
- Real card/bank rails (e.g., Stripe) can replace the current implementation behind the same interface.
- API handlers in
-
Deploy AcmeUSD via TIP-20 factory and set roles
npm run script:deploy-acme
Outputs the token address to place in
NEXT_PUBLIC_ACMEUSD_ADDRESS. -
Check fee pool reserves (AcmeUSD vs alphaUSD)
npm run script:check-fee-pool
-
Seed fee pool with validator-side fee liquidity
Seeds the AcmeUSD/alphaUSD fee pool using alphaUSD only (validator token on testnet; no AcmeUSD deposit required).
npm run script:seed-fee-pool
-
Sanity check paying fees in AcmeUSD
Mints a small amount of AcmeUSD to the admin account, performs a self-transfer paying fees in AcmeUSD, then burns back to zero float.
npm run script:test-fee-acme
Was previously the primary service that updated the backend for offramps, but now serves mainly as a reconciliation example.
-
Scan off-ramp logs once
npm run script:offramp-worker
-
Watch mode (continuous scanning)
npm run script:offramp-worker -- --watch
npm run dev– Next dev server.npm run lint– ESLint.npm run format/npm run format:write– Prettier check/write.npm run typecheck–tsc --noEmit.
- No AcmeUSD is pre-minted for fee liquidity; user onramps are the only source of AcmeUSD.
- Offramps burn AcmeUSD when received so total supply stays fully backed by USD deposits.
- Fee pool is AcmeUSD (user token) vs alphaUSD (validator token on testnet), with validator-side liquidity provided in alphaUSD. Validate reserves before/after seeding with
npm run script:check-fee-pool. - Fiat fields (
OnrampOrder.usdAmount,OfframpRequest.usdAmount,Payout.amountUsd, backing totals) are stored in cents (minor units).
- Auth for API routes – Session-based auth is in place (
/api/auth/*+acme_sessioncookie) and onramp/offramp routes derive identity from the session. Remaining work: implement/verify Tempo passkey signature verification (WebAuthn/P256/keychain) server-side so non-secp256k1 accounts can authenticate reliably. - Env validation – Enforce required env vars (RPC URLs, ACMEUSD address, treasury/issuer keys + addresses, admin list, DB URLs) with a runtime schema (e.g., zod) so the app fails fast on misconfiguration.
- Payment rails – Mock provider only. Real card/bank rails must handle KYC/AML, idempotency, retries, and webhook verification.
- Backend service boundaries – Move domain logic out of route handlers into explicit services (e.g.,
OnrampService,OfframpSettlementService,TransferService) so retries, idempotency, and reconciliation are consistent across API routes, jobs, and scripts. - Observability (logs + metrics) – Replace
console.*with a structured logger and standardize fields (requestId, orderId, txHash, logIndex). Emit metrics/traces for onramp/offramp errors, payout queue depth, and backing deltas (fiat deposits – withdrawals vs on-chain supply). Alert on any mismatch, stuck settlements, or stalled payouts. - Secrets & ops – Use secure secret management and key rotation for treasury/issuer keys. Remove dev faucet or lock behind verified admin identity in production.
- Indexer (deprecated) – The off-ramp scanner is backfill-only. Prefer the client-driven notify flow; Add alerting and retries.
- CI gates – Run
npm run lint,npm run typecheck,npm run build(and tests when added) in CI; block merges on failure.