Skip to content

Commit 2a80fe6

Browse files
committed
feat: add lottery, poker, roulette solidity smart contract
1 parent e73063e commit 2a80fe6

File tree

4 files changed

+487
-22
lines changed

4 files changed

+487
-22
lines changed

README.md

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -305,15 +305,15 @@ cd Multi-Chain-Casino-Games
305305

306306
#### 2️⃣ Solana Setup
307307
```bash
308-
cd solana
308+
cd web3/solana
309309
npm install
310310
anchor build
311311
anchor test
312312
```
313313

314314
#### 3️⃣ EVM Setup
315315
```bash
316-
cd evm
316+
cd web3/evm
317317
forge install
318318
forge build
319319
forge test -vvv
@@ -332,11 +332,13 @@ npm run dev
332332

333333
**Solana Devnet:**
334334
```bash
335+
cd web3/solana
335336
anchor deploy --provider.cluster devnet
336337
```
337338

338339
**Ethereum Sepolia:**
339340
```bash
341+
cd web3/evm
340342
forge script script/Deploy.s.sol --rpc-url sepolia --broadcast --verify
341343
```
342344

@@ -351,25 +353,45 @@ Multi-Chain-Casino-Games/
351353
├── 📄 CONTRIBUTING.md ← Contribution guidelines
352354
├── 📄 SECURITY.md ← Security policy
353355
354-
├── 📁 solana/ ← Solana programs (Rust + Anchor)
355-
│ ├── programs/
356-
│ │ ├── crash/
357-
│ │ ├── coinflip/
358-
│ │ ├── plinko/
359-
│ │ └── dice/
360-
│ ├── tests/
361-
│ ├── Anchor.toml
362-
│ └── README.md
363-
364-
├── 📁 evm/ ← EVM contracts (Solidity)
365-
│ ├── src/
366-
│ │ ├── Crash.sol
367-
│ │ ├── CoinFlip.sol
368-
│ │ ├── Plinko.sol
369-
│ │ └── Dice.sol
370-
│ ├── test/
371-
│ ├── foundry.toml
372-
│ └── README.md
356+
├── 📁 web3/ ← Smart contracts
357+
│ ├── 📁 solana/ ← Solana programs (Rust + Anchor)
358+
│ │ ├── programs/
359+
│ │ │ ├── common/ ← Shared utilities
360+
│ │ │ ├── crash/
361+
│ │ │ ├── coinflip/
362+
│ │ │ ├── plinko/
363+
│ │ │ ├── dice/
364+
│ │ │ ├── jackpot/
365+
│ │ │ ├── slots/
366+
│ │ │ ├── blackjack/
367+
│ │ │ ├── roulette/
368+
│ │ │ ├── poker/
369+
│ │ │ └── lottery/
370+
│ │ ├── tests/
371+
│ │ ├── Anchor.toml
372+
│ │ ├── Cargo.toml
373+
│ │ └── README.md
374+
│ │
375+
│ └── 📁 evm/ ← EVM contracts (Solidity)
376+
│ ├── src/
377+
│ │ ├── interfaces/
378+
│ │ │ └── ICasinoGame.sol
379+
│ │ ├── libraries/
380+
│ │ │ └── CasinoMath.sol
381+
│ │ ├── Crash.sol
382+
│ │ ├── CoinFlip.sol
383+
│ │ ├── Plinko.sol
384+
│ │ ├── Dice.sol
385+
│ │ ├── Jackpot.sol
386+
│ │ ├── Slots.sol
387+
│ │ ├── Blackjack.sol
388+
│ │ ├── Roulette.sol
389+
│ │ ├── Poker.sol
390+
│ │ └── Lottery.sol
391+
│ ├── test/
392+
│ ├── script/
393+
│ ├── foundry.toml
394+
│ └── README.md
373395
374396
├── 📁 frontend/ ← Next.js frontend
375397
│ ├── src/
@@ -394,7 +416,7 @@ Multi-Chain-Casino-Games/
394416
└── evm/
395417
```
396418

397-
> **Status:** 🚧 Repository structure is being populated.
419+
> **Status:** ✅ All smart contracts implemented and ready for deployment.
398420
399421

400422
## ✨ Core Features

