Skip to content

Commit 68c889a

Browse files
Add Pyth Entropy lottery example
- Implement lottery smart contract using IEntropyV2 - Each ticket purchase draws a unique random number from Entropy - Winner determined by closest random number to target - Complete Next.js frontend with Web3 wallet integration - Support for multiple testnets (Blast, Optimism, Arbitrum Sepolia) - Comprehensive README with setup instructions and technical details Co-Authored-By: Jayant <[email protected]>
1 parent 4490fc7 commit 68c889a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+24904
-0
lines changed

entropy/lottery/README.md

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
# Lottery with Pyth Entropy
2+
3+
This example demonstrates how to build a provably fair lottery application using Pyth Entropy for verifiable randomness. The application showcases a complete implementation where each lottery ticket draws its own random number from Entropy, and a winner is selected based on which ticket's random number is closest to a final target number.
4+
5+
## What This Example Does
6+
7+
The Lottery example implements a complete lottery system with three distinct phases:
8+
9+
### Phase 1: Ticket Sales (ACTIVE)
10+
Users can purchase lottery tickets during the active phase. Each ticket purchase triggers a request to Pyth Entropy for a unique random number. The process works as follows:
11+
- User pays the ticket price plus the Entropy fee
12+
- Contract requests a random number from Pyth Entropy using `IEntropyV2.requestV2()`
13+
- Each ticket is assigned a sequence number that uniquely identifies its random number request
14+
- The ticket price is added to the prize pool
15+
16+
### Phase 2: Drawing (DRAWING)
17+
When the lottery owner decides to end the lottery:
18+
- The owner calls `endLottery()` which triggers one final random number request
19+
- This final number serves as the "target" for determining the winner
20+
- The contract status changes to DRAWING while waiting for the final random number
21+
22+
### Phase 3: Winner Selection (ENDED)
23+
Once the final target random number is revealed through Entropy's callback:
24+
- The contract automatically calculates which ticket's random number is closest to the target
25+
- The winner is determined based on the absolute difference: `|ticket_random - target_random|`
26+
- The winner can then claim the entire prize pool
27+
28+
### Key Features
29+
30+
**True Randomness Per Ticket**: Unlike simple lottery systems that draw one random number at the end, this implementation ensures each ticket gets its own verifiable random number from Pyth Entropy. This provides transparency and fairness, as all random numbers are generated independently and can be verified on-chain.
31+
32+
**Entropy V2 Integration**: The contract uses the latest `IEntropyV2` interface from Pyth, utilizing the simplified `requestV2()` method that leverages in-contract PRNG for user contribution.
33+
34+
**Automatic Winner Selection**: The winner is automatically determined when the final random number callback is received, eliminating the need for additional transactions.
35+
36+
**Complete Frontend**: A Next.js application with Web3 wallet integration allows users to buy tickets, view their tickets with real-time random number updates, and claim prizes.
37+
38+
## Project Structure
39+
40+
```
41+
entropy/lottery/
42+
├── contract/ # Smart contracts built with Hardhat
43+
│ ├── contracts/
44+
│ │ └── Lottery.sol # Main lottery contract with Entropy integration
45+
│ ├── ignition/
46+
│ │ └── modules/
47+
│ │ └── Lottery.ts # Deployment configuration
48+
│ ├── package.json
49+
│ └── hardhat.config.ts
50+
51+
├── app/ # Next.js frontend application
52+
│ ├── app/
53+
│ │ ├── (home)/
54+
│ │ │ ├── page.tsx # Main lottery interface
55+
│ │ │ └── components/ # Lottery-specific components
56+
│ │ │ ├── buy-ticket.tsx # Ticket purchase component
57+
│ │ │ ├── my-tickets.tsx # User tickets display
58+
│ │ │ └── lottery-status.tsx # Lottery status and winner info
59+
│ │ ├── layout.tsx # App layout with providers
60+
│ │ └── globals.css # Global styles
61+
│ ├── components/ # Reusable UI components
62+
│ ├── contracts/ # Generated contract ABIs and types
63+
│ ├── providers/ # Wagmi and React Query providers
64+
│ ├── config.ts # Chain configuration
65+
│ └── package.json
66+
67+
└── README.md # This file
68+
```
69+
70+
## Prerequisites
71+
72+
Before running this example, ensure you have:
73+
74+
- **Node.js** (v18 or later)
75+
- **npm** or **bun** package manager
76+
- A Web3 wallet (e.g., MetaMask) with testnet funds
77+
- Access to a testnet where Pyth Entropy is deployed (e.g., Blast Sepolia, Optimism Sepolia, Arbitrum Sepolia)
78+
79+
## Running the Example
80+
81+
### Step 1: Deploy the Smart Contract
82+
83+
Navigate to the contract directory and install dependencies:
84+
85+
```bash
86+
cd contract
87+
npm install
88+
```
89+
90+
Create a `.env` file in the contract directory with your wallet private key:
91+
92+
```env
93+
WALLET_KEY=your_private_key_here
94+
BLAST_SCAN_API_KEY=your_api_key_here # Optional, for verification
95+
```
96+
97+
Deploy the contract to Blast Sepolia testnet:
98+
99+
```bash
100+
npx hardhat ignition deploy ignition/modules/Lottery.ts --network blast-sepolia --verify
101+
```
102+
103+
The deployment module is configured with:
104+
- **Entropy Contract**: `0x98046Bd286715D3B0BC227Dd7a956b83D8978603` (Blast Sepolia)
105+
- **Entropy Provider**: `0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344`
106+
- **Ticket Price**: 0.001 ETH
107+
108+
After deployment, note the deployed contract address. You'll need to update this in the frontend.
109+
110+
### Step 2: Configure the Frontend
111+
112+
Navigate to the app directory and install dependencies:
113+
114+
```bash
115+
cd ../app
116+
npm install
117+
```
118+
119+
Update the contract address in `contracts/addresses.ts` with your deployed contract address:
120+
121+
```typescript
122+
export const lotteryAddress = "YOUR_DEPLOYED_CONTRACT_ADDRESS" as const;
123+
```
124+
125+
Generate the contract types for TypeScript:
126+
127+
```bash
128+
npx wagmi generate
129+
```
130+
131+
### Step 3: Run the Frontend
132+
133+
Start the development server:
134+
135+
```bash
136+
npm run dev
137+
```
138+
139+
The application will be available at http://localhost:3000.
140+
141+
### Step 4: Interact with the Lottery
142+
143+
1. **Connect Wallet**: Click the wallet button to connect your Web3 wallet (make sure you're on the correct network)
144+
145+
2. **Buy Tickets**: In the "Buy Tickets" tab, purchase one or more lottery tickets. Each purchase will:
146+
- Charge you the ticket price (0.001 ETH) plus the Entropy fee
147+
- Request a random number from Pyth Entropy
148+
- Show your ticket with a "Waiting for random number..." status
149+
150+
3. **View Your Tickets**: Switch to the "My Tickets" tab to see all your purchased tickets. Once the Entropy callback completes (usually within a few seconds), you'll see the random number assigned to each ticket.
151+
152+
4. **End the Lottery** (Owner Only): If you're the contract owner, you can end the lottery after tickets have been sold. This triggers the final random number draw to determine the winner.
153+
154+
5. **Claim Prize**: If you're the winner, a "Claim Your Prize!" button will appear in the Lottery Status card. Click it to transfer the entire prize pool to your wallet.
155+
156+
## How It Works: Technical Details
157+
158+
### Smart Contract Architecture
159+
160+
The `Lottery.sol` contract implements the `IEntropyConsumer` interface to receive callbacks from Pyth Entropy:
161+
162+
```solidity
163+
contract Lottery is IEntropyConsumer, Ownable, ReentrancyGuard {
164+
// Lottery states: ACTIVE -> DRAWING -> ENDED
165+
enum LotteryStatus { ACTIVE, DRAWING, ENDED }
166+
167+
// Each ticket stores its buyer, sequence number, and random number
168+
struct Ticket {
169+
address buyer;
170+
uint64 sequenceNumber;
171+
bytes32 randomNumber;
172+
bool fulfilled;
173+
}
174+
175+
// ...
176+
}
177+
```
178+
179+
### Buying a Ticket
180+
181+
When a user buys a ticket, the contract:
182+
183+
1. Validates the lottery is in ACTIVE status
184+
2. Checks sufficient payment (ticket price + entropy fee)
185+
3. Calls `entropy.requestV2()` to request a random number
186+
4. Stores the ticket with its sequence number
187+
5. Adds the ticket price to the prize pool
188+
189+
```solidity
190+
function buyTicket() external payable nonReentrant {
191+
// ... validation ...
192+
193+
uint64 sequenceNumber = entropy.requestV2{value: entropyFee}();
194+
195+
tickets[ticketId] = Ticket({
196+
buyer: msg.sender,
197+
sequenceNumber: sequenceNumber,
198+
randomNumber: bytes32(0),
199+
fulfilled: false
200+
});
201+
202+
// ...
203+
}
204+
```
205+
206+
### Entropy Callback
207+
208+
The Pyth Entropy protocol calls back the contract with random numbers:
209+
210+
```solidity
211+
function entropyCallback(
212+
uint64 sequenceNumber,
213+
address,
214+
bytes32 randomNumber
215+
) internal override {
216+
if (sequenceNumber == winningSequenceNumber) {
217+
// This is the final winning target number
218+
winningTargetNumber = randomNumber;
219+
220+
// Find the ticket closest to the target
221+
for (uint256 i = 0; i < ticketCount; i++) {
222+
if (tickets[i].fulfilled) {
223+
uint256 difference = _calculateDifference(
224+
tickets[i].randomNumber,
225+
winningTargetNumber
226+
);
227+
// Track the closest ticket...
228+
}
229+
}
230+
231+
status = LotteryStatus.ENDED;
232+
} else {
233+
// This is a ticket's random number
234+
uint256 ticketId = sequenceToTicketId[sequenceNumber];
235+
tickets[ticketId].randomNumber = randomNumber;
236+
tickets[ticketId].fulfilled = true;
237+
}
238+
}
239+
```
240+
241+
### Winner Determination
242+
243+
The winner is determined by calculating which ticket's random number has the smallest absolute difference from the target:
244+
245+
```solidity
246+
function _calculateDifference(bytes32 a, bytes32 b) private pure returns (uint256) {
247+
uint256 aValue = uint256(a);
248+
uint256 bValue = uint256(b);
249+
return aValue > bValue ? aValue - bValue : bValue - aValue;
250+
}
251+
```
252+
253+
### Frontend Implementation
254+
255+
The frontend uses Wagmi v2 for Web3 interactions and React Query for state management:
256+
257+
- **BuyTicket Component**: Handles ticket purchases and displays the cost
258+
- **MyTickets Component**: Lists user's tickets with real-time status updates
259+
- **LotteryStatus Component**: Shows prize pool, ticket count, and winner information
260+
261+
The app automatically listens for blockchain events to update the UI when random numbers are revealed or the lottery ends.
262+
263+
## Supported Networks
264+
265+
This example is configured for multiple testnets:
266+
267+
- **Blast Sepolia** (default in deployment)
268+
- **Optimism Sepolia**
269+
- **Arbitrum Sepolia**
270+
271+
To deploy to a different network:
272+
273+
1. Find the Entropy contract and provider addresses for your target network at https://docs.pyth.network/entropy
274+
2. Update `ignition/modules/Lottery.ts` with the correct addresses
275+
3. Ensure the network is configured in `hardhat.config.ts` and `app/config.ts`
276+
4. Deploy using `--network <network-name>`
277+
278+
## Security Considerations
279+
280+
**Randomness Source**: This implementation uses `IEntropyV2.requestV2()` without a user-provided random number. This method uses in-contract PRNG for the user contribution, which means you trust the validator to honestly generate random numbers. For applications requiring stronger guarantees against validator collusion, consider using the variant that accepts a `userRandomNumber` parameter.
281+
282+
**Reentrancy Protection**: The contract uses OpenZeppelin's `ReentrancyGuard` to prevent reentrancy attacks on critical functions like `buyTicket()` and `claimPrize()`.
283+
284+
**Access Control**: Only the contract owner can end the lottery using the `endLottery()` function, preventing premature lottery closures.
285+
286+
## Development Notes
287+
288+
### Technology Stack
289+
290+
**Smart Contracts**:
291+
- Solidity ^0.8.24
292+
- Hardhat for development and deployment
293+
- OpenZeppelin contracts for security
294+
- Pyth Entropy SDK v2.0.0 for randomness
295+
296+
**Frontend**:
297+
- Next.js 14 with App Router
298+
- React 18
299+
- Wagmi v2 for Ethereum interactions
300+
- Viem for contract interactions
301+
- TanStack React Query for state management
302+
- Tailwind CSS with shadcn/ui components
303+
304+
### Testing
305+
306+
To test the contract compilation:
307+
308+
```bash
309+
cd contract
310+
npx hardhat compile
311+
```
312+
313+
To build the frontend:
314+
315+
```bash
316+
cd app
317+
npm run build
318+
```
319+
320+
### Customization
321+
322+
You can customize the lottery parameters by modifying `ignition/modules/Lottery.ts`:
323+
324+
```typescript
325+
const TicketPrice = parseEther("0.001"); // Change ticket price
326+
```
327+
328+
You can also modify the contract to add features like:
329+
- Multiple lottery rounds
330+
- Automatic lottery duration limits
331+
- Multiple winners or prize distribution
332+
- Minimum ticket requirements
333+
334+
## Gas Costs
335+
336+
Approximate gas costs on testnets:
337+
338+
- **Deploy Contract**: ~2,000,000 gas
339+
- **Buy Ticket**: ~150,000 gas (includes Entropy fee)
340+
- **End Lottery**: ~100,000 gas (includes Entropy fee)
341+
- **Claim Prize**: ~50,000 gas
342+
343+
Note: Gas costs vary by network and include the Entropy provider fee for randomness generation.
344+
345+
## Troubleshooting
346+
347+
### Common Issues
348+
349+
**"Lottery not active" error**: The lottery has already ended or is in drawing phase. Wait for a new round.
350+
351+
**"Insufficient fee" error**: Make sure you're sending enough ETH to cover both the ticket price and the Entropy fee. Use `getTotalCost()` to check the required amount.
352+
353+
**Random number not appearing**: Entropy callbacks typically complete within a few seconds, but may take longer during network congestion. Check the blockchain explorer for the callback transaction.
354+
355+
**Wallet not connecting**: Ensure your wallet is on the correct network (e.g., Blast Sepolia). The app will prompt you to switch networks if needed.
356+
357+
## Additional Resources
358+
359+
- **Pyth Entropy Documentation**: https://docs.pyth.network/entropy
360+
- **IEntropyV2 Interface**: https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol
361+
- **Pyth Network**: https://pyth.network
362+
- **Source Repository**: https://github.com/pyth-network/pyth-examples
363+
364+
## License
365+
366+
This example is provided under the MIT License.

entropy/lottery/app/.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

0 commit comments

Comments
 (0)