luckypool
v1.0
OverviewQuickstartSDK ReferenceDraw TypesVRF & FairnessOn-chain RecordsContractsError CodesEventsFAQ
DrawEngine SDK · v1.0

Build with
DrawEngine.

Add provably fair, on-chain draws to your protocol in minutes. No VRF infrastructure, no ticketing logic — just one SDK and a Stellar wallet.

Provably fair VRFAny ticket weightOn-chain recordsStellar native
What is DrawEngine

Overview

DrawEngine is the draw primitive powering LuckyPool's weekly jackpot. It handles VRF randomness, ticket weighting, winner selection, and on-chain recording — so your team doesn't have to build any of that from scratch.

Provably fair

VRF-backed randomness, verifiable by anyone on Stellar.

Any weight model

Equal odds, proportional tickets, tiered — configure it your way.

Instant recording

Winner and proof written on-chain the moment the draw fires.

Architecture
Your AppSDK call
DrawEngineSoroban contract
VRF OracleRandomness + proof
Stellar LedgerPermanent record
Get started in 5 minutes

Quickstart

NoteMake sure you have Node.js 18+ and a Freighter wallet installed before following this guide.
01
Install
bash
npm install @luckypool/draw-engine
02
Initialise a pool
ts
import { DrawEngine } from "@luckypool/draw-engine";

const engine = new DrawEngine({
  network: "mainnet",          // "mainnet" | "testnet"
  contractId: "YOUR_CONTRACT", // your deployed DrawEngine contract
  signer: freighterSigner,     // any Stellar signer
});
03
Add participants
ts
await engine.addEntrants([
  { address: "GABC...1234", tickets: 100 },
  { address: "GXYZ...5678", tickets: 250 },
]);
04
Trigger the draw
ts
const result = await engine.draw();

console.log(result.winner);   // Stellar address of winner
console.log(result.vrfProof); // on-chain verifiable proof
console.log(result.txHash);   // Stellar transaction hash
Methods

SDK Reference

new DrawEngine(config)DrawEngine

Creates a new DrawEngine instance connected to your contract.

Parameters
network"mainnet" | "testnet"Stellar network to connect to.
contractIdstringAddress of your deployed DrawEngine contract.
signerStellarSignerWallet signer for submitting transactions.
engine.addEntrants(entrants[])Promise<void>

Register participants for the next draw. Overwrites existing entries for the same address.

Parameters
addressstringStellar address of the participant.
ticketsnumberWeight / ticket count for this entrant. Must be > 0.
engine.draw()Promise<DrawResult>

Requests VRF randomness, selects a winner proportionally, and records the result on-chain.

engine.getResult(txHash)Promise<DrawResult>

Fetches a past draw result by Stellar transaction hash.

Parameters
txHashstringStellar transaction hash from a previous draw.
engine.getEntrants()Promise<Entrant[]>

Returns the current list of registered entrants and their ticket counts.

engine.clearEntrants()Promise<void>

Removes all registered entrants. Typically called after a draw to reset for the next round.

Weighting models

Draw Types

DrawEngine supports any weighting model. Pass ticket counts to control odds — or give everyone equal weight for a pure raffle.

Equal odds
ts
// Every entrant gets 1 ticket
{ address: "G...", tickets: 1 }
Proportional (deposit-based)
ts
// Tickets = USDC deposited
{ address: "G...", tickets: 500 }
Tiered
ts
// Custom tier weights
{ address: "G...", tickets: 3 } // Gold
{ address: "G...", tickets: 1 } // Bronze
NFT raffle
ts
// 1 ticket per NFT held
{ address: "G...", tickets: nftCount }
TipLuckyPool itself uses proportional weighting — 1 USDC deposited = 1 ticket. This gives larger depositors better odds while keeping the system transparent and fair.
How randomness works

VRF & Fairness

Every draw uses a Verifiable Random Function (VRF) seeded from the Stellar ledger hash. The VRF proof is published on-chain alongside the draw result — anyone can independently verify the winner selection was fair and not manipulated.

DrawEngine uses Stellar's native randomness beacon. No external oracle dependency, no off-chain trust assumption, no admin key that could influence the outcome.

Seed source

Stellar ledger hash — deterministic and tamper-proof.

Proof on-chain

VRF proof stored with every draw. Verify anytime.

No admin key

No privileged actor can influence the draw outcome.

Verification example
ts
// Independent verification — no trust required
const proof = await engine.getResult(txHash);

