Field Notes

Why our float receipts are designed to live on an L2 someday

A technical walkthrough of the Float Receipt abstraction, post-quantum signature design, and the Ethereum L2 settlement path we have built toward without committing to yet.

Why our float receipts are designed to live on an L2 someday

A note before you read: this post describes architectural intent, not shipped product. The on-chain path is real; the timeline is not fixed. If you are an engineer doing diligence, the source of truth is the code.


We run a prediction market on fine-art auctions. Bettors lock virtual points against a lot's low estimate; the platform settles outcomes when hammer prices are published and distributes payouts parimutuel-style. Today, every locked stake is a row in a Postgres database. A bettor who locks 500 points against a Basquiat at Christie's gets a database record and a UI confirmation. That is it.

This is perfectly fine for where we are. What we have done in Sprint 5 is make it less fine to keep it that way indefinitely. We have introduced the Float Receipt abstraction -- a structured object that represents a locked stake, carries a cryptographic attestation signature, and is designed to become an ERC-1155 token on an Ethereum L2 in Phase 6 without a rebuild.

This post explains why we made these choices now, what the Float Receipt looks like, how the signature scheme is designed for post-quantum agility, and where the L2 fits in.


The problem with "just a database row"

At current scale, a database row works. The problem is the architectural ceiling it implies.

Sophisticated bettors -- and eventually, institutional ones -- will not lock seven-figure float in a centralized prediction platform. "Trust us, we won't misappropriate your stake" is a weak promise. An auction-house counterparty that wants a verifiable proof-of-funds attestation needs something stronger than our word. And as float pools grow, a single-operator custodian model attracts regulatory attention that a non-custodial, smart-contract-backed design sidesteps.

The architecture that solves all three is non-custodial float on an Ethereum L2. Bettors lock USDC into a smart-contract escrow. The escrow attests proof-of-funds to a designated auction-house verifier. Settlement is automated by an oracle reading hammer prices. Payouts route directly to the bettor's wallet without ever touching our balance sheet.

We are not doing this today. We are building so it is a feature flag, not a rebuild, when we do.


The Float Receipt object

A Float Receipt carries:

  • receipt_id -- a UUID that threads through our append-only audit log, giving every mutation a traceable identity without a JOIN.
  • holder_id -- the bettor's identity.
  • lot_id -- which auction lot the stake is locked against.
  • amount -- the locked stake (points today; USDC on-chain in Phase 6).
  • lock_window_start / lock_window_end -- the window during which the stake is locked.
  • claim_conditions -- opaque JSON. Today this is {}. In Phase 6 it will carry the EIP-712 domain separator, the escrow contract address, and the chain ID.
  • attestation_signature -- the platform's cryptographic attestation that this receipt was issued and has not been tampered with.
  • signature_primitive -- which cryptographic scheme produced the signature. ECDSA-secp256k1 today.

That last field is the key design decision. It is a free-text discriminator stored alongside the signature, not a hard-coded assumption baked into the schema. When we provision a post-quantum signing key, new receipts will carry ML-DSA-65 in that field. Existing ECDSA receipts remain valid. No schema migration, no backfill, no flag day.


The signing pipeline

Signing is performed server-side by a Supabase Edge Function. It reads the signing key from Supabase Vault, never from an environment variable in production. The function has a single abstraction boundary -- a signPayload() function -- that takes a canonical JSON payload and a JWK private key and returns a Base64url-encoded signature.

The canonical form of the payload is defined in a PostgreSQL helper function, not in application code. This means the server and any future verifier agree on exactly what was signed without reading a TypeScript file. The receipt hash (SHA-256 of the canonical payload) is stored separately from the signature, so callers can verify payload integrity even without access to the public key.

To swap ECDSA for ML-DSA-65 when the time comes: provision an ML-DSA key in Vault, update the primitive environment variable, and replace the signPayload body with an ML-DSA implementation. Nothing else changes. The database, the audit log, the client rendering layer -- all untouched.


Why post-quantum agility matters now

ECDSA is not broken. But it is on the long-fuse list of primitives that a sufficiently advanced quantum computer would retire. This concern is sometimes dismissed as science fiction. It should not be, for two reasons.

