bloom22

Inspiration

While reading the Stay22 API docs, we realized we were looking at a live, multi-vendor price feed. The same accommodation inventory appears across Expedia, Booking.com, Hotels.com, and Vrbo, with prices changing based on demand, seasonality, and events.

That makes hotel pricing structurally similar to a volatile underlying asset. bloom22 turns hotel inventory into a live options exchange, using the median nightly price of a room in Paris as the underlying asset.

What it does

bloom22 converts live Stay22 inventory into a synthetic, tradeable price index and runs a full options market on top of it. The money is simulated, but the market mechanics are real.

  • The underlying: Live Stay22 listings are distilled into a single median nightly index, called the spot price, along with an annualized realized-volatility estimate. Both are refreshed on a scheduled ingest cycle.

  • The market: Traders buy calls and puts from a fixed strike grid priced in real time with Black-Scholes. An always-liquid market maker quotes bid and ask prices. Traders buy at the ask and close at the bid.

  • The Oracle: Gemini acts as an AI desk analyst. It researches each market using function calling and Google Search grounding, runs a bull-versus-bear debate between two model personas, selects a specific strike from the live chain, and delivers its recommendation as a spoken trading-floor squawk through Gemini's native TTS.

  • Identity: Auth0 Universal Login gates every account. Each paper bankroll is bound to the user's Auth0 subject, so traders remain the same account across sessions.

  • On-chain provenance: Traders can link a Solana wallet by signing a nonce challenge. Every fill is anchored as a tamper-evident receipt on Solana devnet.

  • Time-warp settlement: An operator advances the market clock to the check-in date, fetches a fresh Stay22 price, and settles each option at its intrinsic payoff. The leaderboard ranks traders by liquidation equity rather than headline P&L.

How we built it

Core trading system

The core stack uses Next.js and Supabase Postgres.

Every money-moving operation, including execute_trade, close_position, and settlement, runs through an atomic Postgres RPC. Each function locks the relevant market and account or position rows, checks a quote-staleness token, and verifies that the client's last-seen market_state.updated_at still matches the current value.

Balances are updated in a single transaction. Client-generated idempotency keys are persisted with the resulting post-trade balance under a unique (account_id, idempotency_key) index. If a request is retried, the original result is returned instead of allowing a second charge.

Every trade-moving RPC is revoked from anonymous and authenticated database clients. These functions are reachable only through the service-role backend.

Stay22 index engine

lib/ingest.ts calls the Stay22 v2 accommodations endpoint under a hard budget: one 8-second timeout and a maximum of three pages.

For each property, the ingest process keeps the cheapest valid supplier total. It requires at least three priced properties before producing an index. With fewer results, it reports an empty state instead of guessing.

Realized volatility is calculated from the actual captured_at intervals between observations. Cross-sectional dispersion across listings is used only as a cold-start fallback before real time-series history exists. It is never blended with temporal volatility.

Pricing volatility is clamped to a 5% to 200% range so a single noisy observation cannot break option pricing.

Raw listings never leave the ingest module. Only the derived median, bounded volatility, currency, and sample size are persisted. Each observation is tagged with a source_status of seeded, live, or stale, so the UI cannot present fallback data as fresh.

A scheduled job re-ingests every open market on a fixed interval.

Gemini Oracle

The Oracle runs in two deliberately separate phases against Google's Gemini API.

The first phase is an agentic function-calling loop with up to six tool turns. It combines Google Search grounding with four tools that expose only derived market surfaces:

  • get_market_state
  • get_chain, including the exact tradeable strike grid and Greeks
  • get_datetime
  • get_weather_climate, using an Open-Meteo forecast or seasonal climatology

Two independent model calls then argue the trade from bull and bear perspectives.

The second phase is a separate, tool-free structured-output call. It converts the debate into a schema-validated desk note containing the analysis, direction, side, strike, confidence, and cited sources. The strike is constrained to one of the options actually available on the live chain.

Separating grounding from structured output fixed a real failure mode. Combining Google Search tool use with strict structured output in a single call was unreliable.

The finished note is rendered to audio through Gemini's native TTS using gemini-2.5-flash-tts and the "Puck" voice. The result is returned as a base64 WAV file, allowing the desk analyst to read the recommendation aloud.

Auth0

@auth0/nextjs-auth0 handles Universal Login.

POST /api/session mints a paper-trading bearer token bound to the Auth0 subject. The token is sent as Authorization: Bearer, with an HttpOnly same-origin cookie fallback for browser flows.

Solana receipts

Wallet linking uses a nonce challenge with the format:

bloom22:link-wallet:v1:...

The challenge is signed client-side with the user's wallet and verified server-side with ed25519 using tweetnacl and bs58. Challenges are single-use, time-boxed, and rejected if replayed.

After every fill, the canonical trade is hashed with SHA-256. The digest is written to the Memo program on Solana devnet, and the resulting explorer link is stored as the receipt.

This process is deliberately fail-open. A Solana outage produces a receipt-pending status and never reverses or delays a paper balance update.

Frontend

The frontend is a React trading terminal with:

  • A candlestick price chart with technical overlays
  • A live options chain
  • A portfolio view
  • An Oracle panel that displays the debate and plays the audio squawk

Supabase Realtime, using Postgres Changes, pushes index and portfolio updates to every open session without polling.

Challenges we ran into

Sponsor terms as an architectural constraint

The Oracle can reason about a market without seeing raw listings. Its context builder reads only the same derived surfaces exposed by the public /chain endpoint.

That boundary is enforced by the module structure. The raw Stay22 response literally never leaves lib/ingest.ts.

Concurrency correctness

Quotes, trades, and settlement all touch the same rows. Getting row locking and quote-staleness tokens right, so two traders cannot fill against a price that has already moved, took several rewrites of the Postgres functions.

Keeping on-chain writes separate from the ledger

Receipt anchoring had to remain strictly one-directional. The chain write happens after the atomic paper trade commits. If Solana is unavailable, the trade remains valid and the receipt moves to a pending state.

Accomplishments

  • Repurposed a travel-booking API into a working options exchange with a live index, real Greeks, and atomic settlement.

  • Built an agentic Gemini analyst that researches the market, debates both sides, commits to a specific tradeable strike, and reads the recommendation aloud.

  • Built money movement around atomic transactions and idempotent fills, while keeping mark-to-market valuation separate from liquidation valuation.

  • Created provable trade history on Solana without allowing chain availability to affect the paper ledger.

What we learned

Financial correctness is primarily a database problem. Black-Scholes was the easy part. Preventing balances from drifting under concurrent activity was much harder.

Reliable LLM agents need clear boundaries. Separating grounded tool use from structured output, and limiting the model to a closed set of derived tools, made the Oracle more predictable.

A strict rule against fabricating data improves the architecture around the model. The system has to expose only the information the agent is allowed to use.

On-chain writes work best as additive, fail-open operations. That gives bloom22 verifiable provenance without making the paper ledger depend on Solana's availability.

What's next for bloom22

  • Support multiple simultaneous city markets, each with event-driven volatility, spreads, and multiple expiries.

  • Evolve the Oracle from an advisor into an autonomous paper market maker that continuously quotes and hedges its own book.

  • Move receipts from devnet memos to a settlement program with an on-chain, verifiable leaderboard.

  • Connect option exercise to an actual reservation. Since Stay22 pays real commission on completed bookings, a winning call could eventually settle into a real hotel stay.

Built With

Share this project:

Updates