Swix

Work in progress·Solidity · Hardhat · ERC-721 · React · wagmi · Privy · Base

swixtickets.com ↗

Swix resale listing page showing a $30.00 original price, a $36.00 maximum resale price, and the 20% cap above face value

Overview

Swix is a ticketing platform where resale rules are enforced by the contract rather than by policy. A ticket cannot be listed above a set percentage of its face value, and a share of every resale routes to the promoter automatically. There is no policy to appeal to. The transaction reverts.

Still in progress. Both contracts are deployed and working on Base Sepolia, the test network for Base, with a live marketplace frontend on top of them where fans sign in with email through Privy instead of a crypto wallet. Card payments are what I would want solved before calling it real. A Stripe backend for them is started but not finished.

The transfer restriction

Once a ticket can move directly between wallets, every other rule becomes optional. A price cap that can be routed around is not a cap.

TicketNFT overrides _update(), the function every mint, transfer, and burn passes through in OpenZeppelin v5, and blocks transfers between two real addresses unless the marketplace contract is the caller. Minting and burning pass through untouched. Wallet-to-wallet transfers revert. The marketplace becomes the only path a ticket can take between owners, which is what gives every other rule its authority.

What the marketplace enforces

Listing a ticket verifies ownership, confirms the ticket has not already been scanned at a gate, and rejects any price above 120% of that ticket's face value. Face value is stored per ticket at mint, so the ceiling is calculated per ticket rather than per event. A $50 general admission and a $100 VIP each carry their own cap with no additional bookkeeping.

Purchase splits payment in the same transaction that transfers the ticket, 5% to the promoter and the remainder to the seller. No invoicing and no payout delay, because the split is not a separate step.

Both percentages are constants, fixed at 120 and 5 across every event. That is the clearest limitation in the design as it stands. Making them per-event parameters set at deployment is the obvious next change, and it would let one contract serve an artist who wants no resale alongside one who wants an active secondary market.

Decisions that came from getting it wrong first

Several choices in these contracts exist because the obvious version had a problem.

The listing is deleted before any tokens or funds move. Transferring first leaves a window for a malicious contract to call back into buyTicket before the first call completes and drain the sale.

Payouts use call rather than transfer. Solidity's transfer forwards a fixed 2300 gas stipend that fails silently when the recipient is a contract wallet rather than a plain address, which promoters increasingly are.

Approval is checked at listing rather than at purchase. It could wait, but then the failure lands on the buyer for something the seller neglected to do. The error belongs with the person who can fix it.

Batch minting validates every array length before writing state, so a mismatched upload reverts whole instead of leaving an event half minted.

Thirty-eight tests cover the two contracts. The run I trusted most was the end-to-end test on Sepolia: mint a ticket, list it, buy it from a second wallet, confirm where the funds landed. The royalty split came out exact at 0.0005 ETH to the promoter and 0.0095 to the seller on a 0.01 ETH sale.

What I learned

  • I deployed TicketNFT before realizing it had no way to reference the marketplace contract I wrote afterward. Live contracts cannot be patched, so the fix was a fresh deployment and a full migration to the new address. I now spend considerably longer on an interface before it goes on chain.
  • Minting, gate scanning, and administration are three distinct powers. Collapsing them into a single owner address would have given a gate scanner the ability to mint tickets.
  • The blockchain layer is the part a user should never see. Every decision since has been about hiding it, with prices shown in dollars rather than ETH and email login and card payments so a fan never learns what a wallet is.
  • Gas costs determine the architecture. Fees on a $30 ticket can approach the price of the ticket on Ethereum mainnet, which pushed the project toward a cheaper chain before much else was settled.

Status

Both contracts are live on Base Sepolia, and swixtickets.com runs against them:

The Marketplace source is verified and readable. TicketNFT is not verified yet, so its page shows compiled bytecode rather than Solidity.

The same two contracts, unchanged, were first deployed to Ethereum Sepolia, where the end-to-end test above ran:

I moved to Base because gas on a $30 ticket has to round to nothing, and Base Sepolia mirrors Base mainnet, so launching there is a configuration change rather than a rewrite.

Next: finish card payments, per-event cap and royalty configuration, the move to Base mainnet, and a small pilot event to find out what breaks when the users are not me.

Solidity excerpts: the 120% price cap and 5% royalty constants, the listing checks, the payment split in buyTicket, and the _update override that restricts transfers to the marketplace
The resale rules as written in Marketplace.sol and TicketNFT.sol
Swix marketplace showing a general admission ticket listed at $36.00
Marketplace browse view with a live listing
Sequence diagram of the Swix contracts: promoter mints tickets, seller approves and lists under the 120% cap, buyer purchases, the marketplace transfers the ticket and pays 5% to the promoter and 95% to the seller in one transaction, the gate scanner marks tickets used, and direct wallet-to-wallet transfers revert
Contract and settlement flow