Search projects, hackathons, sponsors and tech

    ← All projects

    Reshuffle

    Swap tickets without selling first — sign the outcome you'll accept, settle it on Arc.

    Demo

    About

    RESHUFFLE is a market for outcomes, not listings. Ticket holders who want to change tickets are forced to speculate. Resale makes you sell first and buy back, so the risk window is yours: sell your Friday pair and the Saturday pair may be gone, or buy first and you're carrying two sets. Official exchange usually requires the same event, venue and date at equal or higher price, so a date change falls outside it entirely. Worse, the trade that works often doesn't exist bilaterally. A holds Friday and wants Saturday, B holds Saturday and wants Sunday, C holds Sunday and wants Friday. Every pairwise negotiation fails; all three together clear. And even when a match exists, it stalls until every participant comes back online and approves that exact proposal. RESHUFFLE removes both. A user deposits their tickets into escrow and signs one EIP-712 intent describing the outcome they will accept: give up these tickets, only if I receive exactly two Saturday tickets in the same section with adjacent seats, and pay at most 30 USDC net, valid until Friday 18:00. Then they close the tab. A solver later composes many such intents — swappers, pure buyers, pure sellers, and unsold issuer inventory as a standing participant — into one reallocation where every signed condition holds simultaneously. The Settlement contract re-verifies each condition independently and either moves every ticket and every USDC payment in a single transaction, or reverts. The solver is untrusted; the contract is the trust anchor. That's the whole point: nobody is present to click confirm, so the outcome predicate has to be enforceable on-chain. On Arc Testnet we settled a three-party six-ticket reshuffle in one transaction, a four-party open chain with a pure buyer and pure seller, and confirmed that a non-adjacent allocation reverts with SeatsNotAdjacent. In the sign-and-leave demo, the participant browsers and signing process had exited before a separate solver settled — participant nonces unchanged, no new signatures required.

    How it's made

    RESHUFFLE is four Solidity contracts, a bounded TypeScript solver, a subgraph, and a Next.js app. Contracts (Arc Testnet, Foundry). TicketNFT is ERC-721 with seat metadata packed into one storage slot. Escrow holds deposited tickets; withdrawal is unconditional, but releaseBatch is onlySettlement — so Settlement is the only actor that can move an escrowed ticket to someone else. IntentRegistry.commit() is the only place a signature is ever checked (EIP-712, domain-bound to chainId and verifyingContract). Settlement.settle(Intent[], Leg[]) runs V0–V8 in the real transaction: intent state, escrow custody read at settlement time, redemption status, event/session/section masks, exact ticket count (exact, never a minimum), cohesion, seat adjacency, maxNetPay as a signed bound covering both payers and receivers, conservation, and USDC balance plus allowance capacity. Measured settle gas: 208k for a two-party exchange, 379k for a three-party six-ticket reshuffle. Arc / Circle. USDC is native gas, so a participant never needs a separate gas token, and cash legs settle in the same asset the constraint is denominated in. Net payments are grouped by wallet and are zero-sum excluding gas. The Graph. The registry stores commitment state and nonces but not an enumerable pool of matching conditions — those live in IntentCommitted. So an indexer is structurally required, not decorative. The subgraph indexes Ticket, Intent and Settlement; escrow custody is derived from NFT transfers. Three trust mechanisms: the shared hashIntent() recomputes every indexed commitment and rejects altered fields as HASH_MISMATCH; every read after a write is pinned with block: { number_gte: receiptBlock } so a stale snapshot can never replace newer state; and indexed discovery is always followed by chain reads and eth_call simulation before broadcast. A real transfer was observed indexed 7.3s after confirmation. Solver. Bounded search over the live pool — max 4 participants, 100 candidates, 2000ms — ranked by gross cash moved, then participant count, then intent-hash set. POST /api/solve/pool returns snapshotBlock, bounds, candidates, exclusions and simulation evidence. Agent. A read-only Claude agent with three tools (diagnose_intent, what_if, pool_overview) answers "why didn't this match?" from solver evidence, not from vibes. Every answer is pinned to one indexed block N, all balance reads use blockNumber: N, and a guard requires the answer to open with At Arc Testnet block #N, and to match complete evidence passages. Diagnosis works with no model key at all via a deterministic path. The agent has no signing capability. Hacky bits worth naming. foundry.toml sets evm_version = "paris" as a workaround for the open PUSH0 compatibility issue on Arc Testnet, even though Arc's docs describe Prague — flagged for re-test before mainnet. Gas estimation on some USDC writes is unreliable, so deployment scripts pass explicit gas limits. And Settled only emits hashes plus a participant count, so per-leg receipts and net USDC payments are decoded from transaction input and re-verified through RPC rather than indexed as entities.