Architecture

Partner platform model

Luckotto is one self-contained app: business pages, partner APIs, an embeddable player UI, and background scanner jobs.

Entity model

EntityUserA website account that owns partner sites and manages API keys.
EntityPartner siteA B2B tenant publicly addressed by its current payout address, with display metadata, verification status, and one active server-side API key.
EntityAPI keyA single static partner secret for trusted server-side calls to /api/luckotto/tickets/authenticated. It is never placed in iframe URLs or browser code.
EntityplayerUnameA public display name shown in public ledgers when supplied by a partner.
EntityplayerIdentifierA per-player secret the partner generates unpredictably (for example, an HMAC of their user ID with a partner secret). Players may see their own; Luckotto never reveals one player's identifier to another, and it never appears in public ledgers.
EntityLuckotto ticketA reserved ticket record with player-selected tiles and credited-payment draw weight.
EntityDeposit addressA UUIDv7 record with a hash-tweaked Bitcoin address scoped to one ticket request.
EntityPaymentAn observed Bitcoin output to a deposit address.
EntityTicketA round-local UUIDv7 ticket with the selected six-tile set and credited payment weight.
EntityRoundThe global Luckotto draw window shared by all partners.

Lifecycle

  1. A website user creates a partner site and receives the initial API key.
  2. The user copies an /embed URL with partner payout address, playerUname, and playerIdentifier.
  3. The embed queries /api/luckotto routes in the same monolith. Browser code never touches Postgres directly.
  4. The API routes return one min-round-scoped deposit address from partner payout address and player context. Trusted server-side clients use /api/luckotto/tickets/authenticated with the partner API key.
  5. The player sends Bitcoin to the player/partner/minimum-round deposit address.
  6. The root worker loop or scanner commands record payments, credit Luckotto tickets, and settle rounds.
  7. Public round and Luckotto ticket pages remain visible on the website.
  8. A ticket record is public: anyone with its round number and ticket UUID can view its tiles, draw weight, and status.

Product rules

Single deploymentThe website, /api/luckotto routes, /embed player UI, and worker loop are built from one root package.
Database accessServer-side pages, route handlers, and jobs use shared root utilities to talk directly to Postgres.
API surfaceThe monolith exposes a small JSON REST surface under /api/luckotto.
Embed isolationThe embed communicates only with public /api/luckotto routes and can be embedded by partners.
No iframe secretsEvery API endpoint used by the iframe is public and must work without a partner API key.
Player contextPartners pass playerUname and playerIdentifier to create deposit addresses and look up player Luckotto tickets.
REST APIPartner-facing API operations are plain JSON REST endpoints.

Iframe contract

Player help
<iframe
  title="Partner Lotto"
  src="https://luckotto.example/embed?partner=tb1pyqn8k6d9lyjen5akpz7v42y37vc42cp6tzepvvvr8h63ulecu27suulrjw&playerUname=DisplayName&playerIdentifier=UNGUESSABLE-PER-PLAYER-SECRET"
  style="width:100%;max-width:560px;height:760px;border:0;"
  loading="lazy"
></iframe>

The iframe URL carries public player context only. It must never include an API key or server-side partner settings.

The embedded navigation exposes play, rounds, verification, how-to-play, and FAQ pages with the same public context preserved in the URL.