This document explains how the factory, raffle instances, oracle, and clients interact.
graph TB
UI[Frontend / DApp]
Factory[RaffleFactory Contract]
Instance[RaffleInstance Contract]
Oracle[Oracle Service]
Stellar[Stellar Network]
IPFS[IPFS / Metadata]
UI -->|create_raffle| Factory
Factory -->|deploys| Instance
UI -->|buy_tickets| Instance
UI -->|finalize_raffle| Instance
Instance -->|RandomnessRequested event| Stellar
Oracle -->|polls events| Stellar
Oracle -->|provide_randomness| Instance
Instance -->|RaffleFinalized event| Stellar
UI -->|claim_prize| Instance
UI -->|metadata_hash| IPFS
- A creator calls
create_raffleon the factory withRaffleConfig. - The factory deploys a new raffle instance and returns the new instance address.
- Users buy tickets directly on the raffle instance contract.
- When finalization starts, the instance emits randomness request events to the network.
- The oracle service polls those events and calls
provide_randomnessback on the instance. - The instance finalizes winners, emits finalization events, and winners claim prizes.
stateDiagram-v2
[*] --> PendingPrize: create_raffle
PendingPrize --> Active: deposit_prize
Active --> Drawing: finalize_raffle / tickets_full
Active --> Cancelled: cancel_raffle
Active --> Failed: finalize_raffle (min_tickets not met)
Drawing --> Finalized: provide_randomness / finalize (internal)
Drawing --> Cancelled: cancel_raffle / fallback(refund)
Finalized --> Claimed: all winners claim
Drawing --> Cancelled: emergency_withdraw (after timeout)
PendingPrize: created but not funded yet.Active: funded and selling tickets.Drawing: draw execution in progress.Finalized: winners are locked and can claim.Claimed: terminal state when all claims are complete.Cancelled/Failed: terminal non-success states.
The instance has four intended token-moving paths:
claim_prizepays each unclaimed winner and records protocol fees.sweep_unclaimedpays unclaimed prizes to the treasury after the claim expiry period and marks those prizes claimed.refund_prizereturns the deposited prize afterCancelledorFailed.refund_ticketreturns each ticket payment afterCancelledorFailed.withdraw_feespays only recorded accumulated fees after finalization.
Administrative escape paths are constrained by the same invariant:
emergency_withdrawis only available for a timed-outDrawingraffle. Its delay starts atend_time, or at the randomness request ledger for a no-deadline raffle. It transfers only the deposited prize token and leaves all remaining obligations covered.rescue_tokenscan transfer unrelated-token surplus, but for either configured raffle token it must leave unpaid ticket refunds, accumulated fees, and outstanding prize claims fully covered.sweep_dustis available only after settlement and transfers payment-token surplus above all remaining entitlements; accumulated fees are preserved.
No token-moving path may reduce a token balance below its outstanding
entitlement. emergency_withdraw cannot operate on Finalized, because
unclaimed winners remain entitled to their prizes.
The following table summarizes the behavior of mutating contract entrypoints across all 7 RaffleStatus states (#623):
| Mutating Entrypoint | PendingPrize | Active | Drawing | Finalized | Cancelled | Failed | Claimed |
|---|---|---|---|---|---|---|---|
deposit_prize |
Allowed (-> Active) | Rejected (PrizeAlreadyDeposited) |
Rejected (PrizeAlreadyDeposited) |
Rejected (PrizeAlreadyDeposited) |
Rejected (PrizeAlreadyDeposited) |
Rejected (PrizeAlreadyDeposited) |
Rejected (PrizeAlreadyDeposited) |
buy_tickets |
Rejected (RaffleInactive) |
Allowed (-> Active / Drawing) | Rejected (DrawingAlreadyInProgress / RaffleInactive) |
Rejected (RaffleInactive) |
Rejected (RaffleInactive) |
Rejected (RaffleInactive) |
Rejected (RaffleInactive) |
finalize_raffle |
Rejected (InvalidStateTransition) |
Allowed (if ended/full) | Allowed (if Drawing) | Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
provide_randomness |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Allowed (-> Finalized) | Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
claim_prize |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Allowed (-> Finalized / Claimed) | Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
cancel_raffle |
Allowed (-> Cancelled) | Allowed (-> Cancelled) | Allowed (-> Cancelled) | Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Allowed (-> Cancelled) | Rejected (InvalidStatus) |
refund_ticket |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Rejected (InvalidStatus) |
Allowed | Allowed | Rejected (InvalidStatus) |