web3/evm/src/Lottery.sol

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import "@openzeppelin/contracts/access/Ownable.sol";
7+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
8+
import "./libraries/CasinoMath.sol";
9+
10+
contract Lottery is Ownable, ReentrancyGuard {
11+
using SafeERC20 for IERC20;
12+
13+
struct Ticket {
14+
address player;
15+
uint8[6] numbers;
16+
uint256 betAmount;
17+
uint256 drawId;
18+
uint256 timestamp;
19+
}
20+
21+
struct Draw {
22+
uint256 drawId;
23+
uint8[6] winningNumbers;
24+
uint256 prizePool;
25+
uint256 tickets;
26+
bool drawn;
27+
uint256 timestamp;
28+
}
29+
30+
uint256 public minBet;
31+
uint256 public maxBet;
32+
mapping(uint256 => Draw) public draws;
33+
mapping(uint256 => Ticket) public tickets;
34+
mapping(uint256 => uint256[]) public drawTickets; // drawId => ticketIds
35+
uint256 public ticketCounter;
36+
uint256 public drawCounter;
37+
IERC20 public token;
38+
39+
event TicketBought(
40+
address indexed player,
41+
uint256 indexed ticketId,
42+
uint256 indexed drawId,
43+
uint8[6] numbers,
44+
uint256 betAmount
45+
);
46+
event NumbersDrawn(uint256 indexed drawId, uint8[6] winningNumbers);
47+
event PrizeClaimed(address indexed player, uint256 indexed ticketId, uint8 matches, uint256 prize);
48+
49+
constructor(address _owner, address _token) Ownable(_owner) {
50+
token = IERC20(_token);
51+
}
52+
53+
function initialize(uint256 _minBet, uint256 _maxBet) external onlyOwner {
54+
minBet = _minBet;
55+
maxBet = _maxBet;
56+
}
57+
58+
function buyTicket(
59+
uint256 betAmount,
60+
uint8[6] calldata numbers,
61+
uint256 drawId
62+
) external nonReentrant {
63+
CasinoMath.validateBet(betAmount, minBet, maxBet);
64+
65+
// Validate numbers (1-49)
66+
for (uint i = 0; i < 6; i++) {
67+
require(numbers[i] >= 1 && numbers[i] <= 49, "Invalid number");
68+
}
69+
70+
// Check for duplicates
71+
for (uint i = 0; i < 6; i++) {
72+
for (uint j = i + 1; j < 6; j++) {
73+
require(numbers[i] != numbers[j], "Duplicate numbers");
74+
}
75+
}
76+
77+
token.safeTransferFrom(msg.sender, address(this), betAmount);
78+
79+
// Initialize draw if needed
80+
if (draws[drawId].drawId == 0) {
81+
draws[drawId] = Draw({
82+
drawId: drawId,
83+
winningNumbers: [uint8(0), 0, 0, 0, 0, 0],
84+
prizePool: 0,
85+
tickets: 0,
86+
drawn: false,
87+
timestamp: block.timestamp
88+
});
89+
}
90+
91+
// Update draw pool
92+
Draw storage draw = draws[drawId];
93+
draw.prizePool += betAmount;
94+
draw.tickets++;
95+
96+
// Create ticket
97+
uint256 ticketId = ++ticketCounter;
98+
tickets[ticketId] = Ticket({
99+
player: msg.sender,
100+
numbers: numbers,
101+
betAmount: betAmount,
102+
drawId: drawId,
103+
timestamp: block.timestamp
104+
});
105+
106+
drawTickets[drawId].push(ticketId);
107+
108+
emit TicketBought(msg.sender, ticketId, drawId, numbers, betAmount);
109+
}
110+
111+
function drawNumbers(uint256 drawId) external onlyOwner {
112+
Draw storage draw = draws[drawId];
113+
require(!draw.drawn, "Already drawn");
114+
require(draw.tickets > 0, "No tickets");
115+
116+
// Generate 6 unique random numbers (1-49)
117+
uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, drawId)));
118+
uint8[6] memory numbers;
119+
bool[50] memory used;
120+
121+
for (uint i = 0; i < 6; i++) {
122+
uint8 num;
123+
do {
124+
num = uint8(CasinoMath.generateRandom(seed + i, 48) + 1);
125+
} while (used[num]);
126+
used[num] = true;
127+
numbers[i] = num;
128+
}
129+
130+
// Sort numbers
131+
for (uint i = 0; i < 6; i++) {
132+
for (uint j = i + 1; j < 6; j++) {
133+
if (numbers[i] > numbers[j]) {
134+
uint8 temp = numbers[i];
135+
numbers[i] = numbers[j];
136+
numbers[j] = temp;
137+
}
138+
}
139+
}
140+
141+
draw.winningNumbers = numbers;
142+
draw.drawn = true;
143+
draw.timestamp = block.timestamp;
144+
145+
emit NumbersDrawn(drawId, numbers);
146+
}
147+
148+
function claimPrize(uint256 ticketId) external nonReentrant {
149+
Ticket storage ticket = tickets[ticketId];
150+
require(ticket.player == msg.sender, "Not your ticket");
151+
152+
Draw storage draw = draws[ticket.drawId];
153+
require(draw.drawn, "Draw not completed");
154+
155+
// Calculate matches
156+
uint8 matches = 0;
157+
for (uint i = 0; i < 6; i++) {
158+
for (uint j = 0; j < 6; j++) {
159+
if (ticket.numbers[i] == draw.winningNumbers[j]) {
160+
matches++;
161+
break;
162+
}
163+
}
164+
}
165+
166+
// Calculate prize based on matches
167+
uint256 prize = 0;
168+
if (matches == 6) {
169+
prize = draw.prizePool; // Jackpot
170+
} else if (matches == 5) {
171+
prize = draw.prizePool / 10;
172+
} else if (matches == 4) {
173+
prize = draw.prizePool / 100;
174+
}
175+
176+
require(prize > 0, "No prize");
177+
178+
// Mark ticket as claimed (prevent double claiming)
179+
delete tickets[ticketId];
180+
181+
token.safeTransfer(msg.sender, prize);
182+
emit PrizeClaimed(msg.sender, ticketId, matches, prize);
183+
}
184+
185+
function getDrawTickets(uint256 drawId) external view returns (uint256[] memory) {
186+
return drawTickets[drawId];
187+
}
188+
}
189+

