# LuckDrop > A free recurring prize draw with tradable tickets, on Base Sepolia. Entry costs no > ticket price; the entrant sends their own transaction and pays its gas. Five rounds: > four land on the word LUCK or DROP, the fifth on a number. Match your ticket's slots to > stay in. Tickets are NFTs and can be traded while the draw is running. LuckDrop is currently running on Base Sepolia, a TEST NETWORK. LUCK earned or won here is for testing and has no monetary value. The contracts do not exist on Base mainnet -- sending a transaction to these addresses on chain id 8453 would reach an address with no code and burn gas for nothing. ## Automated participation Automated entry is EXPLICITLY ALLOWED. From the Terms (section 4): "Automated entry is allowed. You may enter using a script, a bot, or an AI agent, and you pay the gas for your own entry like everyone else." The unit of the rule is the PERSON -- one entry per person per draw -- and the smart contract enforces it per WALLET, rejecting a second claim from the same wallet. Using multiple wallets to take extra entries in one draw is what gets you disqualified. There is no CAPTCHA and no bot gate. Marketplace trading is likewise open: the contract's buy function has no caller gate. ## How to enter Send `claimTicketSelf()` (selector `0x03e9f73e`, no arguments) from the wallet that should receive the ticket, to the Draw contract below, on chain id 84532. The contract mints to msg.sender, so the sending wallet is the parameter. CHECK BEFORE YOU SEND. GET https://luckdrop.app/api/agent/preflight?address=0xYourWallet returns, read from the chain rather than from our database, whether that wallet can enter right now and why not if it cannot. Its `reason` values mirror the contract's own revert strings: OK, ALREADY_CLAIMED, NOT_IN_CLAIM_PHASE, DRAW_FULL, PAUSED. Sending without checking risks a revert that costs you gas and gains you nothing. YOU NEED A WALLET AND GAS BEFORE YOU CAN ENTER. Any Ethereum account works; for an agent a freshly generated private key is enough (viem: `generatePrivateKey()` then `privateKeyToAccount(key)`). Keep the key secret -- it controls the wallet and anything it wins. You also need an RPC to estimate gas; a public endpoint for this chain is https://sepolia.base.org. Gas is paid in test ETH, which is free from a Base Sepolia faucet, cannot be bought, and has no monetary value. The full walkthrough is in the interface contract below. ## When things happen Every draw runs the same timetable, set per deployment. On this one: - **Entries** are open for 25200 s (7 hours), counted from the draw's `startedAt`. - **The reveal** follows: entries close and every ticket's five slots are filled in on-chain by a Pyth Entropy callback. It usually takes a few minutes. - **Round 1** runs one full round interval AFTER the reveal -- so roughly 37800 s (10½ hours) after `startedAt`, not straight after entries close. Rounds 2 to 5 follow 12600 s (3½ hours) apart. - **The whole draw** takes about 88200 s (24½ hours). The next draw opens on the automatic starter's next tick after one completes -- but ONLY while `autoCycleEnabled` is true and `autoCycleHalted` is false on `/api/draw/current`. Otherwise no next draw is scheduled, and none should be assumed. To see where the current draw is, read `/api/draw/current`: `week.status` is the phase, `week.startedAt` is when it opened, and `week.countdown` is the seconds until the NEXT EVENT FOR THAT PHASE -- entries closing (`claiming`), round 1 (`revealing`) or the next round (`drawing`). It is 0 once the draw is complete. **Polling.** Nothing here changes faster than once a round, except the market. Once a minute is plenty for `/api/draw/current`, `/api/draw/history` and `/api/draw/{week}/results`; poll `/api/market/listings` no faster than every few seconds while you are actively trading. Where these reads are rate limited, the limit is a shared, site-wide ceiling rather than a per-caller quota. A 429 carries `Retry-After` in seconds -- wait that long before trying again. ## Contracts (chain id 84532, Base Sepolia) - Draw `0xa66369D56ce3973b8Fbb0b9F2Ba14AF2d37EB4B3`: entry (`claimTicketSelf`), week state, entrant cap - Ticket `0x90276E892974384A5dDceA1CeA907FcA027a02B0`: the ticket NFT; `hasClaimed(weekId, wallet)` and `getWeekTicketCount(weekId)` - LUCK prize `0x2963Df33C54530d4F2c1F454Bd591296F59C91B2`: `weekWinner(weekId)`, `unclaimedPrize(weekId)` and `claimPrize(weekId)` for LUCK prizes - USDC prize `0xdF53CAA3054c76bE2935A49CCF80eFba8bb1F0BD`: the same three functions for USDC prizes - Market `0x6a0fC0A795C5Fb658e6D6DB08e7600e114d06D40`: list and buy live tickets while a draw is running. A completed sale pays a fee read from `feeBasisPoints()` on that contract, part of it into the draw's prize pool -- that share is `prizeShareBasisPoints()` on the same contract. Read BOTH values; do not assume a rate or a split, they are setters. - LUCK token `0x877ebed524E28a984776c0Ceed25694c27903F57`: the ERC-20 used for marketplace settlement and prizes ## How the winner is decided Each of the first four rounds draws either LUCK or DROP; the fifth draws a number from 1 to 9,999. A ticket is out as soon as a round fails to match its slot. The ticket that survives the most rounds wins. If several go out in the same round, the winner is the one whose five slots are closest overall to those drawn, measured as the total difference across all five positions -- in practice the final number decides it. If two are exactly equal on both measures, the earlier entry wins. Matching all five is 1 in 159,984, so nearly every draw is decided by survival rather than by an outright match. ## Collecting a prize Prizes are PULL payments: winning records the prize, and the winner must call `claimPrize(weekId)` themselves. You have 180 days from the award (`PRIZE_CLAIM_WINDOW` on the prize contract), after which an admin MAY return an unclaimed prize to a future draw's pool -- so collect promptly. `weekId` is the CONTRACT week id, not the display week number -- the two are different numbers. Every draw endpoint below returns both as `contractWeekId` and `weekNumber`; use `contractWeekId`. CHECK `weekWinner(weekId)` EQUALS YOUR WALLET BEFORE YOU TRUST A BALANCE. Contract week ids reset when the contracts are redeployed, so they are not unique: a week number from an older deployment can carry a real `unclaimedPrize` belonging to the CURRENT deployment's week of the same number, and claiming it reverts with "Not winner" and costs you the gas. That is the same check `claimPrize` itself enforces. ## Getting LUCK (the welcome grant) Entry is free, but the marketplace settles in LUCK, and LUCK cannot be bought permissionlessly -- there is no faucet and no DEX pool. The way to obtain it is the one-time welcome grant of 500 LUCK, and it is open to agents on exactly the same terms as people. There is no CAPTCHA on it: the human-test that used to guard this endpoint was removed on 2026-08-29 precisely because it contradicted the automated-entry policy above. You must ALREADY HAVE CLAIMED A TICKET on-chain to be eligible -- the grant follows entry, it does not fund it. Enter first, then claim the grant. The endpoint is session-authenticated, and the session is obtained by signing in with your wallet (Sign-In With Ethereum, ERC-4361). Four HTTP calls, no API key, no account: 1. POST https://luckdrop.app/api/auth/siwe/nonce Body: `{"walletAddress": "0xYourWallet", "chainId": 84532}` Returns: `{"nonce": "..."}` PASS chainId EXPLICITLY. It defaults to 1 (Ethereum mainnet) when omitted, and the nonce is stored against the address AND the chain id, so a nonce taken with the default can never verify against 84532 -- the step 4 call fails with a mismatch that reads like a bad signature. 2. Build an ERC-4361 message with domain `luckdrop.app`, that same address, that nonce, and chainId 84532. Domain, address, nonce and chain id are all compared server-side; any one of them differing is rejected. 3. Sign the message with the wallet. EOA signatures, ERC-1271 contract signatures (deployed smart accounts, e.g. Safe), ERC-6492 wrapped signatures (an undeployed counterfactual account, e.g. a fresh Coinbase Smart Wallet) and ERC-8010 pre-delegated signatures (an EIP-7702 delegated EOA) are ALL accepted. 4. POST https://luckdrop.app/api/auth/siwe/verify Body: `{"message": "...", "signature": "0x...", "walletAddress": "0xYourWallet", "chainId": 84532}` Returns 200 with `{"success": true, ...}` and a `Set-Cookie` session cookie. KEEP THE COOKIE and send it on subsequent requests -- a cookie jar is the session. The `token` in the body is not a bearer credential; there is no bearer auth here. Then, with that cookie: - GET https://luckdrop.app/api/rewards/eligibility -- is a grant coming, and if not, why not. Read-only; it cannot mint or consume anything. It reads our database, which picks up a new entry within about a minute -- if you entered moments ago and see `no_ticket_yet`, POST https://luckdrop.app/api/tickets/sync with the same cookie and check again. - POST https://luckdrop.app/api/rewards/claim -- claims it. Idempotent, so retrying is safe. Returns 200 with a `status` of `granted` (plus `amount` and `txHash`), `already_granted`, `in_progress`, `no_ticket_yet` (you have not entered a draw yet -- enter, then come back), `cap_reached` or `budget_reached`. A 429 with `rate_limited` means the programme's DAILY ceiling is exhausted, not yours: back off and retry tomorrow, not in a minute. The two sign-in endpoints are rate-limited to 5 requests per minute per IP. Sign in once and keep the session; it lasts a week. ## API - [Agent registration](https://luckdrop.app/.well-known/agent-registration.json): our ERC-8004 registration file — what we are, where to reach us, and our on-chain registry record once one exists. - [MCP connector](https://luckdrop.app/mcp): a Model Context Protocol server. Add this URL as a connector and you get the draw, the entry preflight, results, market listings and claimable prizes as named tools, instead of driving the endpoints below by hand. The prize check is the one tool with no HTTP equivalent; everything else maps to an endpoint below. No authentication. - [Agent Plugins package](https://luckdrop.app/plugin/plugin.json): installs the MCP connector above and a skill describing how to use it, in one step. Agent Plugins 1.0.0; the server config is at `/plugin/mcp.json` and the skill at `/plugin/skills/luckdrop/SKILL.md`. - [Interface contract](https://luckdrop.app/agent-guide.md): the whole agent-facing contract in one Markdown page — the entry call, its four revert strings, the events to watch, and the one-entry-per-person rule. Start here if you are entering rather than reading. - [OpenAPI 3.1](https://luckdrop.app/openapi.json): the endpoints below as a machine-readable schema, with the response shapes spelled out. - [Current draw](https://luckdrop.app/api/draw/current): live draw state, prize, entrant count, countdown. Publishes both `weekNumber` and `contractWeekId`. The `week` block is sourced from our database; the `chain` block beside it is read from the contracts, and `chain.inSync` says whether the two agree about which week this is and how far along it is. Trust `chain` when they disagree, and note `chain` is null (not false) when the chain could not be read. Do NOT compare `chain.state` to `week.status` yourself: the two advance at different moments and both are correct — the contract stays REVEALING until round 1 is requested, hours after the database has moved to `drawing` — so a string comparison reports a healthy draw as broken. Use `inSync`. - [Entry preflight](https://luckdrop.app/api/agent/preflight?address=0x0000000000000000000000000000000000000000): can this wallet enter, and the exact transaction to send. Sourced from the CHAIN. Requires an `address` query parameter. - [Draw history](https://luckdrop.app/api/draw/history): the last 10 completed draws, with both ids. - [Draw results](https://luckdrop.app/api/draw/70/results): one draw's rounds and winner. The path segment is the DISPLAY week number by default. If you are following `contractWeekId`, add `?id=contract` -- the same number means different draws in the two id spaces, and without it you get a valid-looking 200 describing the wrong draw. The response echoes `idSpace` and both ids. Each round also carries `vrf_request_id` (the Pyth Entropy sequence number behind that round's randomness, as a string -- it is a uint64) and `tx_hash` with `tx_url` (the transaction that settled the drawn number), so a round can be checked against the chain rather than taken on trust. Null means we did not record it, not that it did not happen: rounds drawn on an earlier contract deployment are not in this one's logs. - [Market listings](https://luckdrop.app/api/market/listings): live listings, and whether the trading window is open. - [Ticket](https://luckdrop.app/api/ticket/1): one ticket's ERC-721 metadata by token id -- its status and, once revealed, its five round slots. The Ticket contract's `getTicketNumbers(tokenId)` and `isAlive(tokenId)` carry the same facts. No authentication is needed for any of the above. Endpoints under `/api/admin` and `/api/cron` are authenticated and are not for public use. ## Pages - [How it works](https://luckdrop.app/how-it-works): the rules, in plain English - [Terms](https://luckdrop.app/terms): the full rules, including the automated-entry clause - [Privacy](https://luckdrop.app/privacy): what data is collected - [LUCK token](https://luckdrop.app/luck-token): the token used for prizes and marketplace settlement - [Sponsors](https://luckdrop.app/sponsors): sponsoring a draw ## Optional - The home and draw pages server-render the live draw, so their HTML is readable without executing JavaScript. The API endpoints above are still the better source: they are structured, and they carry the chain state. - The market, profile and login pages render client-side and their HTML carries navigation only. Fetch the API endpoints rather than scraping them. - Randomness comes from Pyth Entropy and is recorded on-chain; every result is independently verifiable.