Add Starknet ticketing contracts and deploy to Sepolia - #1
Merged
Conversation
Zicket had Starknet and zk-verification trust markers on the homepage but no on-chain layer, and the "Get Ticket" button did nothing. This adds the missing layer end to end. Contracts (contracts/, Cairo 2.14 + snforge): ZicketEvents supports two kinds of ticket. A public ticket binds to the buyer's address, one per wallet. An anonymous ticket is a commitment: the buyer computes poseidon(secret, nullifier) in the browser and only that reaches the chain, so the ticket has no owner at all. Check-in reveals the preimage and burns poseidon(nullifier), which is what stops a ticket being used twice without ever identifying who held it. Also covers ERC-20 payment, per-event escrow with organizer withdrawal, a platform fee in bps, and a cancel/refund path. MockERC20 provides a payment token for local runs. 36 snforge tests. Backend: tickets gains its on-chain columns and a new ticket_purchases table keyed on the transaction hash. POST /api/chain/purchases accepts only a tx hash and reads every other field back from the receipt, so a client cannot fabricate a ticket. Anonymous purchases are stored without a buyer address or email -- recording either would defeat the commitment scheme. Listings are published on-chain by a server relayer, so an organizer does not need a funded wallet to list. Frontend: Wallet connection is built on starknet.js's own WalletAccount plus SNIP-1193 discovery. get-starknet-core was tried and rejected: its current release still targets the starknet v5 API and does not work against v8. The purchase flow writes an anonymous ticket's secret to localStorage before the transaction is signed, because the secret is the only proof of ownership and exists nowhere else -- if the tab dies mid-flight the attendee can still redeem. The UI says plainly that clearing site data loses the ticket. Notes: Seeded events are rebased onto the current date. Every fixture date was in the past, which made all of them unpurchasable once the sale window was enforced. Recording a purchase now reports whether the row was new, because retrying the same transaction hash was inflating the attendee count. Covered by a test. src/index.ts can point the Neon driver at a local HTTP proxy, so the stack runs against a plain Postgres container; unset in production. Verified against starknet-devnet in Docker: 36 contract tests, 20 assertions directly against the deployed contracts, and 32 through the running app. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Both contracts are live on Sepolia and the app runs against them. Deploying the mock ERC-20 outside devnet is now opt-in via ZICKET_DEPLOY_MOCK_TOKEN so a real payment token can't be swapped out by accident; on Sepolia the mock is intentional, since its permissionless mint doubles as a faucet. The e2e harnesses hardcoded devnet's predeployed accounts, so neither could run anywhere else. scripts/starknet/buyers.ts provisions buyers per network: predeployed on devnet, otherwise generated, funded from the deployer and counterfactually deployed, with keys cached in .env.local. Scope on-chain linkage to a deployment. Event ids restart at 1 for every ZicketEvents deployment, so tickets.onchain_event_id alone was ambiguous: after repointing at Sepolia, listings still carrying devnet ids resolved to zero-filled events, publish short-circuited as "already published", and buys reverted. Tickets now record the contract address that issued the id, and a mismatch reads as unpublished. 20/20 contract assertions and 32/32 full-stack assertions pass against live Sepolia. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Record the live contract addresses, class hashes, deployer and RPC in the README with explorer links, and note the JSON-RPC 0.9.0 requirement that constrains which public endpoints work. Replace the create-next-app boilerplate header and footer, which described neither the project nor how it is deployed, with a summary of what Zicket is and a table of the scripts. Drop the Connect Wallet button from the navbar. Connecting is only meaningful at checkout, so the purchase button now renders as Connect Wallet until a wallet is attached — previously the card just told the user to find the button in the header. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Three jobs on every PR: the Cairo contracts (fmt, build, 36 tests), the app (lint, typecheck, build), and a devnet deploy running the 20 contract assertions. None of this was checked automatically before. The build job needs a database, since / and /explore are prerendered from it, so it brings up Postgres behind the same Neon HTTP proxy the README documents for local development. Migrations run through psql: drizzle-kit picks the Neon WebSocket driver, which that proxy does not serve. scarb fmt --check required reformatting. The section banner comments were long enough to exceed the line limit, so the formatter wrapped them onto a second line and split the rule from its heading; shortening them keeps the banners intact and the formatting canonical. Verified by reproducing each job locally, including a build against an empty database and a deploy plus e2e against a fresh devnet with no .env.local. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A bare POST to the Neon proxy's /sql is rejected with 400 without its connection headers, so curl -f read a healthy proxy as a failure and the job timed out waiting on a service that was already up. Wait on the port instead. Also point setup-scarb at contracts/Scarb.lock so it can cache dependencies; the workspace is not at the repository root. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the on-chain ticketing layer to Zicket: a pair of Cairo contracts, the API and UI wiring around them, and a live deployment on Starknet Sepolia.
Contracts
ZicketEventsMockERC20Two kinds of ticket:
commitment = poseidon(secret, nullifier)in the browser and only the commitment reaches the chain. The ticket has no owner. Check-in reveals the preimage and burnsnullifier_hash = poseidon(nullifier), so a ticket cannot be used twice.The secret never leaves the browser, and is written to
localStoragebefore the transaction is signed — it is the only proof of ownership and exists nowhere else.Live on Sepolia
ZicketEvents0x7b318fc4…52b8cbbZUSD(18dp)0x785ab439…3333f15The mock ERC-20 is deliberate here — its
mintis permissionless, so it doubles as a faucet and the testnet demo is self-contained. Deploying it off devnet is gated behindZICKET_DEPLOY_MOCK_TOKEN=1so a real payment token can't be swapped out by accident.Trust boundary
POST /api/chain/purchasestakes only a transaction hash; every other field is read back from the receipt, so a client cannot fabricate a ticket. Anonymous purchases are stored without a buyer address or email — recording either would defeat the commitment scheme.Cross-deployment bug found during Sepolia bring-up
Event ids restart at 1 for every
ZicketEventsdeployment, sotickets.onchain_event_idalone was ambiguous. After repointing at Sepolia, listings still carrying devnet ids resolved to zero-filled events, publish short-circuited as "already published", and purchases reverted inside the account. Tickets now record the contract address that issued the id, and a mismatch reads as unpublished.Also
scripts/starknet/buyers.tsprovisions buyers per network — predeployed on devnet, otherwise generated, funded from the deployer and counterfactually deployed.create-next-appboilerplate with the deployment record, environment reference and script table.Verification
snforgepnpm chain:e2e— against live Sepoliapnpm chain:flow— against live Sepoliatsc --noEmit,pnpm buildIncludes Poseidon JS↔Cairo parity, anonymous-ticket unlinkability, nullifier double-spend protection, replay idempotency, escrow accounting, and rejection of an unrelated transaction submitted as a purchase.
Migration
0001_stormy_galactus.sqladdstickets.onchain_contract_addressand needs applying on deploy.Note: buying through the browser on Sepolia needs Argent X or Braavos — the burner is devnet-only — so that path was verified via the headless harness rather than a real extension.