web3/evm/src/Poker.sol

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import "@openzeppelin/contracts/access/Ownable.sol";
7+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
8+
9+
contract Poker is Ownable, ReentrancyGuard {
10+
using SafeERC20 for IERC20;
11+
12+
struct Tournament {
13+
uint256 buyIn;
14+
uint256 prizePool;
15+
address[] players;
16+
uint8 maxPlayers;
17+
uint8 status; // 0=waiting, 1=active, 2=finished
18+
address winner;
19+
}
20+
21+
mapping(uint256 => Tournament) public tournaments;
22+
uint256 public tournamentCounter;
23+
IERC20 public token;
24+
25+
event TournamentCreated(uint256 indexed tournamentId, uint256 buyIn, uint8 maxPlayers);
26+
event PlayerJoined(uint256 indexed tournamentId, address indexed player);
27+
event TournamentStarted(uint256 indexed tournamentId);
28+
event TournamentEnded(uint256 indexed tournamentId, address indexed winner, uint256 prize);
29+
30+
constructor(address _owner, address _token) Ownable(_owner) {
31+
token = IERC20(_token);
32+
}
33+
34+
function createTournament(uint256 buyIn, uint8 maxPlayers) external onlyOwner returns (uint256) {
35+
require(buyIn > 0, "Invalid buy-in");
36+
require(maxPlayers >= 2 && maxPlayers <= 10, "Invalid max players");
37+
38+
uint256 tournamentId = ++tournamentCounter;
39+
tournaments[tournamentId] = Tournament({
40+
buyIn: buyIn,
41+
prizePool: 0,
42+
players: new address[](0),
43+
maxPlayers: maxPlayers,
44+
status: 0,
45+
winner: address(0)
46+
});
47+
48+
emit TournamentCreated(tournamentId, buyIn, maxPlayers);
49+
return tournamentId;
50+
}
51+
52+
function joinTournament(uint256 tournamentId) external nonReentrant {
53+
Tournament storage tournament = tournaments[tournamentId];
54+
require(tournament.status == 0, "Tournament not open");
55+
require(tournament.players.length < tournament.maxPlayers, "Tournament full");
56+
require(tournament.buyIn > 0, "Invalid tournament");
57+
58+
token.safeTransferFrom(msg.sender, address(this), tournament.buyIn);
59+
tournament.players.push(msg.sender);
60+
tournament.prizePool += tournament.buyIn;
61+
62+
emit PlayerJoined(tournamentId, msg.sender);
63+
}
64+
65+
function startTournament(uint256 tournamentId) external onlyOwner {
66+
Tournament storage tournament = tournaments[tournamentId];
67+
require(tournament.status == 0, "Invalid status");
68+
require(tournament.players.length >= 2, "Not enough players");
69+
70+
tournament.status = 1;
71+
emit TournamentStarted(tournamentId);
72+
}
73+
74+
function endTournament(uint256 tournamentId, uint8 winnerIndex) external onlyOwner {
75+
Tournament storage tournament = tournaments[tournamentId];
76+
require(tournament.status == 1, "Invalid status");
77+
require(winnerIndex < tournament.players.length, "Invalid winner index");
78+
79+
address winner = tournament.players[winnerIndex];
80+
tournament.winner = winner;
81+
tournament.status = 2;
82+
83+
if (tournament.prizePool > 0) {
84+
token.safeTransfer(winner, tournament.prizePool);
85+
}
86+
87+
emit TournamentEnded(tournamentId, winner, tournament.prizePool);
88+
}
89+
90+
function getTournamentPlayers(uint256 tournamentId) external view returns (address[] memory) {
91+
return tournaments[tournamentId].players;
92+
}
93+
}
94+

0 commit comments

Comments
 (0)