First, harvest-now-decrypt-later: a well-funded adversary that records signed data today can break ECDSA offline when a quantum computer becomes available. For ephemeral data this barely matters. For a float receipt that anchors a six-month guarantee, the tail is long enough to think about.

Second, regulatory alignment: NIST finalised three post-quantum standards in 2024 -- ML-KEM (FIPS 203, based on Kyber for key encapsulation), ML-DSA (FIPS 204, based on Dilithium for digital signatures), and SLH-DSA (FIPS 205, based on SPHINCS+ for stateless hash-based signatures). FALCON is in late-stage standardisation as a compact lattice-signature alternative with smaller signatures than ML-DSA. European financial regulators have started asking about PQC migration plans in diligence conversations. We want to be able to answer those questions with specifics.

Our approach is not to run a cryptographic research project. It is to stay on the NIST corpus, build the signature field with a primitive discriminator from day one, and maintain an internal PQC readiness inventory that maps every signature and KEM usage in the stack to its migration cost and effort estimate.


The Settlement Oracle

Settlement today is a two-step process: an admin records the hammer price from the auction-house results page; the system distributes payouts parimutuel-style. We have abstracted the first step behind a SettlementOracle interface with a single method: resolveLot(lotId) returning a structured HammerPriceResult.

Today's implementation reads from our Postgres lot_outcomes table -- the same table the ingest functions write to. The interface exists so Phase 6 can swap in a Chainlink Functions call (for automated, trustless price feeds on EVM chains) or a UMA Optimistic Oracle adapter (for high-value lots that benefit from a dispute window and economic bond). Swapping implementations requires changing one line in the Edge Function that calls the factory; the settlement and payout logic is untouched.

This mirrors the oracle-modularity pattern the Ethereum ecosystem has converged on: define the interface once, ship the simple implementation, and upgrade the provider when the use-case demands it.


Why an L2, and why Ethereum

L1 calldata costs make per-bettor float-locking uneconomic at retail stake sizes. At prevailing gas prices, a simple ERC-20 lock on Ethereum L1 costs more than the stake itself for a typical retail bettor. This is not a temporary market condition; it is structural.

The post-Dencun and post-Pectra L2 ecosystem changes the economics substantially. EIP-4844 (included in the Dencun upgrade) introduced blob transactions that reduced L2 calldata costs by roughly 90 percent. The Pectra upgrade continues this trajectory with increased blobs-per-block. Base, Arbitrum, Optimism, and zkSync now offer sub-cent costs for simple token operations while inheriting Ethereum settlement assurance.

Our preference at this stage is Base for the consumer cohort -- Coinbase's distribution and the Base ecosystem's focus on mainstream onboarding align with our bettor profile -- and zkSync for institutional bettors who want ZK proof guarantees over optimistic fault proofs. Cross-rollup messaging via standard bridge protocols handles the edge case where a single lot has bettors across multiple rollups.

Account abstraction via EIP-4337 completes the picture. It lets us onboard users today with email or social login -- no wallet required, no seed phrase, no browser extension -- and bind a self-custody wallet address to their account later, optionally and non-custodially. The user model has a wallet_address field that is nullable and off by default. When the non-custodial path opens in Phase 5-6, users who want it can claim their float receipt on-chain without any forced migration for everyone else.


What this means for bettors today

Nothing changes immediately. If you are betting on a Lucian Freud at Sotheby's this week, you lock points the same way you always have. The Float Receipt is created and signed in the background. The audit log records it. You see a confirmation in the UI.

The work described in this post is infrastructure. It is the reason the Phase 6 on-chain migration will be a feature flag and a press release, not a crisis and a rewrite.

We mention this here for the same reason an architect tells a client about the structural steel: it is not what you came to look at, but it is why the building stands up.


Technical review note: the PQC standardisation timeline, L2 cost figures, and Pectra upgrade details are accurate as of May 2026 and should be re-verified before any external circulation or investor sharing. These specifications evolve. -- Engineering team

Stay in the loop

One email per major sale night. Hammer math, market signals, and nothing else.

No spam. Unsubscribe any time. We'll send one confirmation email first.