// 1. Check VRF output is authentic
const valid = await oracle.verify(proof.vrfProof, proof.roundSeed, proof.vrfOutput);

// 2. Check winner arithmetic
const winningTicket = BigInt(proof.vrfOutput.slice(0, 8)) % proof.totalTickets;
assert(winningTicket === proof.winningTicket);

// 3. Check ticket maps to winner address
assert(proof.ticketOwner[winningTicket] === proof.winner);
Querying past draws

On-chain Records

Each draw writes a permanent record to the DrawEngine contract on Stellar containing the winner address, VRF proof, timestamp, and total entrant count.

ts
const past = await engine.getResult("TX_HASH_HERE");

// past.winner         — winning Stellar address
// past.vrfProof       — verifiable proof bytes
// past.vrfOutput      — 32-byte random seed used
// past.winningTicket  — which ticket number won
// past.totalTickets   — total tickets in draw
// past.timestamp      — Unix timestamp of draw
// past.entrants       — total number of entrants
// past.prize          — USDC amount paid to winner (if LuckyPool draw)
Deployed addresses

Contracts

WarningMainnet contracts are not yet deployed. The testnet addresses below are live for integration testing.
Testnet
DrawEngine
CDRAW...TESTNETCore draw primitive
LuckyPool
CLUCKY...TESTNETPrize-linked savings
Mainnet — coming Q3 2026
Pending security audit · Expected August 2026
Contract errors

Error Codes

DrawEngine and LuckyPool contracts return typed errors. Catch them via the SDK's DrawEngineError class.

1AlreadyInitializedContract has already been initialized.
2NotInitializedContract must be initialized before use.
3PausedThe pool is paused. Deposits and draws are blocked.
4UnauthorizedCaller does not have permission for this action.
5InsufficientBalanceWithdrawal amount exceeds deposited balance.
6InsufficientPrizePrize pool is empty — fund it before running a draw.
7InvalidAmountAmount must be greater than zero.
8NoDepositorsDraw cannot run with zero depositors.
9FeeTooHighProtocol fee exceeds the maximum allowed (50%).
ts
import { DrawEngineError } from "@luckypool/draw-engine";

try {
  await engine.draw();
} catch (err) {
  if (err instanceof DrawEngineError) {
    switch (err.code) {
      case 6: console.error("Prize pool is empty — fund it first"); break;
      case 8: console.error("No depositors registered for this draw"); break;
      default: console.error("Contract error:", err.code, err.name);
    }
  }
}
On-chain event log

Events

All state-changing actions emit typed events on Stellar. Subscribe to them via the SDK or query Stellar Horizon directly.

EventFields
Deposited
On every deposit
user: Address, amount: i128, round: u64
Withdrawn
On every withdrawal
user: Address, amount: i128
PrizeFunded
When admin seeds prize pool
from: Address, amount: i128
DrawCompleted
At end of each draw
round: u64, winner: Address, prize: i128
ts
// Subscribe to draw completions
engine.on("DrawCompleted", (event) => {
  console.log(`Round ${event.round}: ${event.winner} won ${event.prize} USDC`);
});

// Or query past events from Horizon
const events = await engine.getEvents({
  type: "DrawCompleted",
  fromLedger: 50000000,
});
Common questions

FAQ

Is the SDK open source?+
Yes. The DrawEngine contract and SDK are open source under the MIT license. The LuckyPool consumer product is built on top of the same contract.
Do I need to deploy my own contract?+
For production use, yes. You deploy a DrawEngine contract to Stellar and pass its address to the SDK. This gives you full ownership of your draw logic and entrant data.
How long does a draw take?+
Typically 1–2 Stellar ledger confirmations (~5–10 seconds). The VRF oracle responds within one ledger and the draw result is written in the same transaction.
What happens if the VRF oracle is down?+
The draw request sits pending until the oracle responds. DrawEngine has a timeout mechanism — if no VRF response arrives within 100 ledgers (~10 minutes), the round can be cancelled and re-requested.
Can I run draws with non-USDC tokens?+
DrawEngine itself is token-agnostic — it only handles winner selection. The LuckyPool consumer product is USDC-only, but the SDK works with any Stellar asset for your own draw.
Is there a fee for using DrawEngine?+
The SDK is free for open use. Production commercial integrations pay a per-draw fee (typically $50–$500 depending on pool size) or a monthly subscription. Contact us to discuss your use case.