Get high signal, high intent guests for every event.
RSVP with a refundable deposit, held on Base. Show up and it comes back; flake and it goes to the people who did.
Live app · Contract on Basescan · A real settlement, recomputed from the chain
Hundreds of people RSVP for events on Luma, and very few show up. Showed solves this problem by charging a temporary entry fee. If you show up to the event, you get your entry fee back. If you don't show up, your entry fee gets distributed to those who do.
| What it does in this app | Where | |
|---|---|---|
| Hardhat | Compiles BondedEvents.sol, runs 25 unit tests plus 2 against live Base USDC on a mainnet fork, deploys, verifies on Basescan. Its artifacts generate lib/abi.ts. |
contracts/ |
| viem | Decodes a settlement's calldata and recovers who signed the attendance list. Builds the relayer wallet, splits guest signatures into v,r,s, fails over across four RPCs. |
lib/chain.ts, lib/relayer.ts, app/api/verify |
| wagmi | Three hooks: useSignTypedData, useSignMessage, useAccount. |
app/e/[slug], components/auth.tsx |
flowchart TD
A["Guest signs a USDC authorization<br/>(no gas, no transaction)"] --> B["Relayer submits it<br/>and pays the gas"]
B --> C[("Deposit locked<br/>in the contract")]
C --> D{"Organizer signs<br/>the attendance list"}
D -->|Checked in| E["Gets the deposit back<br/>plus a share of the no-shows'"]
D -->|No-show| F["Forfeits the deposit"]
F --> E
C -.->|"Organizer never settles<br/>(48h after the event)"| G["Anyone can refund everyone"]
Not for speed, and not for payments. For two properties Postgres cannot offer.
Nobody can spend the deposits — not the organizer, not us. That is what lets someone collect money from their friends without becoming a custodian, and it is precisely why Luma can't ship this feature.
Anyone can check the payout. /verify reads Base, recomputes every figure from event logs, recovers the signing key from the transaction's calldata, and shows its working. It never touches our database. Paste a settlement and check our arithmetic against the chain's.
One deployment holds every event: contracts/contracts/BondedEvents.sol.
| Function | |
|---|---|
createEvent |
id, organizer, deposit, capacity, times, forfeit rule |
rsvpWithAuthorization |
escrows a deposit from an EIP-3009 signature |
cancelRsvpWithSig |
gasless cancellation, until the event locks |
settle |
one transaction: an EIP-712 attestation over a Merkle root of attendees |
claimFor |
pull payment against a Merkle proof |
rescue |
anyone can refund everyone, 48h after an unsettled event |
Three pieces carry the design:
- A guest deposits USDC having never held ETH, sent a transaction, or approved a token.
receiveWithAuthorizationpinsmsg.sender == to, so a signed authorization is useless to anyone who intercepts it. - Attestations make the organizer's signature the oracle. Our server can only relay what they signed; it cannot decide who turned up.
- Merkle batch settlement closes a 200-person event in one transaction instead of 200.
# contracts
cd contracts && npm install
npx hardhat test # 25 unit tests
FORK=1 npx hardhat test test/fork.test.js # 2 tests against live Base USDC
npx hardhat run scripts/deploy.js --network base
# app
cd web && npm install && npm run devcontracts/.env needs RELAYER_PRIVATE_KEY and BASESCAN_API_KEY.
web/.env.local needs DATABASE_URL, NEXT_PUBLIC_PRIVY_APP_ID, PRIVY_APP_SECRET, RELAYER_PRIVATE_KEY, APP_SECRET.
Set SPONSOR_DEPOSITS=true to have the treasury cover deposits instead of guests funding their own.
web/scripts/e2e.mjs runs the whole cycle against Base mainnet with real money — create, RSVP, check in, settle, verify.