How Duty3 Works
A Duty3 raffle follows a deterministic lifecycle: creation → ticket sale → VRF draw → payout. Every step is enforced by immutable smart contracts on Arbitrum.
The Raffle Lifecycle
1. Creation
A creator calls createRaffle() on the Factory contract, providing:
- Prize token — WETH, WBTC, or USDC
- Prize amount — locked immediately into the vault
- Ticket price — fixed price in the payment token
- Max tickets — the total supply of tickets
- Duration — deadline in seconds from now
- Winner count — 1 for single winner, N for multi-winner split
- Creator tag — optional label (e.g.
"AI"for agent-created raffles)
The Factory deploys a new Vault contract and transfers the prize from the creator's wallet into it. The prize cannot be withdrawn by anyone until VRF selects a winner.
2. Ticket Sale (OPEN state)
Once deployed, the vault enters OPEN state. Participants call buyTickets(quantity) on the vault after approving the payment token. Each ticket receives a sequential ID starting from 0. The vault tracks:
- Total tickets sold vs. max tickets
- Each participant's ticket IDs
- Total spent per address
Ticket prices are determined by getPrice(userAddress). Users holding ≥ 1,000 $DUTY3 automatically receive a 10% discount — no coupon codes, no manual claims.
3. Draw Trigger
A draw happens only when every ticket is sold. The purchase of the last ticket triggers it automatically.
If the deadline passes before a sell-out, there is no draw. Ticket sales stop, the creator calls enableRefund(), the prize returns to the creator, and every participant can claim back 100% of what they spent.
Protocol raffles (created by Duty3's own agents) have one addition: if one is at least 50% sold shortly before its deadline, the protocol booster buys the remaining tickets with protocol funds, so the draw happens instead of a refund. The boost is visible on the raffle page. Raffles created by users are never boosted.
On a sell-out, the vault transitions to CALCULATING state and sends a VRF request to Chainlink. The request is fulfilled by Chainlink's oracle network — typically within 1-3 blocks on Arbitrum (~0.5-1 second).
4. Winner Selection (Chainlink VRF)
Chainlink VRF delivers a cryptographically random number to the vault contract. The contract uses this number to select the winning ticket ID:
winningTicketId = vrfRandomWord % totalTicketsSoldFor multi-winner raffles, multiple unique IDs are derived from the same VRF word. The winning address is the holder of the winning ticket ID.
5. Payout (COMPLETED state)
Immediately upon VRF fulfillment, the vault transfers the prize to the winner(s) and emits a WinnerPicked event. A platform fee (in basis points, set by the Factory) is deducted from the ticket revenue and sent to the protocol treasury. The creator receives the remaining ticket revenue.
Vault States
| State | Value | Description |
|---|---|---|
| OPEN | 0 | Accepting ticket purchases |
| CALCULATING | 1 | VRF request sent, awaiting fulfillment |
| COMPLETED | 2 | Winner paid, raffle closed |
| REFUND | 3 | Raffle cancelled — tickets can be refunded |
Fee Structure
Duty3 charges a platform fee expressed in basis points (bps). The fee is deducted from ticket revenue collected by the vault at the time of payout. Prize funds are never touched — the winner always receives 100% of the locked prize.
- Platform fee: read from
Factory.platformFeeBps() - Applied to: ticket sale revenue (not the prize)
- Paid to: protocol treasury address
Refund Mechanism
If a raffle fails to complete (e.g. VRF never fulfills, or an admin emergency abort is triggered), the vault can enter REFUND state. Participants call claimRefund() to recover their ticket payments. The creator's prize is returned to them as well.