├── docs ├── API.md ├── DEPLOYMENT.md └── ARCHITECTURE.md ├── telegram-bot └── package.json ├── web3 ├── solana │ ├── .gitignore │ ├── programs │ │ ├── common │ │ │ ├── src │ │ │ │ ├── lib.rs │ │ │ │ ├── errors.rs │ │ │ │ ├── state.rs │ │ │ │ └── utils.rs │ │ │ └── Cargo.toml │ │ ├── dice │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── crash │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── plinko │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── poker │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── slots │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── jackpot │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── lottery │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── blackjack │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── coinflip │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── roulette │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── lib.rs │ ├── Cargo.toml │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── Anchor.toml └── evm │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── package.json │ └── src │ ├── interfaces │ └── ICasinoGame.sol │ ├── libraries │ └── CasinoMath.sol │ ├── Jackpot.sol │ ├── CoinFlip.sol │ ├── Dice.sol │ ├── Crash.sol │ ├── Poker.sol │ ├── Plinko.sol │ ├── Slots.sol │ ├── Blackjack.sol │ ├── Roulette.sol │ └── Lottery.sol ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── .github └── workflows │ └── deploy.yml └── README.md /docs/API.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /telegram-bot/package.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web3/solana/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | .anchor/ 4 | .DS_Store 5 | test-ledger/ 6 | node_modules/ 7 | *.log 8 | 9 | -------------------------------------------------------------------------------- /web3/evm/.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | cache/ 3 | broadcast/ 4 | *.sol 5 | !src/**/*.sol 6 | !script/**/*.sol 7 | !test/**/*.sol 8 | .env 9 | .env.local 10 | 11 | -------------------------------------------------------------------------------- /web3/solana/programs/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | pub mod errors; 4 | pub mod state; 5 | pub mod utils; 6 | 7 | pub use errors::*; 8 | pub use state::*; 9 | pub use utils::*; 10 | 11 | -------------------------------------------------------------------------------- /web3/solana/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*", 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | 12 | [profile.release.build-override] 13 | opt-level = 3 14 | incremental = false 15 | codegen-units = 1 16 | 17 | -------------------------------------------------------------------------------- /web3/solana/programs/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "common" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | 20 | -------------------------------------------------------------------------------- /web3/solana/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es6"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "skipLibCheck": true, 12 | "resolveJsonModule": true 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /web3/solana/programs/dice/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dice" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "dice" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/crash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crash" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "crash" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/plinko/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plinko" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "plinko" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/poker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poker" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "poker" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/slots/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slots" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "slots" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/README.md: -------------------------------------------------------------------------------- 1 | # Solana Smart Contracts 2 | 3 | This directory contains Solana programs for all casino games built with Rust and Anchor framework. 4 | 5 | ## Structure 6 | 7 | ``` 8 | programs/ 9 | ├── crash/ ← Crash game logic 10 | ├── coinflip/ ← Coinflip game logic 11 | ├── plinko/ ← Plinko game logic 12 | ├── dice/ ← Dice game logic 13 | ├── jackpot/ ← Jackpot pool logic 14 | └── common/ ← Shared utilities 15 | ``` -------------------------------------------------------------------------------- /web3/solana/programs/jackpot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jackpot" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "jackpot" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/lottery/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lottery" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "lottery" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/blackjack/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blackjack" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "blackjack" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/coinflip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coinflip" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "coinflip" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/solana/programs/roulette/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "roulette" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "roulette" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | no-idl = [] 13 | no-log-ix-name = [] 14 | cpi = ["no-entrypoint"] 15 | default = [] 16 | 17 | [dependencies] 18 | anchor-lang = "0.29.0" 19 | anchor-spl = "0.29.0" 20 | common = { path = "../common", features = ["no-entrypoint"] } 21 | 22 | -------------------------------------------------------------------------------- /web3/evm/README.md: -------------------------------------------------------------------------------- 1 | # EVM Smart Contracts 2 | 3 | This directory contains Solidity contracts for all casino games, compatible with Ethereum, Polygon, Base, Arbitrum, and other EVM chains. 4 | 5 | ## Planned Structure 6 | 7 | ``` 8 | src/ 9 | ├── Crash.sol ← Crash game contract 10 | ├── CoinFlip.sol ← Coinflip contract 11 | ├── Plinko.sol ← Plinko contract 12 | ├── Dice.sol ← Dice contract 13 | ├── interfaces/ ← Contract interfaces 14 | └── libraries/ ← Shared libraries 15 | ``` 16 | 17 | ## Framework 18 | 19 | Using Foundry for development, testing, and deployment. 20 | -------------------------------------------------------------------------------- /web3/evm/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | solc_version = "0.8.23" 6 | optimizer = true 7 | optimizer_runs = 200 8 | via_ir = false 9 | evm_version = "paris" 10 | remappings = [ 11 | "@openzeppelin/=lib/openzeppelin-contracts/", 12 | "@chainlink/=lib/chainlink/", 13 | ] 14 | 15 | [profile.ci] 16 | fuzz = { runs = 10000 } 17 | 18 | [profile.lite] 19 | optimizer = false 20 | optimizer_runs = 1 21 | 22 | [fmt] 23 | line_length = 120 24 | tab_width = 4 25 | bracket_spacing = true 26 | int_types = "long" 27 | multiline_func_header = "all" 28 | quote_style = "double" 29 | number_underscore = "thousands" 30 | 31 | -------------------------------------------------------------------------------- /web3/evm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-chain-casino-evm", 3 | "version": "1.0.0", 4 | "description": "EVM smart contracts for Multi-Chain Casino Games", 5 | "scripts": { 6 | "build": "forge build", 7 | "test": "forge test", 8 | "test:verbose": "forge test -vvv", 9 | "test:gas": "forge test --gas-report", 10 | "deploy:sepolia": "forge script script/Deploy.s.sol --rpc-url sepolia --broadcast --verify", 11 | "deploy:mainnet": "forge script script/Deploy.s.sol --rpc-url mainnet --broadcast --verify", 12 | "lint": "forge fmt --check", 13 | "format": "forge fmt" 14 | }, 15 | "dependencies": { 16 | "@chainlink/contracts": "^0.8.0", 17 | "@openzeppelin/contracts": "^5.0.0" 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug 4 | target 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # Generated by cargo mutants 13 | # Contains mutation testing data 14 | **/mutants.out*/ 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | #.idea/ 22 | -------------------------------------------------------------------------------- /web3/solana/programs/common/src/errors.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum CasinoError { 5 | #[msg("Invalid bet amount")] 6 | InvalidBetAmount, 7 | #[msg("Bet amount too low")] 8 | BetTooLow, 9 | #[msg("Bet amount too high")] 10 | BetTooHigh, 11 | #[msg("Insufficient funds")] 12 | InsufficientFunds, 13 | #[msg("Game not active")] 14 | GameNotActive, 15 | #[msg("Invalid game state")] 16 | InvalidGameState, 17 | #[msg("Unauthorized")] 18 | Unauthorized, 19 | #[msg("VRF not ready")] 20 | VRFNotReady, 21 | #[msg("Invalid randomness")] 22 | InvalidRandomness, 23 | #[msg("Math overflow")] 24 | MathOverflow, 25 | #[msg("Invalid multiplier")] 26 | InvalidMultiplier, 27 | #[msg("Game already settled")] 28 | GameAlreadySettled, 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web3/solana/programs/common/src/state.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct GameConfig { 5 | pub authority: Pubkey, 6 | pub treasury: Pubkey, 7 | pub min_bet: u64, 8 | pub max_bet: u64, 9 | pub house_edge_bps: u16, // Basis points (e.g., 250 = 2.5%) 10 | pub paused: bool, 11 | pub bump: u8, 12 | } 13 | 14 | impl GameConfig { 15 | pub const LEN: usize = 8 + 32 + 32 + 8 + 8 + 2 + 1 + 1; 16 | } 17 | 18 | #[account] 19 | pub struct GameState { 20 | pub player: Pubkey, 21 | pub bet_amount: u64, 22 | pub game_id: u64, 23 | pub timestamp: i64, 24 | pub settled: bool, 25 | pub result: Option, // Game-specific result 26 | pub payout: Option, 27 | pub bump: u8, 28 | } 29 | 30 | impl GameState { 31 | pub const LEN: usize = 8 + 32 + 8 + 8 + 8 + 1 + 1 + 8 + 1 + 1; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /web3/solana/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-chain-casino-solana", 3 | "version": "1.0.0", 4 | "description": "Solana programs for Multi-Chain Casino Games", 5 | "scripts": { 6 | "build": "anchor build", 7 | "test": "anchor test --skip-local-validator", 8 | "test:local": "anchor test", 9 | "deploy:devnet": "anchor deploy --provider.cluster devnet", 10 | "deploy:mainnet": "anchor deploy --provider.cluster mainnet", 11 | "lint": "cargo clippy --all-targets", 12 | "format": "cargo fmt --all" 13 | }, 14 | "dependencies": { 15 | "@coral-xyz/anchor": "^0.29.0", 16 | "@solana/web3.js": "^1.87.6", 17 | "@solana/spl-token": "^0.3.9" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20.10.0", 21 | "@types/mocha": "^10.0.6", 22 | "chai": "^4.3.10", 23 | "mocha": "^10.2.0", 24 | "ts-mocha": "^10.0.0", 25 | "typescript": "^5.3.3" 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Multi-Chain Casino Games 2 | 3 | ## How to Contribute 4 | 5 | 1. Fork the repository 6 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 7 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 8 | 4. Push to the branch (`git push origin feature/AmazingFeature`) 9 | 5. Open a Pull Request 10 | 11 | ## Development Setup 12 | 13 | See [README.md](./README.md#installation--deployment) 14 | 15 | ## Coding Standards 16 | 17 | ### Solana (Rust) 18 | - Follow Rust conventions 19 | - Use `cargo fmt` and `cargo clippy` 20 | - Write tests for all new features 21 | 22 | ### EVM (Solidity) 23 | - Follow Solidity style guide 24 | - Gas optimize all contracts 25 | - Minimum 80% test coverage 26 | 27 | ## Pull Request Guidelines 28 | 29 | - Clear description of changes 30 | - Link related issues 31 | - Include tests 32 | - Update documentation 33 | 34 | ## Need Help? 35 | 36 | Join our [Telegram](https://t.me/lachancelab) -------------------------------------------------------------------------------- /web3/evm/src/interfaces/ICasinoGame.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | interface ICasinoGame { 5 | struct GameConfig { 6 | address treasury; 7 | uint256 minBet; 8 | uint256 maxBet; 9 | uint16 houseEdgeBps; // Basis points (e.g., 250 = 2.5%) 10 | bool paused; 11 | } 12 | 13 | struct GameState { 14 | address player; 15 | uint256 betAmount; 16 | uint256 gameId; 17 | uint256 timestamp; 18 | bool settled; 19 | uint256 result; 20 | uint256 payout; 21 | } 22 | 23 | event GamePlayed( 24 | address indexed player, 25 | uint256 indexed gameId, 26 | uint256 betAmount, 27 | uint256 result, 28 | uint256 payout 29 | ); 30 | 31 | function initialize( 32 | address treasury, 33 | uint256 minBet, 34 | uint256 maxBet, 35 | uint16 houseEdgeBps 36 | ) external; 37 | 38 | function pause() external; 39 | function unpause() external; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /web3/evm/src/libraries/CasinoMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | library CasinoMath { 5 | error InvalidBetAmount(); 6 | error BetTooLow(); 7 | error BetTooHigh(); 8 | error MathOverflow(); 9 | 10 | function calculatePayout( 11 | uint256 betAmount, 12 | uint256 multiplierBps, 13 | uint16 houseEdgeBps 14 | ) internal pure returns (uint256) { 15 | uint256 payout = (betAmount * multiplierBps) / 10000; 16 | uint256 houseEdge = (payout * houseEdgeBps) / 10000; 17 | return payout - houseEdge; 18 | } 19 | 20 | function validateBet( 21 | uint256 betAmount, 22 | uint256 minBet, 23 | uint256 maxBet 24 | ) internal pure { 25 | if (betAmount < minBet) revert BetTooLow(); 26 | if (betAmount > maxBet) revert BetTooHigh(); 27 | } 28 | 29 | function generateRandom(uint256 seed, uint256 max) internal view returns (uint256) { 30 | return uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, seed))) % (max + 1); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /web3/solana/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | resolution = true 3 | skip-lint = false 4 | 5 | [programs.localnet] 6 | crash = "Crash111111111111111111111111111111111" 7 | coinflip = "CoinFlip1111111111111111111111111111" 8 | plinko = "Plinko1111111111111111111111111111111" 9 | dice = "Dice1111111111111111111111111111111111" 10 | jackpot = "Jackpot11111111111111111111111111111" 11 | slots = "Slots11111111111111111111111111111111" 12 | blackjack = "Blackjack1111111111111111111111111" 13 | roulette = "Roulette111111111111111111111111111" 14 | poker = "Poker11111111111111111111111111111111" 15 | lottery = "Lottery1111111111111111111111111111" 16 | 17 | [registry] 18 | url = "https://api.apr.dev" 19 | 20 | [provider] 21 | cluster = "Localnet" 22 | wallet = "~/.config/solana/id.json" 23 | 24 | [scripts] 25 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 26 | 27 | [test] 28 | startup_wait = 10000 29 | 30 | [test.validator.clone] 31 | address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" 32 | address = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" 33 | address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 LaChanceLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test-solana: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Install Rust 11 | uses: actions-rs/toolchain@v1 12 | - name: Install Solana 13 | run: sh -c "$(curl -sSfL https://release.solana.com/stable/install)" 14 | - name: Install Anchor 15 | run: cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked 16 | - name: Build 17 | run: cd solana && anchor build 18 | - name: Test 19 | run: cd solana && anchor test 20 | 21 | test-evm: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Install Foundry 26 | uses: foundry-rs/foundry-toolchain@v1 27 | - name: Build 28 | run: cd evm && forge build 29 | - name: Test 30 | run: cd evm && forge test -vvv 31 | ``` 32 | 33 | --- 34 | 35 | ### **10. Add Example Code** 36 | 37 | Create `/examples` folder: 38 | ``` 39 | examples/ 40 | ├── solana/ 41 | │ ├── play-crash.ts 42 | │ ├── play-coinflip.ts 43 | │ └── check-results.ts 44 | ├── evm/ 45 | │ ├── play-crash.js 46 | │ └── interact.js 47 | └── README.md -------------------------------------------------------------------------------- /web3/solana/programs/common/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use crate::errors::CasinoError; 3 | 4 | pub fn calculate_payout(bet_amount: u64, multiplier: u64, house_edge_bps: u16) -> Result { 5 | let payout = bet_amount 6 | .checked_mul(multiplier) 7 | .ok_or(CasinoError::MathOverflow)? 8 | .checked_div(10000) 9 | .ok_or(CasinoError::MathOverflow)?; 10 | 11 | let house_edge = payout 12 | .checked_mul(house_edge_bps as u64) 13 | .ok_or(CasinoError::MathOverflow)? 14 | .checked_div(10000) 15 | .ok_or(CasinoError::MathOverflow)?; 16 | 17 | payout 18 | .checked_sub(house_edge) 19 | .ok_or(CasinoError::MathOverflow) 20 | } 21 | 22 | pub fn validate_bet(bet_amount: u64, min_bet: u64, max_bet: u64) -> Result<()> { 23 | require!(bet_amount >= min_bet, CasinoError::BetTooLow); 24 | require!(bet_amount <= max_bet, CasinoError::BetTooHigh); 25 | Ok(()) 26 | } 27 | 28 | pub fn generate_random_from_seed(seed: &[u8], max: u64) -> u64 { 29 | use std::collections::hash_map::DefaultHasher; 30 | use std::hash::{Hash, Hasher}; 31 | 32 | let mut hasher = DefaultHasher::new(); 33 | seed.hash(&mut hasher); 34 | (hasher.finish() % (max as u64 + 1)) as u64 35 | } 36 | 37 | -------------------------------------------------------------------------------- /web3/evm/src/Jackpot.sol: -------------------------------------------------------------------------------- 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 Jackpot is Ownable, ReentrancyGuard { 11 | using SafeERC20 for IERC20; 12 | 13 | uint256 public totalPool; 14 | uint256 public totalBets; 15 | uint16 public rakeBps; // 500 = 5% 16 | IERC20 public token; 17 | 18 | event BetPlaced(address indexed player, uint256 betAmount, uint256 poolContribution); 19 | event WinnerDrawn(address indexed winner, uint256 prize); 20 | 21 | constructor(address _owner, address _token) Ownable(_owner) { 22 | token = IERC20(_token); 23 | rakeBps = 500; // 5% default 24 | } 25 | 26 | function placeBet(uint256 betAmount) external nonReentrant { 27 | token.safeTransferFrom(msg.sender, address(this), betAmount); 28 | 29 | uint256 rake = (betAmount * rakeBps) / 10000; 30 | uint256 poolContribution = betAmount - rake; 31 | 32 | totalPool += poolContribution; 33 | totalBets++; 34 | 35 | emit BetPlaced(msg.sender, betAmount, poolContribution); 36 | } 37 | 38 | function drawWinner(address winner) external onlyOwner { 39 | require(totalPool > 0, "No pool"); 40 | require(winner != address(0), "Invalid winner"); 41 | 42 | uint256 prize = totalPool; 43 | totalPool = 0; 44 | totalBets = 0; 45 | 46 | token.safeTransfer(winner, prize); 47 | emit WinnerDrawn(winner, prize); 48 | } 49 | 50 | function setRake(uint16 newRakeBps) external onlyOwner { 51 | require(newRakeBps <= 1000, "Rake too high"); // Max 10% 52 | rakeBps = newRakeBps; 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /web3/evm/src/CoinFlip.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract CoinFlip is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => GameState) public games; 16 | uint256 public gameCounter; 17 | IERC20 public token; 18 | 19 | event GamePlayed( 20 | address indexed player, 21 | uint256 indexed gameId, 22 | uint256 betAmount, 23 | uint256 choice, 24 | uint256 result, 25 | uint256 payout 26 | ); 27 | 28 | constructor(address _owner, address _token) Ownable(_owner) { 29 | token = IERC20(_token); 30 | } 31 | 32 | function initialize( 33 | address treasury, 34 | uint256 minBet, 35 | uint256 maxBet, 36 | uint16 houseEdgeBps 37 | ) external onlyOwner { 38 | config = GameConfig({ 39 | treasury: treasury, 40 | minBet: minBet, 41 | maxBet: maxBet, 42 | houseEdgeBps: houseEdgeBps, 43 | paused: false 44 | }); 45 | } 46 | 47 | function play(uint256 betAmount, uint8 choice) external nonReentrant { 48 | require(!config.paused, "Game paused"); 49 | require(choice <= 1, "Invalid choice"); 50 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 51 | 52 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 53 | 54 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))); 55 | uint256 result = CasinoMath.generateRandom(seed, 1); 56 | 57 | uint256 gameId = ++gameCounter; 58 | bool won = (choice == result); 59 | uint256 payout = 0; 60 | 61 | if (won) { 62 | // 1.95x payout = 19500 bps 63 | payout = CasinoMath.calculatePayout(betAmount, 19500, config.houseEdgeBps); 64 | token.safeTransferFrom(config.treasury, msg.sender, payout); 65 | } 66 | 67 | games[gameId] = GameState({ 68 | player: msg.sender, 69 | betAmount: betAmount, 70 | gameId: gameId, 71 | timestamp: block.timestamp, 72 | settled: true, 73 | result: result, 74 | payout: payout 75 | }); 76 | 77 | emit GamePlayed(msg.sender, gameId, betAmount, choice, result, payout); 78 | } 79 | 80 | function pause() external onlyOwner { 81 | config.paused = true; 82 | } 83 | 84 | function unpause() external onlyOwner { 85 | config.paused = false; 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /web3/evm/src/Dice.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract Dice is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => GameState) public games; 16 | uint256 public gameCounter; 17 | IERC20 public token; 18 | 19 | event GamePlayed( 20 | address indexed player, 21 | uint256 indexed gameId, 22 | uint256 betAmount, 23 | uint8 target, 24 | bool rollUnder, 25 | uint256 roll, 26 | uint256 payout 27 | ); 28 | 29 | constructor(address _owner, address _token) Ownable(_owner) { 30 | token = IERC20(_token); 31 | } 32 | 33 | function initialize( 34 | address treasury, 35 | uint256 minBet, 36 | uint256 maxBet, 37 | uint16 houseEdgeBps 38 | ) external onlyOwner { 39 | config = GameConfig({ 40 | treasury: treasury, 41 | minBet: minBet, 42 | maxBet: maxBet, 43 | houseEdgeBps: houseEdgeBps, 44 | paused: false 45 | }); 46 | } 47 | 48 | function play(uint256 betAmount, uint8 target, bool rollUnder) external nonReentrant { 49 | require(!config.paused, "Game paused"); 50 | require(target > 0 && target < 100, "Invalid target"); 51 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 52 | 53 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 54 | 55 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))); 56 | uint256 roll = CasinoMath.generateRandom(seed, 99) + 1; // 1-100 57 | 58 | bool won = rollUnder ? (roll < target) : (roll > target); 59 | uint256 payout = 0; 60 | 61 | if (won) { 62 | uint256 probability = rollUnder ? (target - 1) : (100 - target); 63 | uint256 multiplierBps = (10000 * 10000) / probability; 64 | payout = CasinoMath.calculatePayout(betAmount, multiplierBps, config.houseEdgeBps); 65 | token.safeTransferFrom(config.treasury, msg.sender, payout); 66 | } 67 | 68 | uint256 gameId = ++gameCounter; 69 | games[gameId] = GameState({ 70 | player: msg.sender, 71 | betAmount: betAmount, 72 | gameId: gameId, 73 | timestamp: block.timestamp, 74 | settled: true, 75 | result: roll, 76 | payout: payout 77 | }); 78 | 79 | emit GamePlayed(msg.sender, gameId, betAmount, target, rollUnder, roll, payout); 80 | } 81 | 82 | function pause() external onlyOwner { 83 | config.paused = true; 84 | } 85 | 86 | function unpause() external onlyOwner { 87 | config.paused = false; 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /web3/evm/src/Crash.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract Crash is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => GameState) public games; 16 | uint256 public gameCounter; 17 | 18 | event BetPlaced(address indexed player, uint256 indexed gameId, uint256 betAmount, uint256 autoCashout); 19 | event Cashout(address indexed player, uint256 indexed gameId, uint256 multiplier, uint256 payout); 20 | event Crashed(uint256 indexed gameId, uint256 multiplier); 21 | 22 | constructor(address _owner) Ownable(_owner) {} 23 | 24 | function initialize( 25 | address treasury, 26 | uint256 minBet, 27 | uint256 maxBet, 28 | uint16 houseEdgeBps 29 | ) external onlyOwner { 30 | config = GameConfig({ 31 | treasury: treasury, 32 | minBet: minBet, 33 | maxBet: maxBet, 34 | houseEdgeBps: houseEdgeBps, 35 | paused: false 36 | }); 37 | } 38 | 39 | function placeBet(uint256 betAmount, uint256 autoCashout) external nonReentrant { 40 | require(!config.paused, "Game paused"); 41 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 42 | 43 | IERC20 token = IERC20(msg.sender); // Assume token passed via context 44 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 45 | 46 | uint256 gameId = ++gameCounter; 47 | games[gameId] = GameState({ 48 | player: msg.sender, 49 | betAmount: betAmount, 50 | gameId: gameId, 51 | timestamp: block.timestamp, 52 | settled: false, 53 | result: autoCashout, 54 | payout: 0 55 | }); 56 | 57 | emit BetPlaced(msg.sender, gameId, betAmount, autoCashout); 58 | } 59 | 60 | function cashout(uint256 gameId, uint256 currentMultiplier) external nonReentrant { 61 | GameState storage game = games[gameId]; 62 | require(!game.settled, "Game already settled"); 63 | require(game.player == msg.sender, "Not your game"); 64 | 65 | uint256 multiplierBps = currentMultiplier * 100; 66 | uint256 payout = CasinoMath.calculatePayout(game.betAmount, multiplierBps, config.houseEdgeBps); 67 | 68 | game.settled = true; 69 | game.result = currentMultiplier; 70 | game.payout = payout; 71 | 72 | IERC20 token = IERC20(msg.sender); 73 | token.safeTransferFrom(config.treasury, msg.sender, payout); 74 | 75 | emit Cashout(msg.sender, gameId, currentMultiplier, payout); 76 | } 77 | 78 | function settleCrashed(uint256 gameId) external onlyOwner { 79 | GameState storage game = games[gameId]; 80 | require(!game.settled, "Game already settled"); 81 | 82 | game.settled = true; 83 | game.payout = 0; 84 | 85 | emit Crashed(gameId, game.result); 86 | } 87 | 88 | function pause() external onlyOwner { 89 | config.paused = true; 90 | } 91 | 92 | function unpause() external onlyOwner { 93 | config.paused = false; 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /web3/evm/src/Poker.sol: -------------------------------------------------------------------------------- 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 | 95 | -------------------------------------------------------------------------------- /web3/evm/src/Plinko.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract Plinko is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => GameState) public games; 16 | uint256 public gameCounter; 17 | IERC20 public token; 18 | 19 | event GamePlayed( 20 | address indexed player, 21 | uint256 indexed gameId, 22 | uint256 betAmount, 23 | uint8 rows, 24 | int256 position, 25 | uint256 payout 26 | ); 27 | 28 | constructor(address _owner, address _token) Ownable(_owner) { 29 | token = IERC20(_token); 30 | } 31 | 32 | function initialize( 33 | address treasury, 34 | uint256 minBet, 35 | uint256 maxBet, 36 | uint16 houseEdgeBps 37 | ) external onlyOwner { 38 | config = GameConfig({ 39 | treasury: treasury, 40 | minBet: minBet, 41 | maxBet: maxBet, 42 | houseEdgeBps: houseEdgeBps, 43 | paused: false 44 | }); 45 | } 46 | 47 | function play(uint256 betAmount, uint8 rows) external nonReentrant { 48 | require(!config.paused, "Game paused"); 49 | require(rows >= 8 && rows <= 16, "Invalid rows"); 50 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 51 | 52 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 53 | 54 | // Simulate ball path 55 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))); 56 | int256 position = 0; 57 | 58 | for (uint8 i = 0; i < rows; i++) { 59 | uint256 random = CasinoMath.generateRandom(seed + i, 1); 60 | position += (random == 0) ? int256(-1) : int256(1); 61 | } 62 | 63 | uint256 multiplierBps = calculateMultiplier(position, int256(rows)); 64 | uint256 payout = CasinoMath.calculatePayout(betAmount, multiplierBps, config.houseEdgeBps); 65 | 66 | if (payout > 0) { 67 | token.safeTransferFrom(config.treasury, msg.sender, payout); 68 | } 69 | 70 | uint256 gameId = ++gameCounter; 71 | games[gameId] = GameState({ 72 | player: msg.sender, 73 | betAmount: betAmount, 74 | gameId: gameId, 75 | timestamp: block.timestamp, 76 | settled: true, 77 | result: uint256(position >= 0 ? position : -position), 78 | payout: payout 79 | }); 80 | 81 | emit GamePlayed(msg.sender, gameId, betAmount, rows, position, payout); 82 | } 83 | 84 | function calculateMultiplier(int256 position, int256 maxPosition) internal pure returns (uint256) { 85 | int256 distanceFromCenter = position < 0 ? -position : position; 86 | int256 maxDistance = maxPosition; 87 | 88 | if (distanceFromCenter == 0) { 89 | return 10000000; // 1000x at center 90 | } else { 91 | uint256 reduction = uint256((distanceFromCenter * 500000) / maxDistance); 92 | uint256 multiplier = 10000000 > reduction ? 10000000 - reduction : 100000; // Min 10x 93 | return multiplier; 94 | } 95 | } 96 | 97 | function pause() external onlyOwner { 98 | config.paused = true; 99 | } 100 | 101 | function unpause() external onlyOwner { 102 | config.paused = false; 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /web3/evm/src/Slots.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract Slots is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => GameState) public games; 16 | uint256 public gameCounter; 17 | IERC20 public token; 18 | 19 | event GamePlayed( 20 | address indexed player, 21 | uint256 indexed gameId, 22 | uint256 betAmount, 23 | uint8 reel1, 24 | uint8 reel2, 25 | uint8 reel3, 26 | uint256 payout 27 | ); 28 | 29 | constructor(address _owner, address _token) Ownable(_owner) { 30 | token = IERC20(_token); 31 | } 32 | 33 | function initialize( 34 | address treasury, 35 | uint256 minBet, 36 | uint256 maxBet, 37 | uint16 houseEdgeBps 38 | ) external onlyOwner { 39 | config = GameConfig({ 40 | treasury: treasury, 41 | minBet: minBet, 42 | maxBet: maxBet, 43 | houseEdgeBps: houseEdgeBps, 44 | paused: false 45 | }); 46 | } 47 | 48 | function spin(uint256 betAmount) external nonReentrant { 49 | require(!config.paused, "Game paused"); 50 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 51 | 52 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 53 | 54 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))); 55 | uint8 reel1 = uint8(CasinoMath.generateRandom(seed, 6)); 56 | uint8 reel2 = uint8(CasinoMath.generateRandom(seed + 1, 6)); 57 | uint8 reel3 = uint8(CasinoMath.generateRandom(seed + 2, 6)); 58 | 59 | uint256 multiplierBps = calculatePayoutMultiplier(reel1, reel2, reel3); 60 | uint256 payout = CasinoMath.calculatePayout(betAmount, multiplierBps, config.houseEdgeBps); 61 | 62 | if (payout > 0) { 63 | token.safeTransferFrom(config.treasury, msg.sender, payout); 64 | } 65 | 66 | uint256 gameId = ++gameCounter; 67 | games[gameId] = GameState({ 68 | player: msg.sender, 69 | betAmount: betAmount, 70 | gameId: gameId, 71 | timestamp: block.timestamp, 72 | settled: true, 73 | result: (uint256(reel1) << 16) | (uint256(reel2) << 8) | uint256(reel3), 74 | payout: payout 75 | }); 76 | 77 | emit GamePlayed(msg.sender, gameId, betAmount, reel1, reel2, reel3, payout); 78 | } 79 | 80 | function calculatePayoutMultiplier(uint8 reel1, uint8 reel2, uint8 reel3) internal pure returns (uint256) { 81 | // Three of a kind 82 | if (reel1 == reel2 && reel2 == reel3) { 83 | if (reel1 == 6) return 250000; // Three Sevens = 25x 84 | if (reel1 == 5) return 100000; // Three Bars = 10x 85 | if (reel1 == 4) return 50000; // Three Bells = 5x 86 | return 20000; // Three others = 2x 87 | } 88 | // Two of a kind 89 | else if (reel1 == reel2 || reel2 == reel3 || reel1 == reel3) { 90 | return 10000; // 1x 91 | } 92 | // No match 93 | else { 94 | return 0; 95 | } 96 | } 97 | 98 | function pause() external onlyOwner { 99 | config.paused = true; 100 | } 101 | 102 | function unpause() external onlyOwner { 103 | config.paused = false; 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /web3/solana/programs/jackpot/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Jackpot11111111111111111111111111111"); 5 | 6 | #[account] 7 | pub struct JackpotPool { 8 | pub total_pool: u64, 9 | pub total_bets: u64, 10 | pub rake_bps: u16, // 500 = 5% 11 | pub bump: u8, 12 | } 13 | 14 | impl JackpotPool { 15 | pub const LEN: usize = 8 + 8 + 8 + 2 + 1; 16 | } 17 | 18 | #[program] 19 | pub mod jackpot { 20 | use super::*; 21 | 22 | pub fn initialize(ctx: Context, rake_bps: u16) -> Result<()> { 23 | let pool = &mut ctx.accounts.pool; 24 | pool.total_pool = 0; 25 | pool.total_bets = 0; 26 | pool.rake_bps = rake_bps; 27 | pool.bump = ctx.bumps.pool; 28 | Ok(()) 29 | } 30 | 31 | pub fn place_bet(ctx: Context, bet_amount: u64) -> Result<()> { 32 | let pool = &mut ctx.accounts.pool; 33 | 34 | // Transfer bet amount from player to pool 35 | let cpi_accounts = Transfer { 36 | from: ctx.accounts.player_token_account.to_account_info(), 37 | to: ctx.accounts.pool_token_account.to_account_info(), 38 | authority: ctx.accounts.player.to_account_info(), 39 | }; 40 | let cpi_program = ctx.accounts.token_program.to_account_info(); 41 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 42 | token::transfer(cpi_ctx, bet_amount)?; 43 | 44 | // Calculate rake (5% goes to house) 45 | let rake = bet_amount 46 | .checked_mul(pool.rake_bps as u64) 47 | .and_then(|x| x.checked_div(10000)) 48 | .ok_or(common::CasinoError::MathOverflow)?; 49 | 50 | let pool_contribution = bet_amount 51 | .checked_sub(rake) 52 | .ok_or(common::CasinoError::MathOverflow)?; 53 | 54 | pool.total_pool = pool.total_pool 55 | .checked_add(pool_contribution) 56 | .ok_or(common::CasinoError::MathOverflow)?; 57 | pool.total_bets = pool.total_bets 58 | .checked_add(1) 59 | .ok_or(common::CasinoError::MathOverflow)?; 60 | 61 | Ok(()) 62 | } 63 | 64 | pub fn draw_winner(ctx: Context) -> Result<()> { 65 | let pool = &ctx.accounts.pool; 66 | require!(pool.total_pool > 0, common::CasinoError::InsufficientFunds); 67 | 68 | // Generate random winner from recent bets 69 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 70 | let winner_index = common::generate_random_from_seed(&seed, pool.total_bets - 1); 71 | 72 | // Transfer entire pool to winner 73 | let seeds = &[ 74 | b"pool", 75 | ctx.accounts.pool.to_account_info().key.as_ref(), 76 | &[pool.bump], 77 | ]; 78 | let signer = &[&seeds[..]]; 79 | 80 | let cpi_accounts = Transfer { 81 | from: ctx.accounts.pool_token_account.to_account_info(), 82 | to: ctx.accounts.winner_token_account.to_account_info(), 83 | authority: ctx.accounts.pool.to_account_info(), 84 | }; 85 | let cpi_program = ctx.accounts.token_program.to_account_info(); 86 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 87 | token::transfer(cpi_ctx, pool.total_pool)?; 88 | 89 | // Reset pool 90 | let pool = &mut ctx.accounts.pool; 91 | pool.total_pool = 0; 92 | pool.total_bets = 0; 93 | 94 | Ok(()) 95 | } 96 | } 97 | 98 | #[derive(Accounts)] 99 | pub struct Initialize<'info> { 100 | #[account( 101 | init, 102 | payer = authority, 103 | space = JackpotPool::LEN, 104 | seeds = [b"pool", b"jackpot"], 105 | bump 106 | )] 107 | pub pool: Account<'info, JackpotPool>, 108 | 109 | #[account(mut)] 110 | pub authority: Signer<'info>, 111 | 112 | pub system_program: Program<'info, System>, 113 | } 114 | 115 | #[derive(Accounts)] 116 | pub struct PlaceBet<'info> { 117 | #[account(mut)] 118 | pub pool: Account<'info, JackpotPool>, 119 | 120 | #[account(mut)] 121 | pub player: Signer<'info>, 122 | 123 | #[account(mut)] 124 | pub player_token_account: Account<'info, TokenAccount>, 125 | 126 | #[account( 127 | mut, 128 | constraint = pool_token_account.owner == pool.key() 129 | )] 130 | pub pool_token_account: Account<'info, TokenAccount>, 131 | 132 | pub token_program: Program<'info, Token>, 133 | } 134 | 135 | #[derive(Accounts)] 136 | pub struct DrawWinner<'info> { 137 | #[account(mut)] 138 | pub pool: Account<'info, JackpotPool>, 139 | 140 | /// CHECK: Winner account 141 | pub winner: UncheckedAccount<'info>, 142 | 143 | #[account(mut)] 144 | pub pool_token_account: Account<'info, TokenAccount>, 145 | 146 | #[account(mut)] 147 | pub winner_token_account: Account<'info, TokenAccount>, 148 | 149 | pub token_program: Program<'info, Token>, 150 | pub clock: Sysvar<'info, Clock>, 151 | } 152 | 153 | -------------------------------------------------------------------------------- /web3/solana/programs/poker/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Poker11111111111111111111111111111111"); 5 | 6 | #[account] 7 | pub struct PokerTournament { 8 | pub buy_in: u64, 9 | pub prize_pool: u64, 10 | pub players: Vec, 11 | pub max_players: u8, 12 | pub status: u8, // 0=waiting, 1=active, 2=finished 13 | pub winner: Option, 14 | pub bump: u8, 15 | } 16 | 17 | impl PokerTournament { 18 | pub const LEN: usize = 8 + 8 + 8 + 4 + 1 + 1 + 1 + 33 + 1 + 200; // Extra space for players 19 | } 20 | 21 | #[program] 22 | pub mod poker { 23 | use super::*; 24 | 25 | pub fn create_tournament(ctx: Context, buy_in: u64, max_players: u8) -> Result<()> { 26 | let tournament = &mut ctx.accounts.tournament; 27 | tournament.buy_in = buy_in; 28 | tournament.prize_pool = 0; 29 | tournament.players = vec![]; 30 | tournament.max_players = max_players; 31 | tournament.status = 0; 32 | tournament.winner = None; 33 | tournament.bump = ctx.bumps.tournament; 34 | Ok(()) 35 | } 36 | 37 | pub fn join_tournament(ctx: Context) -> Result<()> { 38 | let tournament = &mut ctx.accounts.tournament; 39 | require!(tournament.status == 0, common::CasinoError::InvalidGameState); 40 | require!(tournament.players.len() < tournament.max_players as usize, common::CasinoError::InvalidGameState); 41 | 42 | // Transfer buy-in 43 | let cpi_accounts = Transfer { 44 | from: ctx.accounts.player_token_account.to_account_info(), 45 | to: ctx.accounts.tournament_token_account.to_account_info(), 46 | authority: ctx.accounts.player.to_account_info(), 47 | }; 48 | let cpi_program = ctx.accounts.token_program.to_account_info(); 49 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 50 | token::transfer(cpi_ctx, tournament.buy_in)?; 51 | 52 | tournament.players.push(ctx.accounts.player.key()); 53 | tournament.prize_pool = tournament.prize_pool 54 | .checked_add(tournament.buy_in) 55 | .ok_or(common::CasinoError::MathOverflow)?; 56 | 57 | Ok(()) 58 | } 59 | 60 | pub fn start_tournament(ctx: Context) -> Result<()> { 61 | let tournament = &mut ctx.accounts.tournament; 62 | require!(tournament.status == 0, common::CasinoError::InvalidGameState); 63 | require!(tournament.players.len() >= 2, common::CasinoError::InvalidGameState); 64 | tournament.status = 1; 65 | Ok(()) 66 | } 67 | 68 | pub fn end_tournament(ctx: Context, winner_index: u8) -> Result<()> { 69 | let tournament = &mut ctx.accounts.tournament; 70 | require!(tournament.status == 1, common::CasinoError::InvalidGameState); 71 | require!((winner_index as usize) < tournament.players.len(), common::CasinoError::InvalidBetAmount); 72 | 73 | let winner = tournament.players[winner_index as usize]; 74 | tournament.winner = Some(winner); 75 | tournament.status = 2; 76 | 77 | // Transfer prize pool to winner 78 | let seeds = &[ 79 | b"tournament", 80 | ctx.accounts.tournament.to_account_info().key.as_ref(), 81 | &[tournament.bump], 82 | ]; 83 | let signer = &[&seeds[..]]; 84 | 85 | let cpi_accounts = Transfer { 86 | from: ctx.accounts.tournament_token_account.to_account_info(), 87 | to: ctx.accounts.winner_token_account.to_account_info(), 88 | authority: ctx.accounts.tournament.to_account_info(), 89 | }; 90 | let cpi_program = ctx.accounts.token_program.to_account_info(); 91 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 92 | token::transfer(cpi_ctx, tournament.prize_pool)?; 93 | 94 | Ok(()) 95 | } 96 | } 97 | 98 | #[derive(Accounts)] 99 | pub struct CreateTournament<'info> { 100 | #[account( 101 | init, 102 | payer = authority, 103 | space = PokerTournament::LEN, 104 | seeds = [b"tournament", authority.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 105 | bump 106 | )] 107 | pub tournament: Account<'info, PokerTournament>, 108 | 109 | #[account(mut)] 110 | pub authority: Signer<'info>, 111 | 112 | pub system_program: Program<'info, System>, 113 | pub clock: Sysvar<'info, Clock>, 114 | } 115 | 116 | #[derive(Accounts)] 117 | pub struct JoinTournament<'info> { 118 | #[account(mut)] 119 | pub tournament: Account<'info, PokerTournament>, 120 | 121 | #[account(mut)] 122 | pub player: Signer<'info>, 123 | 124 | #[account(mut)] 125 | pub player_token_account: Account<'info, TokenAccount>, 126 | 127 | #[account(mut)] 128 | pub tournament_token_account: Account<'info, TokenAccount>, 129 | 130 | pub token_program: Program<'info, Token>, 131 | } 132 | 133 | #[derive(Accounts)] 134 | pub struct StartTournament<'info> { 135 | #[account(mut)] 136 | pub tournament: Account<'info, PokerTournament>, 137 | pub authority: Signer<'info>, 138 | } 139 | 140 | #[derive(Accounts)] 141 | pub struct EndTournament<'info> { 142 | #[account(mut)] 143 | pub tournament: Account<'info, PokerTournament>, 144 | 145 | /// CHECK: Winner account 146 | pub winner: UncheckedAccount<'info>, 147 | 148 | #[account(mut)] 149 | pub tournament_token_account: Account<'info, TokenAccount>, 150 | 151 | #[account(mut)] 152 | pub winner_token_account: Account<'info, TokenAccount>, 153 | 154 | pub token_program: Program<'info, Token>, 155 | } 156 | 157 | -------------------------------------------------------------------------------- /web3/solana/programs/roulette/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Roulette111111111111111111111111111"); 5 | 6 | #[program] 7 | pub mod roulette { 8 | use super::*; 9 | 10 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 11 | let config = &mut ctx.accounts.config; 12 | config.authority = ctx.accounts.authority.key(); 13 | config.treasury = ctx.accounts.treasury.key(); 14 | config.min_bet = min_bet; 15 | config.max_bet = max_bet; 16 | config.house_edge_bps = house_edge_bps; 17 | config.paused = false; 18 | config.bump = ctx.bumps.config; 19 | Ok(()) 20 | } 21 | 22 | pub fn place_bet(ctx: Context, bet_amount: u64, bet_type: u8, bet_value: u8) -> Result<()> { 23 | let config = &ctx.accounts.config; 24 | require!(!config.paused, common::CasinoError::GameNotActive); 25 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 26 | 27 | // Transfer bet 28 | let cpi_accounts = Transfer { 29 | from: ctx.accounts.player_token_account.to_account_info(), 30 | to: ctx.accounts.treasury_token_account.to_account_info(), 31 | authority: ctx.accounts.player.to_account_info(), 32 | }; 33 | let cpi_program = ctx.accounts.token_program.to_account_info(); 34 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 35 | token::transfer(cpi_ctx, bet_amount)?; 36 | 37 | // Spin wheel (0-36 for European, 0-37 for American with 00) 38 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 39 | let winning_number = common::generate_random_from_seed(&seed, 36); 40 | 41 | // Calculate payout based on bet type 42 | let multiplier_bps = match bet_type { 43 | 0 => 360000, // Single number (35:1) 44 | 1 => 20000, // Red/Black (1:1) 45 | 2 => 20000, // Odd/Even (1:1) 46 | 3 => 20000, // High/Low (1:1) 47 | 4 => 30000, // Dozen (2:1) 48 | 5 => 30000, // Column (2:1) 49 | _ => return Err(common::CasinoError::InvalidBetAmount.into()), 50 | }; 51 | 52 | let won = check_win(winning_number, bet_type, bet_value); 53 | let payout = if won { 54 | common::calculate_payout(bet_amount, multiplier_bps, config.house_edge_bps)? 55 | } else { 56 | 0 57 | }; 58 | 59 | let game_state = &mut ctx.accounts.game_state; 60 | game_state.player = ctx.accounts.player.key(); 61 | game_state.bet_amount = bet_amount; 62 | game_state.game_id = Clock::get()?.unix_timestamp as u64; 63 | game_state.timestamp = Clock::get()?.unix_timestamp; 64 | game_state.settled = true; 65 | game_state.result = Some(winning_number as u64); 66 | game_state.payout = Some(payout); 67 | 68 | if payout > 0 { 69 | let seeds = &[ 70 | b"treasury", 71 | config.to_account_info().key.as_ref(), 72 | &[config.bump], 73 | ]; 74 | let signer = &[&seeds[..]]; 75 | 76 | let cpi_accounts = Transfer { 77 | from: ctx.accounts.treasury_token_account.to_account_info(), 78 | to: ctx.accounts.player_token_account.to_account_info(), 79 | authority: config.to_account_info(), 80 | }; 81 | let cpi_program = ctx.accounts.token_program.to_account_info(); 82 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 83 | token::transfer(cpi_ctx, payout)?; 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | fn check_win(winning_number: u64, bet_type: u8, bet_value: u8) -> bool { 90 | match bet_type { 91 | 0 => winning_number == bet_value as u64, // Single number 92 | 1 => (winning_number % 2 == 1) == (bet_value == 1), // Red/Black 93 | 2 => (winning_number % 2 == 0) == (bet_value == 1), // Odd/Even 94 | 3 => (winning_number > 18) == (bet_value == 1), // High/Low 95 | 4 => (winning_number / 12) == bet_value as u64, // Dozen 96 | 5 => (winning_number % 3) == bet_value as u64, // Column 97 | _ => false, 98 | } 99 | } 100 | } 101 | 102 | #[derive(Accounts)] 103 | pub struct Initialize<'info> { 104 | #[account( 105 | init, 106 | payer = authority, 107 | space = common::GameConfig::LEN, 108 | seeds = [b"config", b"roulette"], 109 | bump 110 | )] 111 | pub config: Account<'info, common::GameConfig>, 112 | 113 | #[account(mut)] 114 | pub authority: Signer<'info>, 115 | 116 | /// CHECK: Treasury account 117 | pub treasury: UncheckedAccount<'info>, 118 | 119 | pub system_program: Program<'info, System>, 120 | } 121 | 122 | #[derive(Accounts)] 123 | pub struct PlaceBet<'info> { 124 | #[account( 125 | seeds = [b"config", b"roulette"], 126 | bump = config.bump 127 | )] 128 | pub config: Account<'info, common::GameConfig>, 129 | 130 | #[account(mut)] 131 | pub player: Signer<'info>, 132 | 133 | #[account(mut)] 134 | pub player_token_account: Account<'info, TokenAccount>, 135 | 136 | #[account(mut)] 137 | pub treasury_token_account: Account<'info, TokenAccount>, 138 | 139 | #[account( 140 | init, 141 | payer = player, 142 | space = common::GameState::LEN, 143 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 144 | bump 145 | )] 146 | pub game_state: Account<'info, common::GameState>, 147 | 148 | pub token_program: Program<'info, Token>, 149 | pub system_program: Program<'info, System>, 150 | pub clock: Sysvar<'info, Clock>, 151 | } 152 | 153 | -------------------------------------------------------------------------------- /web3/evm/src/Blackjack.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract Blackjack is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => BlackjackGame) public games; 16 | uint256 public gameCounter; 17 | IERC20 public token; 18 | 19 | struct BlackjackGame { 20 | address player; 21 | uint256 betAmount; 22 | uint8[] playerCards; 23 | uint8[] dealerCards; 24 | uint8 playerScore; 25 | uint8 dealerScore; 26 | uint8 gameState; // 0=betting, 1=playing, 2=settled 27 | uint256 timestamp; 28 | } 29 | 30 | event GameStarted(address indexed player, uint256 indexed gameId, uint256 betAmount); 31 | event CardDealt(address indexed player, uint256 indexed gameId, bool isPlayer, uint8 card); 32 | event GameSettled(address indexed player, uint256 indexed gameId, uint256 payout); 33 | 34 | constructor(address _owner, address _token) Ownable(_owner) { 35 | token = IERC20(_token); 36 | } 37 | 38 | function initialize( 39 | address treasury, 40 | uint256 minBet, 41 | uint256 maxBet, 42 | uint16 houseEdgeBps 43 | ) external onlyOwner { 44 | config = GameConfig({ 45 | treasury: treasury, 46 | minBet: minBet, 47 | maxBet: maxBet, 48 | houseEdgeBps: houseEdgeBps, 49 | paused: false 50 | }); 51 | } 52 | 53 | function placeBet(uint256 betAmount) external nonReentrant { 54 | require(!config.paused, "Game paused"); 55 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 56 | 57 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 58 | 59 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))); 60 | uint8 playerCard1 = uint8(CasinoMath.generateRandom(seed, 12) + 1); 61 | uint8 playerCard2 = uint8(CasinoMath.generateRandom(seed + 1, 12) + 1); 62 | uint8 dealerCard1 = uint8(CasinoMath.generateRandom(seed + 2, 12) + 1); 63 | 64 | uint256 gameId = ++gameCounter; 65 | BlackjackGame storage game = games[gameId]; 66 | game.player = msg.sender; 67 | game.betAmount = betAmount; 68 | game.playerCards.push(playerCard1); 69 | game.playerCards.push(playerCard2); 70 | game.dealerCards.push(dealerCard1); 71 | game.playerScore = calculateScore(game.playerCards); 72 | game.dealerScore = calculateScore(game.dealerCards); 73 | game.gameState = 1; 74 | game.timestamp = block.timestamp; 75 | 76 | // Check for blackjack 77 | if (game.playerScore == 21) { 78 | game.gameState = 2; 79 | uint256 payout = (betAmount * 3) / 2; // 3:2 payout 80 | token.safeTransferFrom(config.treasury, msg.sender, payout); 81 | emit GameSettled(msg.sender, gameId, payout); 82 | } 83 | 84 | emit GameStarted(msg.sender, gameId, betAmount); 85 | } 86 | 87 | function hit(uint256 gameId) external nonReentrant { 88 | BlackjackGame storage game = games[gameId]; 89 | require(game.gameState == 1, "Invalid state"); 90 | require(game.player == msg.sender, "Not your game"); 91 | 92 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, gameId))); 93 | uint8 newCard = uint8(CasinoMath.generateRandom(seed, 12) + 1); 94 | game.playerCards.push(newCard); 95 | game.playerScore = calculateScore(game.playerCards); 96 | 97 | if (game.playerScore > 21) { 98 | game.gameState = 2; 99 | emit GameSettled(msg.sender, gameId, 0); 100 | } 101 | } 102 | 103 | function stand(uint256 gameId) external nonReentrant { 104 | BlackjackGame storage game = games[gameId]; 105 | require(game.gameState == 1, "Invalid state"); 106 | require(game.player == msg.sender, "Not your game"); 107 | 108 | // Dealer draws until 17+ 109 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, gameId))); 110 | while (game.dealerScore < 17) { 111 | uint8 newCard = uint8(CasinoMath.generateRandom(seed + game.dealerCards.length, 12) + 1); 112 | game.dealerCards.push(newCard); 113 | game.dealerScore = calculateScore(game.dealerCards); 114 | } 115 | 116 | game.gameState = 2; 117 | 118 | uint256 payout = 0; 119 | if (game.dealerScore > 21 || game.playerScore > game.dealerScore) { 120 | payout = game.betAmount * 2; // 1:1 payout 121 | } else if (game.playerScore == game.dealerScore) { 122 | payout = game.betAmount; // Push 123 | } 124 | 125 | if (payout > 0) { 126 | token.safeTransferFrom(config.treasury, msg.sender, payout); 127 | } 128 | 129 | emit GameSettled(msg.sender, gameId, payout); 130 | } 131 | 132 | function calculateScore(uint8[] memory cards) internal pure returns (uint8) { 133 | uint8 score = 0; 134 | uint8 aces = 0; 135 | 136 | for (uint i = 0; i < cards.length; i++) { 137 | uint8 value = cards[i] % 13; 138 | if (value == 0) { 139 | aces++; 140 | score += 11; 141 | } else if (value >= 10) { 142 | score += 10; 143 | } else { 144 | score += value + 1; 145 | } 146 | } 147 | 148 | while (score > 21 && aces > 0) { 149 | score -= 10; 150 | aces--; 151 | } 152 | 153 | return score; 154 | } 155 | 156 | function pause() external onlyOwner { 157 | config.paused = true; 158 | } 159 | 160 | function unpause() external onlyOwner { 161 | config.paused = false; 162 | } 163 | } 164 | 165 | -------------------------------------------------------------------------------- /web3/evm/src/Roulette.sol: -------------------------------------------------------------------------------- 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 "./interfaces/ICasinoGame.sol"; 9 | import "./libraries/CasinoMath.sol"; 10 | 11 | contract Roulette is ICasinoGame, Ownable, ReentrancyGuard { 12 | using SafeERC20 for IERC20; 13 | 14 | GameConfig public config; 15 | mapping(uint256 => GameState) public games; 16 | uint256 public gameCounter; 17 | IERC20 public token; 18 | 19 | // Bet types: 0=Single, 1=Red/Black, 2=Odd/Even, 3=High/Low, 4=Dozen, 5=Column 20 | event GamePlayed( 21 | address indexed player, 22 | uint256 indexed gameId, 23 | uint256 betAmount, 24 | uint8 betType, 25 | uint8 betValue, 26 | uint256 winningNumber, 27 | uint256 payout 28 | ); 29 | 30 | constructor(address _owner, address _token) Ownable(_owner) { 31 | token = IERC20(_token); 32 | } 33 | 34 | function initialize( 35 | address treasury, 36 | uint256 minBet, 37 | uint256 maxBet, 38 | uint16 houseEdgeBps 39 | ) external onlyOwner { 40 | config = GameConfig({ 41 | treasury: treasury, 42 | minBet: minBet, 43 | maxBet: maxBet, 44 | houseEdgeBps: houseEdgeBps, 45 | paused: false 46 | }); 47 | } 48 | 49 | function placeBet( 50 | uint256 betAmount, 51 | uint8 betType, 52 | uint8 betValue 53 | ) external nonReentrant { 54 | require(!config.paused, "Game paused"); 55 | require(betType <= 5, "Invalid bet type"); 56 | CasinoMath.validateBet(betAmount, config.minBet, config.maxBet); 57 | 58 | // Validate bet value based on bet type 59 | if (betType == 0) { 60 | require(betValue <= 36, "Invalid number"); 61 | } else if (betType == 1 || betType == 2 || betType == 3) { 62 | require(betValue <= 1, "Invalid bet value"); 63 | } else if (betType == 4) { 64 | require(betValue <= 2, "Invalid dozen"); 65 | } else if (betType == 5) { 66 | require(betValue <= 2, "Invalid column"); 67 | } 68 | 69 | token.safeTransferFrom(msg.sender, config.treasury, betAmount); 70 | 71 | // Spin wheel (0-36 for European roulette) 72 | uint256 seed = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))); 73 | uint256 winningNumber = CasinoMath.generateRandom(seed, 36); 74 | 75 | // Calculate payout based on bet type 76 | uint256 multiplierBps = getMultiplier(betType); 77 | bool won = checkWin(winningNumber, betType, betValue); 78 | 79 | uint256 payout = 0; 80 | if (won) { 81 | payout = CasinoMath.calculatePayout(betAmount, multiplierBps, config.houseEdgeBps); 82 | token.safeTransferFrom(config.treasury, msg.sender, payout); 83 | } 84 | 85 | uint256 gameId = ++gameCounter; 86 | games[gameId] = GameState({ 87 | player: msg.sender, 88 | betAmount: betAmount, 89 | gameId: gameId, 90 | timestamp: block.timestamp, 91 | settled: true, 92 | result: winningNumber, 93 | payout: payout 94 | }); 95 | 96 | emit GamePlayed(msg.sender, gameId, betAmount, betType, betValue, winningNumber, payout); 97 | } 98 | 99 | function getMultiplier(uint8 betType) internal pure returns (uint256) { 100 | if (betType == 0) return 360000; // Single number (35:1) = 360000 bps 101 | if (betType == 1 || betType == 2 || betType == 3) return 20000; // Red/Black, Odd/Even, High/Low (1:1) = 20000 bps 102 | if (betType == 4 || betType == 5) return 30000; // Dozen, Column (2:1) = 30000 bps 103 | return 0; 104 | } 105 | 106 | function checkWin( 107 | uint256 winningNumber, 108 | uint8 betType, 109 | uint8 betValue 110 | ) internal pure returns (bool) { 111 | if (betType == 0) { 112 | // Single number 113 | return winningNumber == betValue; 114 | } else if (betType == 1) { 115 | // Red/Black (0=Black, 1=Red) 116 | // Red numbers: 1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36 117 | bool isRed = isRedNumber(winningNumber); 118 | return (betValue == 1 && isRed) || (betValue == 0 && !isRed && winningNumber != 0); 119 | } else if (betType == 2) { 120 | // Odd/Even (0=Even, 1=Odd) 121 | bool isOdd = (winningNumber % 2 == 1); 122 | return (betValue == 1 && isOdd) || (betValue == 0 && !isOdd && winningNumber != 0); 123 | } else if (betType == 3) { 124 | // High/Low (0=Low 1-18, 1=High 19-36) 125 | bool isHigh = winningNumber > 18 && winningNumber <= 36; 126 | return (betValue == 1 && isHigh) || (betValue == 0 && !isHigh && winningNumber != 0); 127 | } else if (betType == 4) { 128 | // Dozen (0=1-12, 1=13-24, 2=25-36) 129 | if (winningNumber == 0) return false; 130 | uint256 dozen = (winningNumber - 1) / 12; 131 | return dozen == betValue; 132 | } else if (betType == 5) { 133 | // Column (0=1st, 1=2nd, 2=3rd) 134 | if (winningNumber == 0) return false; 135 | uint256 column = (winningNumber - 1) % 3; 136 | return column == betValue; 137 | } 138 | return false; 139 | } 140 | 141 | function isRedNumber(uint256 number) internal pure returns (bool) { 142 | if (number == 0) return false; 143 | uint8[18] memory redNumbers = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36]; 144 | for (uint i = 0; i < redNumbers.length; i++) { 145 | if (number == redNumbers[i]) { 146 | return true; 147 | } 148 | } 149 | return false; 150 | } 151 | 152 | function pause() external onlyOwner { 153 | config.paused = true; 154 | } 155 | 156 | function unpause() external onlyOwner { 157 | config.paused = false; 158 | } 159 | } 160 | 161 | -------------------------------------------------------------------------------- /web3/solana/programs/coinflip/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("CoinFlip1111111111111111111111111111"); 5 | 6 | #[program] 7 | pub mod coinflip { 8 | use super::*; 9 | 10 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 11 | let config = &mut ctx.accounts.config; 12 | config.authority = ctx.accounts.authority.key(); 13 | config.treasury = ctx.accounts.treasury.key(); 14 | config.min_bet = min_bet; 15 | config.max_bet = max_bet; 16 | config.house_edge_bps = house_edge_bps; 17 | config.paused = false; 18 | config.bump = ctx.bumps.config; 19 | Ok(()) 20 | } 21 | 22 | pub fn play(ctx: Context, bet_amount: u64, choice: u8) -> Result<()> { 23 | let config = &ctx.accounts.config; 24 | require!(!config.paused, common::CasinoError::GameNotActive); 25 | 26 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 27 | 28 | // Transfer bet amount from player to treasury 29 | let cpi_accounts = Transfer { 30 | from: ctx.accounts.player_token_account.to_account_info(), 31 | to: ctx.accounts.treasury_token_account.to_account_info(), 32 | authority: ctx.accounts.player.to_account_info(), 33 | }; 34 | let cpi_program = ctx.accounts.token_program.to_account_info(); 35 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 36 | token::transfer(cpi_ctx, bet_amount)?; 37 | 38 | // Generate random result (0 = heads, 1 = tails) 39 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 40 | let random = common::generate_random_from_seed(&seed, 1); 41 | let result = random; 42 | 43 | let game_state = &mut ctx.accounts.game_state; 44 | game_state.player = ctx.accounts.player.key(); 45 | game_state.bet_amount = bet_amount; 46 | game_state.game_id = Clock::get()?.unix_timestamp as u64; 47 | game_state.timestamp = Clock::get()?.unix_timestamp; 48 | game_state.settled = false; 49 | game_state.result = Some(result); 50 | game_state.bump = ctx.bumps.game_state; 51 | 52 | // Calculate payout (1.95x = 19500/10000) 53 | let won = (choice == result); 54 | if won { 55 | let payout = common::calculate_payout(bet_amount, 19500, config.house_edge_bps)?; 56 | game_state.payout = Some(payout); 57 | 58 | // Transfer payout to player 59 | let seeds = &[ 60 | b"treasury", 61 | ctx.accounts.config.to_account_info().key.as_ref(), 62 | &[ctx.accounts.config.bump], 63 | ]; 64 | let signer = &[&seeds[..]]; 65 | 66 | let cpi_accounts = Transfer { 67 | from: ctx.accounts.treasury_token_account.to_account_info(), 68 | to: ctx.accounts.player_token_account.to_account_info(), 69 | authority: ctx.accounts.config.to_account_info(), 70 | }; 71 | let cpi_program = ctx.accounts.token_program.to_account_info(); 72 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 73 | token::transfer(cpi_ctx, payout)?; 74 | } else { 75 | game_state.payout = Some(0); 76 | } 77 | 78 | game_state.settled = true; 79 | Ok(()) 80 | } 81 | 82 | pub fn pause(ctx: Context) -> Result<()> { 83 | let config = &mut ctx.accounts.config; 84 | require!( 85 | ctx.accounts.authority.key() == config.authority, 86 | common::CasinoError::Unauthorized 87 | ); 88 | config.paused = true; 89 | Ok(()) 90 | } 91 | 92 | pub fn unpause(ctx: Context) -> Result<()> { 93 | let config = &mut ctx.accounts.config; 94 | require!( 95 | ctx.accounts.authority.key() == config.authority, 96 | common::CasinoError::Unauthorized 97 | ); 98 | config.paused = false; 99 | Ok(()) 100 | } 101 | } 102 | 103 | #[derive(Accounts)] 104 | pub struct Initialize<'info> { 105 | #[account( 106 | init, 107 | payer = authority, 108 | space = common::GameConfig::LEN, 109 | seeds = [b"config", b"coinflip"], 110 | bump 111 | )] 112 | pub config: Account<'info, common::GameConfig>, 113 | 114 | #[account(mut)] 115 | pub authority: Signer<'info>, 116 | 117 | /// CHECK: Treasury account 118 | pub treasury: UncheckedAccount<'info>, 119 | 120 | pub system_program: Program<'info, System>, 121 | } 122 | 123 | #[derive(Accounts)] 124 | pub struct Play<'info> { 125 | #[account( 126 | seeds = [b"config", b"coinflip"], 127 | bump = config.bump 128 | )] 129 | pub config: Account<'info, common::GameConfig>, 130 | 131 | #[account(mut)] 132 | pub player: Signer<'info>, 133 | 134 | #[account( 135 | mut, 136 | constraint = player_token_account.owner == player.key() 137 | )] 138 | pub player_token_account: Account<'info, TokenAccount>, 139 | 140 | #[account( 141 | mut, 142 | constraint = treasury_token_account.owner == treasury.key() 143 | )] 144 | pub treasury_token_account: Account<'info, TokenAccount>, 145 | 146 | /// CHECK: Treasury PDA 147 | #[account( 148 | seeds = [b"treasury", config.key().as_ref()], 149 | bump 150 | )] 151 | pub treasury: UncheckedAccount<'info>, 152 | 153 | #[account( 154 | init, 155 | payer = player, 156 | space = common::GameState::LEN, 157 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 158 | bump 159 | )] 160 | pub game_state: Account<'info, common::GameState>, 161 | 162 | pub token_program: Program<'info, Token>, 163 | pub system_program: Program<'info, System>, 164 | pub clock: Sysvar<'info, Clock>, 165 | } 166 | 167 | #[derive(Accounts)] 168 | pub struct Pause<'info> { 169 | #[account( 170 | mut, 171 | seeds = [b"config", b"coinflip"], 172 | bump = config.bump 173 | )] 174 | pub config: Account<'info, common::GameConfig>, 175 | pub authority: Signer<'info>, 176 | } 177 | 178 | -------------------------------------------------------------------------------- /web3/evm/src/Lottery.sol: -------------------------------------------------------------------------------- 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 | 190 | -------------------------------------------------------------------------------- /web3/solana/programs/dice/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Dice1111111111111111111111111111111111"); 5 | 6 | #[program] 7 | pub mod dice { 8 | use super::*; 9 | 10 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 11 | let config = &mut ctx.accounts.config; 12 | config.authority = ctx.accounts.authority.key(); 13 | config.treasury = ctx.accounts.treasury.key(); 14 | config.min_bet = min_bet; 15 | config.max_bet = max_bet; 16 | config.house_edge_bps = house_edge_bps; 17 | config.paused = false; 18 | config.bump = ctx.bumps.config; 19 | Ok(()) 20 | } 21 | 22 | pub fn play(ctx: Context, bet_amount: u64, target: u8, roll_under: bool) -> Result<()> { 23 | let config = &ctx.accounts.config; 24 | require!(!config.paused, common::CasinoError::GameNotActive); 25 | require!(target > 0 && target < 100, common::CasinoError::InvalidBetAmount); 26 | 27 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 28 | 29 | // Transfer bet amount from player to treasury 30 | let cpi_accounts = Transfer { 31 | from: ctx.accounts.player_token_account.to_account_info(), 32 | to: ctx.accounts.treasury_token_account.to_account_info(), 33 | authority: ctx.accounts.player.to_account_info(), 34 | }; 35 | let cpi_program = ctx.accounts.token_program.to_account_info(); 36 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 37 | token::transfer(cpi_ctx, bet_amount)?; 38 | 39 | // Generate random roll (1-100) 40 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 41 | let roll = common::generate_random_from_seed(&seed, 99) + 1; // 1-100 42 | 43 | let game_state = &mut ctx.accounts.game_state; 44 | game_state.player = ctx.accounts.player.key(); 45 | game_state.bet_amount = bet_amount; 46 | game_state.game_id = Clock::get()?.unix_timestamp as u64; 47 | game_state.timestamp = Clock::get()?.unix_timestamp; 48 | game_state.settled = false; 49 | game_state.result = Some(roll as u64); 50 | game_state.bump = ctx.bumps.game_state; 51 | 52 | // Calculate win condition and multiplier 53 | let won = if roll_under { 54 | roll < target 55 | } else { 56 | roll > target 57 | }; 58 | 59 | if won { 60 | // Calculate multiplier based on probability 61 | let probability = if roll_under { 62 | (target - 1) as u64 63 | } else { 64 | (100 - target) as u64 65 | }; 66 | 67 | // Multiplier = 10000 / probability (with house edge) 68 | let multiplier = (10000u64) 69 | .checked_mul(10000) 70 | .and_then(|x| x.checked_div(probability)) 71 | .ok_or(common::CasinoError::MathOverflow)?; 72 | 73 | let payout = common::calculate_payout(bet_amount, multiplier, config.house_edge_bps)?; 74 | game_state.payout = Some(payout); 75 | 76 | // Transfer payout to player 77 | let seeds = &[ 78 | b"treasury", 79 | ctx.accounts.config.to_account_info().key.as_ref(), 80 | &[ctx.accounts.config.bump], 81 | ]; 82 | let signer = &[&seeds[..]]; 83 | 84 | let cpi_accounts = Transfer { 85 | from: ctx.accounts.treasury_token_account.to_account_info(), 86 | to: ctx.accounts.player_token_account.to_account_info(), 87 | authority: ctx.accounts.config.to_account_info(), 88 | }; 89 | let cpi_program = ctx.accounts.token_program.to_account_info(); 90 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 91 | token::transfer(cpi_ctx, payout)?; 92 | } else { 93 | game_state.payout = Some(0); 94 | } 95 | 96 | game_state.settled = true; 97 | Ok(()) 98 | } 99 | 100 | pub fn pause(ctx: Context) -> Result<()> { 101 | let config = &mut ctx.accounts.config; 102 | require!( 103 | ctx.accounts.authority.key() == config.authority, 104 | common::CasinoError::Unauthorized 105 | ); 106 | config.paused = true; 107 | Ok(()) 108 | } 109 | 110 | pub fn unpause(ctx: Context) -> Result<()> { 111 | let config = &mut ctx.accounts.config; 112 | require!( 113 | ctx.accounts.authority.key() == config.authority, 114 | common::CasinoError::Unauthorized 115 | ); 116 | config.paused = false; 117 | Ok(()) 118 | } 119 | } 120 | 121 | #[derive(Accounts)] 122 | pub struct Initialize<'info> { 123 | #[account( 124 | init, 125 | payer = authority, 126 | space = common::GameConfig::LEN, 127 | seeds = [b"config", b"dice"], 128 | bump 129 | )] 130 | pub config: Account<'info, common::GameConfig>, 131 | 132 | #[account(mut)] 133 | pub authority: Signer<'info>, 134 | 135 | /// CHECK: Treasury account 136 | pub treasury: UncheckedAccount<'info>, 137 | 138 | pub system_program: Program<'info, System>, 139 | } 140 | 141 | #[derive(Accounts)] 142 | pub struct Play<'info> { 143 | #[account( 144 | seeds = [b"config", b"dice"], 145 | bump = config.bump 146 | )] 147 | pub config: Account<'info, common::GameConfig>, 148 | 149 | #[account(mut)] 150 | pub player: Signer<'info>, 151 | 152 | #[account( 153 | mut, 154 | constraint = player_token_account.owner == player.key() 155 | )] 156 | pub player_token_account: Account<'info, TokenAccount>, 157 | 158 | #[account(mut)] 159 | pub treasury_token_account: Account<'info, TokenAccount>, 160 | 161 | /// CHECK: Treasury PDA 162 | #[account( 163 | seeds = [b"treasury", config.key().as_ref()], 164 | bump 165 | )] 166 | pub treasury: UncheckedAccount<'info>, 167 | 168 | #[account( 169 | init, 170 | payer = player, 171 | space = common::GameState::LEN, 172 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 173 | bump 174 | )] 175 | pub game_state: Account<'info, common::GameState>, 176 | 177 | pub token_program: Program<'info, Token>, 178 | pub system_program: Program<'info, System>, 179 | pub clock: Sysvar<'info, Clock>, 180 | } 181 | 182 | #[derive(Accounts)] 183 | pub struct Pause<'info> { 184 | #[account( 185 | mut, 186 | seeds = [b"config", b"dice"], 187 | bump = config.bump 188 | )] 189 | pub config: Account<'info, common::GameConfig>, 190 | pub authority: Signer<'info>, 191 | } 192 | 193 | -------------------------------------------------------------------------------- /web3/solana/programs/slots/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Slots11111111111111111111111111111111"); 5 | 6 | #[program] 7 | pub mod slots { 8 | use super::*; 9 | 10 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 11 | let config = &mut ctx.accounts.config; 12 | config.authority = ctx.accounts.authority.key(); 13 | config.treasury = ctx.accounts.treasury.key(); 14 | config.min_bet = min_bet; 15 | config.max_bet = max_bet; 16 | config.house_edge_bps = house_edge_bps; 17 | config.paused = false; 18 | config.bump = ctx.bumps.config; 19 | Ok(()) 20 | } 21 | 22 | pub fn spin(ctx: Context, bet_amount: u64) -> Result<()> { 23 | let config = &ctx.accounts.config; 24 | require!(!config.paused, common::CasinoError::GameNotActive); 25 | 26 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 27 | 28 | // Transfer bet amount from player to treasury 29 | let cpi_accounts = Transfer { 30 | from: ctx.accounts.player_token_account.to_account_info(), 31 | to: ctx.accounts.treasury_token_account.to_account_info(), 32 | authority: ctx.accounts.player.to_account_info(), 33 | }; 34 | let cpi_program = ctx.accounts.token_program.to_account_info(); 35 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 36 | token::transfer(cpi_ctx, bet_amount)?; 37 | 38 | // Generate 3 random symbols (0-6: Cherry, Lemon, Orange, Plum, Bell, Bar, Seven) 39 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 40 | let reel1 = common::generate_random_from_seed(&seed, 6); 41 | let reel2 = common::generate_random_from_seed(&[seed[0].wrapping_add(1)], 6); 42 | let reel3 = common::generate_random_from_seed(&[seed[0].wrapping_add(2)], 6); 43 | 44 | // Calculate payout based on symbol combinations 45 | let multiplier_bps = calculate_payout_multiplier(reel1, reel2, reel3); 46 | 47 | let game_state = &mut ctx.accounts.game_state; 48 | game_state.player = ctx.accounts.player.key(); 49 | game_state.bet_amount = bet_amount; 50 | game_state.game_id = Clock::get()?.unix_timestamp as u64; 51 | game_state.timestamp = Clock::get()?.unix_timestamp; 52 | game_state.settled = false; 53 | game_state.result = Some((reel1 << 16) | (reel2 << 8) | reel3); // Pack reels into u64 54 | game_state.bump = ctx.bumps.game_state; 55 | 56 | let payout = common::calculate_payout(bet_amount, multiplier_bps, config.house_edge_bps)?; 57 | game_state.payout = Some(payout); 58 | 59 | if payout > 0 { 60 | // Transfer payout to player 61 | let seeds = &[ 62 | b"treasury", 63 | ctx.accounts.config.to_account_info().key.as_ref(), 64 | &[ctx.accounts.config.bump], 65 | ]; 66 | let signer = &[&seeds[..]]; 67 | 68 | let cpi_accounts = Transfer { 69 | from: ctx.accounts.treasury_token_account.to_account_info(), 70 | to: ctx.accounts.player_token_account.to_account_info(), 71 | authority: ctx.accounts.config.to_account_info(), 72 | }; 73 | let cpi_program = ctx.accounts.token_program.to_account_info(); 74 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 75 | token::transfer(cpi_ctx, payout)?; 76 | } 77 | 78 | game_state.settled = true; 79 | Ok(()) 80 | } 81 | 82 | fn calculate_payout_multiplier(reel1: u64, reel2: u64, reel3: u64) -> u64 { 83 | // Three of a kind 84 | if reel1 == reel2 && reel2 == reel3 { 85 | match reel1 { 86 | 6 => 250000, // Three Sevens = 25x 87 | 5 => 100000, // Three Bars = 10x 88 | 4 => 50000, // Three Bells = 5x 89 | _ => 20000, // Three others = 2x 90 | } 91 | } 92 | // Two of a kind 93 | else if reel1 == reel2 || reel2 == reel3 || reel1 == reel3 { 94 | 10000 // 1x 95 | } 96 | // No match 97 | else { 98 | 0 99 | } 100 | } 101 | 102 | pub fn pause(ctx: Context) -> Result<()> { 103 | let config = &mut ctx.accounts.config; 104 | require!( 105 | ctx.accounts.authority.key() == config.authority, 106 | common::CasinoError::Unauthorized 107 | ); 108 | config.paused = true; 109 | Ok(()) 110 | } 111 | 112 | pub fn unpause(ctx: Context) -> Result<()> { 113 | let config = &mut ctx.accounts.config; 114 | require!( 115 | ctx.accounts.authority.key() == config.authority, 116 | common::CasinoError::Unauthorized 117 | ); 118 | config.paused = false; 119 | Ok(()) 120 | } 121 | } 122 | 123 | #[derive(Accounts)] 124 | pub struct Initialize<'info> { 125 | #[account( 126 | init, 127 | payer = authority, 128 | space = common::GameConfig::LEN, 129 | seeds = [b"config", b"slots"], 130 | bump 131 | )] 132 | pub config: Account<'info, common::GameConfig>, 133 | 134 | #[account(mut)] 135 | pub authority: Signer<'info>, 136 | 137 | /// CHECK: Treasury account 138 | pub treasury: UncheckedAccount<'info>, 139 | 140 | pub system_program: Program<'info, System>, 141 | } 142 | 143 | #[derive(Accounts)] 144 | pub struct Spin<'info> { 145 | #[account( 146 | seeds = [b"config", b"slots"], 147 | bump = config.bump 148 | )] 149 | pub config: Account<'info, common::GameConfig>, 150 | 151 | #[account(mut)] 152 | pub player: Signer<'info>, 153 | 154 | #[account(mut)] 155 | pub player_token_account: Account<'info, TokenAccount>, 156 | 157 | #[account(mut)] 158 | pub treasury_token_account: Account<'info, TokenAccount>, 159 | 160 | /// CHECK: Treasury PDA 161 | #[account( 162 | seeds = [b"treasury", config.key().as_ref()], 163 | bump 164 | )] 165 | pub treasury: UncheckedAccount<'info>, 166 | 167 | #[account( 168 | init, 169 | payer = player, 170 | space = common::GameState::LEN, 171 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 172 | bump 173 | )] 174 | pub game_state: Account<'info, common::GameState>, 175 | 176 | pub token_program: Program<'info, Token>, 177 | pub system_program: Program<'info, System>, 178 | pub clock: Sysvar<'info, Clock>, 179 | } 180 | 181 | #[derive(Accounts)] 182 | pub struct Pause<'info> { 183 | #[account( 184 | mut, 185 | seeds = [b"config", b"slots"], 186 | bump = config.bump 187 | )] 188 | pub config: Account<'info, common::GameConfig>, 189 | pub authority: Signer<'info>, 190 | } 191 | 192 | -------------------------------------------------------------------------------- /web3/solana/programs/plinko/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Plinko1111111111111111111111111111111"); 5 | 6 | #[program] 7 | pub mod plinko { 8 | use super::*; 9 | 10 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 11 | let config = &mut ctx.accounts.config; 12 | config.authority = ctx.accounts.authority.key(); 13 | config.treasury = ctx.accounts.treasury.key(); 14 | config.min_bet = min_bet; 15 | config.max_bet = max_bet; 16 | config.house_edge_bps = house_edge_bps; 17 | config.paused = false; 18 | config.bump = ctx.bumps.config; 19 | Ok(()) 20 | } 21 | 22 | pub fn play(ctx: Context, bet_amount: u64, rows: u8) -> Result<()> { 23 | let config = &ctx.accounts.config; 24 | require!(!config.paused, common::CasinoError::GameNotActive); 25 | require!(rows >= 8 && rows <= 16, common::CasinoError::InvalidBetAmount); 26 | 27 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 28 | 29 | // Transfer bet amount from player to treasury 30 | let cpi_accounts = Transfer { 31 | from: ctx.accounts.player_token_account.to_account_info(), 32 | to: ctx.accounts.treasury_token_account.to_account_info(), 33 | authority: ctx.accounts.player.to_account_info(), 34 | }; 35 | let cpi_program = ctx.accounts.token_program.to_account_info(); 36 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 37 | token::transfer(cpi_ctx, bet_amount)?; 38 | 39 | // Simulate ball path using randomness 40 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 41 | let mut position: i32 = 0; // Center position 42 | 43 | // Each row, ball can go left (-1) or right (+1) 44 | for _ in 0..rows { 45 | let random = common::generate_random_from_seed(&seed, 1); 46 | position += if random == 0 { -1 } else { 1 }; 47 | } 48 | 49 | // Calculate multiplier based on final position 50 | // Center positions have higher multipliers 51 | let max_position = rows as i32; 52 | let multiplier_bps = calculate_multiplier(position, max_position)?; 53 | 54 | let game_state = &mut ctx.accounts.game_state; 55 | game_state.player = ctx.accounts.player.key(); 56 | game_state.bet_amount = bet_amount; 57 | game_state.game_id = Clock::get()?.unix_timestamp as u64; 58 | game_state.timestamp = Clock::get()?.unix_timestamp; 59 | game_state.settled = false; 60 | game_state.result = Some(position as u64); 61 | game_state.bump = ctx.bumps.game_state; 62 | 63 | let payout = common::calculate_payout(bet_amount, multiplier_bps, config.house_edge_bps)?; 64 | game_state.payout = Some(payout); 65 | 66 | // Transfer payout to player 67 | let seeds = &[ 68 | b"treasury", 69 | ctx.accounts.config.to_account_info().key.as_ref(), 70 | &[ctx.accounts.config.bump], 71 | ]; 72 | let signer = &[&seeds[..]]; 73 | 74 | let cpi_accounts = Transfer { 75 | from: ctx.accounts.treasury_token_account.to_account_info(), 76 | to: ctx.accounts.player_token_account.to_account_info(), 77 | authority: ctx.accounts.config.to_account_info(), 78 | }; 79 | let cpi_program = ctx.accounts.token_program.to_account_info(); 80 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 81 | token::transfer(cpi_ctx, payout)?; 82 | 83 | game_state.settled = true; 84 | Ok(()) 85 | } 86 | 87 | fn calculate_multiplier(position: i32, max_position: i32) -> Result { 88 | // Center = highest multiplier (up to 1000x = 10000000 bps) 89 | // Edges = lower multiplier 90 | let distance_from_center = position.abs(); 91 | let max_distance = max_position; 92 | 93 | // Multiplier decreases as distance from center increases 94 | let multiplier = if distance_from_center == 0 { 95 | 10000000 // 1000x at center 96 | } else { 97 | let reduction = (distance_from_center * 500000) / max_distance; // Reduce by distance 98 | (10000000u64) 99 | .checked_sub(reduction as u64) 100 | .unwrap_or(100000) // Minimum 10x 101 | }; 102 | 103 | Ok(multiplier) 104 | } 105 | 106 | pub fn pause(ctx: Context) -> Result<()> { 107 | let config = &mut ctx.accounts.config; 108 | require!( 109 | ctx.accounts.authority.key() == config.authority, 110 | common::CasinoError::Unauthorized 111 | ); 112 | config.paused = true; 113 | Ok(()) 114 | } 115 | 116 | pub fn unpause(ctx: Context) -> Result<()> { 117 | let config = &mut ctx.accounts.config; 118 | require!( 119 | ctx.accounts.authority.key() == config.authority, 120 | common::CasinoError::Unauthorized 121 | ); 122 | config.paused = false; 123 | Ok(()) 124 | } 125 | } 126 | 127 | #[derive(Accounts)] 128 | pub struct Initialize<'info> { 129 | #[account( 130 | init, 131 | payer = authority, 132 | space = common::GameConfig::LEN, 133 | seeds = [b"config", b"plinko"], 134 | bump 135 | )] 136 | pub config: Account<'info, common::GameConfig>, 137 | 138 | #[account(mut)] 139 | pub authority: Signer<'info>, 140 | 141 | /// CHECK: Treasury account 142 | pub treasury: UncheckedAccount<'info>, 143 | 144 | pub system_program: Program<'info, System>, 145 | } 146 | 147 | #[derive(Accounts)] 148 | pub struct Play<'info> { 149 | #[account( 150 | seeds = [b"config", b"plinko"], 151 | bump = config.bump 152 | )] 153 | pub config: Account<'info, common::GameConfig>, 154 | 155 | #[account(mut)] 156 | pub player: Signer<'info>, 157 | 158 | #[account(mut)] 159 | pub player_token_account: Account<'info, TokenAccount>, 160 | 161 | #[account(mut)] 162 | pub treasury_token_account: Account<'info, TokenAccount>, 163 | 164 | /// CHECK: Treasury PDA 165 | #[account( 166 | seeds = [b"treasury", config.key().as_ref()], 167 | bump 168 | )] 169 | pub treasury: UncheckedAccount<'info>, 170 | 171 | #[account( 172 | init, 173 | payer = player, 174 | space = common::GameState::LEN, 175 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 176 | bump 177 | )] 178 | pub game_state: Account<'info, common::GameState>, 179 | 180 | pub token_program: Program<'info, Token>, 181 | pub system_program: Program<'info, System>, 182 | pub clock: Sysvar<'info, Clock>, 183 | } 184 | 185 | #[derive(Accounts)] 186 | pub struct Pause<'info> { 187 | #[account( 188 | mut, 189 | seeds = [b"config", b"plinko"], 190 | bump = config.bump 191 | )] 192 | pub config: Account<'info, common::GameConfig>, 193 | pub authority: Signer<'info>, 194 | } 195 | 196 | -------------------------------------------------------------------------------- /web3/solana/programs/crash/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Crash111111111111111111111111111111111"); 5 | 6 | #[program] 7 | pub mod crash { 8 | use super::*; 9 | 10 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 11 | let config = &mut ctx.accounts.config; 12 | config.authority = ctx.accounts.authority.key(); 13 | config.treasury = ctx.accounts.treasury.key(); 14 | config.min_bet = min_bet; 15 | config.max_bet = max_bet; 16 | config.house_edge_bps = house_edge_bps; 17 | config.paused = false; 18 | config.bump = ctx.bumps.config; 19 | Ok(()) 20 | } 21 | 22 | pub fn place_bet(ctx: Context, bet_amount: u64, auto_cashout: Option) -> Result<()> { 23 | let config = &ctx.accounts.config; 24 | require!(!config.paused, common::CasinoError::GameNotActive); 25 | 26 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 27 | 28 | // Transfer bet amount from player to treasury 29 | let cpi_accounts = Transfer { 30 | from: ctx.accounts.player_token_account.to_account_info(), 31 | to: ctx.accounts.treasury_token_account.to_account_info(), 32 | authority: ctx.accounts.player.to_account_info(), 33 | }; 34 | let cpi_program = ctx.accounts.token_program.to_account_info(); 35 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 36 | token::transfer(cpi_ctx, bet_amount)?; 37 | 38 | let game_state = &mut ctx.accounts.game_state; 39 | game_state.player = ctx.accounts.player.key(); 40 | game_state.bet_amount = bet_amount; 41 | game_state.game_id = Clock::get()?.unix_timestamp as u64; 42 | game_state.timestamp = Clock::get()?.unix_timestamp; 43 | game_state.settled = false; 44 | game_state.bump = ctx.bumps.game_state; 45 | 46 | // Store auto_cashout multiplier (if provided) in result field 47 | game_state.result = auto_cashout; 48 | 49 | Ok(()) 50 | } 51 | 52 | pub fn cashout(ctx: Context, current_multiplier: u64) -> Result<()> { 53 | let game_state = &ctx.accounts.game_state; 54 | require!(!game_state.settled, common::CasinoError::GameAlreadySettled); 55 | require!( 56 | game_state.player == ctx.accounts.player.key(), 57 | common::CasinoError::Unauthorized 58 | ); 59 | 60 | let config = &ctx.accounts.config; 61 | 62 | // Calculate payout based on current multiplier 63 | let multiplier_bps = current_multiplier * 100; // Convert to basis points 64 | let payout = common::calculate_payout(game_state.bet_amount, multiplier_bps, config.house_edge_bps)?; 65 | 66 | // Update game state 67 | let game_state = &mut ctx.accounts.game_state; 68 | game_state.settled = true; 69 | game_state.result = Some(current_multiplier); 70 | game_state.payout = Some(payout); 71 | 72 | // Transfer payout to player 73 | let seeds = &[ 74 | b"treasury", 75 | ctx.accounts.config.to_account_info().key.as_ref(), 76 | &[ctx.accounts.config.bump], 77 | ]; 78 | let signer = &[&seeds[..]]; 79 | 80 | let cpi_accounts = Transfer { 81 | from: ctx.accounts.treasury_token_account.to_account_info(), 82 | to: ctx.accounts.player_token_account.to_account_info(), 83 | authority: ctx.accounts.config.to_account_info(), 84 | }; 85 | let cpi_program = ctx.accounts.token_program.to_account_info(); 86 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 87 | token::transfer(cpi_ctx, payout)?; 88 | 89 | Ok(()) 90 | } 91 | 92 | pub fn settle_crashed(ctx: Context) -> Result<()> { 93 | let game_state = &mut ctx.accounts.game_state; 94 | require!(!game_state.settled, common::CasinoError::GameAlreadySettled); 95 | 96 | game_state.settled = true; 97 | game_state.payout = Some(0); // Player loses on crash 98 | 99 | Ok(()) 100 | } 101 | 102 | pub fn pause(ctx: Context) -> Result<()> { 103 | let config = &mut ctx.accounts.config; 104 | require!( 105 | ctx.accounts.authority.key() == config.authority, 106 | common::CasinoError::Unauthorized 107 | ); 108 | config.paused = true; 109 | Ok(()) 110 | } 111 | 112 | pub fn unpause(ctx: Context) -> Result<()> { 113 | let config = &mut ctx.accounts.config; 114 | require!( 115 | ctx.accounts.authority.key() == config.authority, 116 | common::CasinoError::Unauthorized 117 | ); 118 | config.paused = false; 119 | Ok(()) 120 | } 121 | } 122 | 123 | #[derive(Accounts)] 124 | pub struct Initialize<'info> { 125 | #[account( 126 | init, 127 | payer = authority, 128 | space = common::GameConfig::LEN, 129 | seeds = [b"config", b"crash"], 130 | bump 131 | )] 132 | pub config: Account<'info, common::GameConfig>, 133 | 134 | #[account(mut)] 135 | pub authority: Signer<'info>, 136 | 137 | /// CHECK: Treasury account 138 | pub treasury: UncheckedAccount<'info>, 139 | 140 | pub system_program: Program<'info, System>, 141 | } 142 | 143 | #[derive(Accounts)] 144 | pub struct PlaceBet<'info> { 145 | #[account( 146 | seeds = [b"config", b"crash"], 147 | bump = config.bump 148 | )] 149 | pub config: Account<'info, common::GameConfig>, 150 | 151 | #[account(mut)] 152 | pub player: Signer<'info>, 153 | 154 | #[account(mut)] 155 | pub player_token_account: Account<'info, TokenAccount>, 156 | 157 | #[account(mut)] 158 | pub treasury_token_account: Account<'info, TokenAccount>, 159 | 160 | /// CHECK: Treasury PDA 161 | #[account( 162 | seeds = [b"treasury", config.key().as_ref()], 163 | bump 164 | )] 165 | pub treasury: UncheckedAccount<'info>, 166 | 167 | #[account( 168 | init, 169 | payer = player, 170 | space = common::GameState::LEN, 171 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 172 | bump 173 | )] 174 | pub game_state: Account<'info, common::GameState>, 175 | 176 | pub token_program: Program<'info, Token>, 177 | pub system_program: Program<'info, System>, 178 | pub clock: Sysvar<'info, Clock>, 179 | } 180 | 181 | #[derive(Accounts)] 182 | pub struct Cashout<'info> { 183 | #[account( 184 | seeds = [b"config", b"crash"], 185 | bump = config.bump 186 | )] 187 | pub config: Account<'info, common::GameConfig>, 188 | 189 | #[account(mut)] 190 | pub player: Signer<'info>, 191 | 192 | #[account(mut)] 193 | pub player_token_account: Account<'info, TokenAccount>, 194 | 195 | #[account(mut)] 196 | pub treasury_token_account: Account<'info, TokenAccount>, 197 | 198 | /// CHECK: Treasury PDA 199 | #[account( 200 | seeds = [b"treasury", config.key().as_ref()], 201 | bump 202 | )] 203 | pub treasury: UncheckedAccount<'info>, 204 | 205 | #[account(mut)] 206 | pub game_state: Account<'info, common::GameState>, 207 | 208 | pub token_program: Program<'info, Token>, 209 | } 210 | 211 | #[derive(Accounts)] 212 | pub struct SettleCrashed<'info> { 213 | #[account(mut)] 214 | pub game_state: Account<'info, common::GameState>, 215 | } 216 | 217 | #[derive(Accounts)] 218 | pub struct Pause<'info> { 219 | #[account( 220 | mut, 221 | seeds = [b"config", b"crash"], 222 | bump = config.bump 223 | )] 224 | pub config: Account<'info, common::GameConfig>, 225 | pub authority: Signer<'info>, 226 | } 227 | 228 | -------------------------------------------------------------------------------- /web3/solana/programs/lottery/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Lottery1111111111111111111111111111"); 5 | 6 | #[account] 7 | pub struct LotteryTicket { 8 | pub player: Pubkey, 9 | pub numbers: [u8; 6], 10 | pub bet_amount: u64, 11 | pub draw_id: u64, 12 | pub timestamp: i64, 13 | pub bump: u8, 14 | } 15 | 16 | impl LotteryTicket { 17 | pub const LEN: usize = 8 + 32 + 6 + 8 + 8 + 8 + 1; 18 | } 19 | 20 | #[account] 21 | pub struct LotteryDraw { 22 | pub draw_id: u64, 23 | pub winning_numbers: [u8; 6], 24 | pub prize_pool: u64, 25 | pub tickets: u64, 26 | pub drawn: bool, 27 | pub timestamp: i64, 28 | pub bump: u8, 29 | } 30 | 31 | impl LotteryDraw { 32 | pub const LEN: usize = 8 + 8 + 6 + 8 + 8 + 1 + 8 + 1; 33 | } 34 | 35 | #[program] 36 | pub mod lottery { 37 | use super::*; 38 | 39 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64) -> Result<()> { 40 | let config = &mut ctx.accounts.config; 41 | config.authority = ctx.accounts.authority.key(); 42 | config.treasury = ctx.accounts.treasury.key(); 43 | config.min_bet = min_bet; 44 | config.max_bet = max_bet; 45 | config.house_edge_bps = 0; // No house edge for lottery 46 | config.paused = false; 47 | config.bump = ctx.bumps.config; 48 | Ok(()) 49 | } 50 | 51 | pub fn buy_ticket(ctx: Context, bet_amount: u64, numbers: [u8; 6], draw_id: u64) -> Result<()> { 52 | let config = &ctx.accounts.config; 53 | require!(!config.paused, common::CasinoError::GameNotActive); 54 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 55 | 56 | // Validate numbers (1-49) 57 | for &num in &numbers { 58 | require!(num >= 1 && num <= 49, common::CasinoError::InvalidBetAmount); 59 | } 60 | 61 | // Transfer bet to prize pool 62 | let cpi_accounts = Transfer { 63 | from: ctx.accounts.player_token_account.to_account_info(), 64 | to: ctx.accounts.pool_token_account.to_account_info(), 65 | authority: ctx.accounts.player.to_account_info(), 66 | }; 67 | let cpi_program = ctx.accounts.token_program.to_account_info(); 68 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 69 | token::transfer(cpi_ctx, bet_amount)?; 70 | 71 | // Update draw pool 72 | let draw = &mut ctx.accounts.draw; 73 | draw.prize_pool = draw.prize_pool 74 | .checked_add(bet_amount) 75 | .ok_or(common::CasinoError::MathOverflow)?; 76 | draw.tickets = draw.tickets 77 | .checked_add(1) 78 | .ok_or(common::CasinoError::MathOverflow)?; 79 | 80 | // Create ticket 81 | let ticket = &mut ctx.accounts.ticket; 82 | ticket.player = ctx.accounts.player.key(); 83 | ticket.numbers = numbers; 84 | ticket.bet_amount = bet_amount; 85 | ticket.draw_id = draw_id; 86 | ticket.timestamp = Clock::get()?.unix_timestamp; 87 | ticket.bump = ctx.bumps.ticket; 88 | 89 | Ok(()) 90 | } 91 | 92 | pub fn draw_numbers(ctx: Context) -> Result<()> { 93 | let draw = &mut ctx.accounts.draw; 94 | require!(!draw.drawn, common::CasinoError::GameAlreadySettled); 95 | 96 | // Generate 6 random numbers (1-49) 97 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 98 | let mut numbers = [0u8; 6]; 99 | let mut used = [false; 50]; 100 | 101 | for i in 0..6 { 102 | let mut num; 103 | loop { 104 | num = (common::generate_random_from_seed(&[seed[i]], 48) + 1) as u8; 105 | if !used[num as usize] { 106 | used[num as usize] = true; 107 | break; 108 | } 109 | } 110 | numbers[i] = num; 111 | } 112 | 113 | numbers.sort(); 114 | draw.winning_numbers = numbers; 115 | draw.drawn = true; 116 | draw.timestamp = Clock::get()?.unix_timestamp; 117 | 118 | Ok(()) 119 | } 120 | 121 | pub fn claim_prize(ctx: Context, matching_numbers: u8) -> Result<()> { 122 | let ticket = &ctx.accounts.ticket; 123 | let draw = &ctx.accounts.draw; 124 | require!(draw.drawn, common::CasinoError::InvalidGameState); 125 | require!(ticket.player == ctx.accounts.player.key(), common::CasinoError::Unauthorized); 126 | 127 | // Calculate matches 128 | let mut matches = 0; 129 | for &num in &ticket.numbers { 130 | if draw.winning_numbers.contains(&num) { 131 | matches += 1; 132 | } 133 | } 134 | 135 | require!(matches == matching_numbers, common::CasinoError::InvalidBetAmount); 136 | 137 | // Calculate prize based on matches 138 | let prize = match matches { 139 | 6 => draw.prize_pool, // Jackpot 140 | 5 => draw.prize_pool / 10, 141 | 4 => draw.prize_pool / 100, 142 | _ => 0, 143 | }; 144 | 145 | if prize > 0 { 146 | let seeds = &[ 147 | b"pool", 148 | ctx.accounts.draw.to_account_info().key.as_ref(), 149 | &[draw.bump], 150 | ]; 151 | let signer = &[&seeds[..]]; 152 | 153 | let cpi_accounts = Transfer { 154 | from: ctx.accounts.pool_token_account.to_account_info(), 155 | to: ctx.accounts.player_token_account.to_account_info(), 156 | authority: ctx.accounts.draw.to_account_info(), 157 | }; 158 | let cpi_program = ctx.accounts.token_program.to_account_info(); 159 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 160 | token::transfer(cpi_ctx, prize)?; 161 | } 162 | 163 | Ok(()) 164 | } 165 | } 166 | 167 | #[derive(Accounts)] 168 | pub struct Initialize<'info> { 169 | #[account( 170 | init, 171 | payer = authority, 172 | space = common::GameConfig::LEN, 173 | seeds = [b"config", b"lottery"], 174 | bump 175 | )] 176 | pub config: Account<'info, common::GameConfig>, 177 | 178 | #[account(mut)] 179 | pub authority: Signer<'info>, 180 | 181 | /// CHECK: Treasury account 182 | pub treasury: UncheckedAccount<'info>, 183 | 184 | pub system_program: Program<'info, System>, 185 | } 186 | 187 | #[derive(Accounts)] 188 | pub struct BuyTicket<'info> { 189 | #[account( 190 | seeds = [b"config", b"lottery"], 191 | bump = config.bump 192 | )] 193 | pub config: Account<'info, common::GameConfig>, 194 | 195 | #[account(mut)] 196 | pub player: Signer<'info>, 197 | 198 | #[account(mut)] 199 | pub player_token_account: Account<'info, TokenAccount>, 200 | 201 | #[account(mut)] 202 | pub pool_token_account: Account<'info, TokenAccount>, 203 | 204 | #[account( 205 | init_if_needed, 206 | payer = player, 207 | space = LotteryDraw::LEN, 208 | seeds = [b"draw", &draw_id.to_le_bytes()], 209 | bump 210 | )] 211 | pub draw: Account<'info, LotteryDraw>, 212 | 213 | #[account( 214 | init, 215 | payer = player, 216 | space = LotteryTicket::LEN, 217 | seeds = [b"ticket", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 218 | bump 219 | )] 220 | pub ticket: Account<'info, LotteryTicket>, 221 | 222 | pub token_program: Program<'info, Token>, 223 | pub system_program: Program<'info, System>, 224 | pub clock: Sysvar<'info, Clock>, 225 | } 226 | 227 | #[derive(Accounts)] 228 | pub struct DrawNumbers<'info> { 229 | #[account(mut)] 230 | pub draw: Account<'info, LotteryDraw>, 231 | pub authority: Signer<'info>, 232 | pub clock: Sysvar<'info, Clock>, 233 | } 234 | 235 | #[derive(Accounts)] 236 | pub struct ClaimPrize<'info> { 237 | #[account(mut)] 238 | pub ticket: Account<'info, LotteryTicket>, 239 | 240 | #[account(mut)] 241 | pub draw: Account<'info, LotteryDraw>, 242 | 243 | #[account(mut)] 244 | pub player: Signer<'info>, 245 | 246 | #[account(mut)] 247 | pub player_token_account: Account<'info, TokenAccount>, 248 | 249 | #[account(mut)] 250 | pub pool_token_account: Account<'info, TokenAccount>, 251 | 252 | pub token_program: Program<'info, Token>, 253 | } 254 | 255 | -------------------------------------------------------------------------------- /web3/solana/programs/blackjack/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | declare_id!("Blackjack1111111111111111111111111"); 5 | 6 | #[account] 7 | pub struct BlackjackGame { 8 | pub player: Pubkey, 9 | pub bet_amount: u64, 10 | pub player_cards: Vec, // Card values (1-13, suit encoded in upper bits) 11 | pub dealer_cards: Vec, 12 | pub player_score: u8, 13 | pub dealer_score: u8, 14 | pub game_state: u8, // 0=betting, 1=playing, 2=settled 15 | pub timestamp: i64, 16 | pub bump: u8, 17 | } 18 | 19 | impl BlackjackGame { 20 | pub const LEN: usize = 8 + 32 + 8 + 4 + 4 + 1 + 1 + 1 + 8 + 1 + 200; // Extra space for vectors 21 | } 22 | 23 | #[program] 24 | pub mod blackjack { 25 | use super::*; 26 | 27 | pub fn initialize(ctx: Context, min_bet: u64, max_bet: u64, house_edge_bps: u16) -> Result<()> { 28 | let config = &mut ctx.accounts.config; 29 | config.authority = ctx.accounts.authority.key(); 30 | config.treasury = ctx.accounts.treasury.key(); 31 | config.min_bet = min_bet; 32 | config.max_bet = max_bet; 33 | config.house_edge_bps = house_edge_bps; 34 | config.paused = false; 35 | config.bump = ctx.bumps.config; 36 | Ok(()) 37 | } 38 | 39 | pub fn place_bet(ctx: Context, bet_amount: u64) -> Result<()> { 40 | let config = &ctx.accounts.config; 41 | require!(!config.paused, common::CasinoError::GameNotActive); 42 | common::validate_bet(bet_amount, config.min_bet, config.max_bet)?; 43 | 44 | // Transfer bet 45 | let cpi_accounts = Transfer { 46 | from: ctx.accounts.player_token_account.to_account_info(), 47 | to: ctx.accounts.treasury_token_account.to_account_info(), 48 | authority: ctx.accounts.player.to_account_info(), 49 | }; 50 | let cpi_program = ctx.accounts.token_program.to_account_info(); 51 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 52 | token::transfer(cpi_ctx, bet_amount)?; 53 | 54 | // Deal initial cards 55 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 56 | let player_card1 = (common::generate_random_from_seed(&seed, 12) + 1) as u8; 57 | let player_card2 = (common::generate_random_from_seed(&[seed[0].wrapping_add(1)], 12) + 1) as u8; 58 | let dealer_card1 = (common::generate_random_from_seed(&[seed[0].wrapping_add(2)], 12) + 1) as u8; 59 | 60 | let mut game = &mut ctx.accounts.game; 61 | game.player = ctx.accounts.player.key(); 62 | game.bet_amount = bet_amount; 63 | game.player_cards = vec![player_card1, player_card2]; 64 | game.dealer_cards = vec![dealer_card1]; 65 | game.player_score = calculate_score(&game.player_cards); 66 | game.dealer_score = calculate_score(&game.dealer_cards); 67 | game.game_state = 1; // Playing 68 | game.timestamp = Clock::get()?.unix_timestamp; 69 | game.bump = ctx.bumps.game; 70 | 71 | // Check for blackjack 72 | if game.player_score == 21 { 73 | game.game_state = 2; // Settled 74 | let payout = bet_amount 75 | .checked_mul(3) 76 | .and_then(|x| x.checked_div(2)) 77 | .ok_or(common::CasinoError::MathOverflow)?; // 3:2 payout 78 | settle_game(ctx, payout)?; 79 | } 80 | 81 | Ok(()) 82 | } 83 | 84 | pub fn hit(ctx: Context) -> Result<()> { 85 | let game = &mut ctx.accounts.game; 86 | require!(game.game_state == 1, common::CasinoError::InvalidGameState); 87 | require!(game.player == ctx.accounts.player.key(), common::CasinoError::Unauthorized); 88 | 89 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 90 | let new_card = (common::generate_random_from_seed(&seed, 12) + 1) as u8; 91 | game.player_cards.push(new_card); 92 | game.player_score = calculate_score(&game.player_cards); 93 | 94 | if game.player_score > 21 { 95 | game.game_state = 2; // Bust 96 | settle_game(ctx, 0)?; 97 | } 98 | 99 | Ok(()) 100 | } 101 | 102 | pub fn stand(ctx: Context) -> Result<()> { 103 | let game = &mut ctx.accounts.game; 104 | require!(game.game_state == 1, common::CasinoError::InvalidGameState); 105 | require!(game.player == ctx.accounts.player.key(), common::CasinoError::Unauthorized); 106 | 107 | // Dealer draws until 17+ 108 | let seed = Clock::get()?.unix_timestamp.to_le_bytes(); 109 | while game.dealer_score < 17 { 110 | let new_card = (common::generate_random_from_seed(&seed, 12) + 1) as u8; 111 | game.dealer_cards.push(new_card); 112 | game.dealer_score = calculate_score(&game.dealer_cards); 113 | } 114 | 115 | game.game_state = 2; 116 | 117 | // Determine winner 118 | let payout = if game.dealer_score > 21 || game.player_score > game.dealer_score { 119 | game.bet_amount * 2 // 1:1 payout 120 | } else if game.player_score == game.dealer_score { 121 | game.bet_amount // Push 122 | } else { 123 | 0 // Dealer wins 124 | }; 125 | 126 | settle_game(ctx, payout) 127 | } 128 | 129 | fn settle_game(ctx: Context, payout: u64) -> Result<()> { 130 | let game = &ctx.accounts.game; 131 | let config = &ctx.accounts.config; 132 | 133 | if payout > 0 { 134 | let seeds = &[ 135 | b"treasury", 136 | config.to_account_info().key.as_ref(), 137 | &[config.bump], 138 | ]; 139 | let signer = &[&seeds[..]]; 140 | 141 | let cpi_accounts = Transfer { 142 | from: ctx.accounts.treasury_token_account.to_account_info(), 143 | to: ctx.accounts.player_token_account.to_account_info(), 144 | authority: config.to_account_info(), 145 | }; 146 | let cpi_program = ctx.accounts.token_program.to_account_info(); 147 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); 148 | token::transfer(cpi_ctx, payout)?; 149 | } 150 | 151 | Ok(()) 152 | } 153 | 154 | fn calculate_score(cards: &[u8]) -> u8 { 155 | let mut score = 0; 156 | let mut aces = 0; 157 | 158 | for &card in cards { 159 | let value = card % 13; 160 | if value == 0 { 161 | aces += 1; 162 | score += 11; 163 | } else if value >= 10 { 164 | score += 10; 165 | } else { 166 | score += value + 1; 167 | } 168 | } 169 | 170 | while score > 21 && aces > 0 { 171 | score -= 10; 172 | aces -= 1; 173 | } 174 | 175 | score 176 | } 177 | } 178 | 179 | #[derive(Accounts)] 180 | pub struct Initialize<'info> { 181 | #[account( 182 | init, 183 | payer = authority, 184 | space = common::GameConfig::LEN, 185 | seeds = [b"config", b"blackjack"], 186 | bump 187 | )] 188 | pub config: Account<'info, common::GameConfig>, 189 | 190 | #[account(mut)] 191 | pub authority: Signer<'info>, 192 | 193 | /// CHECK: Treasury account 194 | pub treasury: UncheckedAccount<'info>, 195 | 196 | pub system_program: Program<'info, System>, 197 | } 198 | 199 | #[derive(Accounts)] 200 | pub struct PlaceBet<'info> { 201 | #[account( 202 | seeds = [b"config", b"blackjack"], 203 | bump = config.bump 204 | )] 205 | pub config: Account<'info, common::GameConfig>, 206 | 207 | #[account(mut)] 208 | pub player: Signer<'info>, 209 | 210 | #[account(mut)] 211 | pub player_token_account: Account<'info, TokenAccount>, 212 | 213 | #[account(mut)] 214 | pub treasury_token_account: Account<'info, TokenAccount>, 215 | 216 | #[account( 217 | init, 218 | payer = player, 219 | space = BlackjackGame::LEN, 220 | seeds = [b"game", player.key().as_ref(), &Clock::get()?.unix_timestamp.to_le_bytes()], 221 | bump 222 | )] 223 | pub game: Account<'info, BlackjackGame>, 224 | 225 | pub token_program: Program<'info, Token>, 226 | pub system_program: Program<'info, System>, 227 | pub clock: Sysvar<'info, Clock>, 228 | } 229 | 230 | #[derive(Accounts)] 231 | pub struct Hit<'info> { 232 | #[account( 233 | seeds = [b"config", b"blackjack"], 234 | bump = config.bump 235 | )] 236 | pub config: Account<'info, common::GameConfig>, 237 | 238 | #[account(mut)] 239 | pub player: Signer<'info>, 240 | 241 | #[account(mut)] 242 | pub player_token_account: Account<'info, TokenAccount>, 243 | 244 | #[account(mut)] 245 | pub treasury_token_account: Account<'info, TokenAccount>, 246 | 247 | #[account(mut)] 248 | pub game: Account<'info, BlackjackGame>, 249 | 250 | pub token_program: Program<'info, Token>, 251 | pub clock: Sysvar<'info, Clock>, 252 | } 253 | 254 | #[derive(Accounts)] 255 | pub struct Stand<'info> { 256 | #[account( 257 | seeds = [b"config", b"blackjack"], 258 | bump = config.bump 259 | )] 260 | pub config: Account<'info, common::GameConfig>, 261 | 262 | #[account(mut)] 263 | pub player: Signer<'info>, 264 | 265 | #[account(mut)] 266 | pub player_token_account: Account<'info, TokenAccount>, 267 | 268 | #[account(mut)] 269 | pub treasury_token_account: Account<'info, TokenAccount>, 270 | 271 | #[account(mut)] 272 | pub game: Account<'info, BlackjackGame>, 273 | 274 | pub token_program: Program<'info, Token>, 275 | pub clock: Sysvar<'info, Clock>, 276 | } 277 | 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎰 Multi-Chain Casino Platform 2 | 3 |
4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | [![Solana](https://img.shields.io/badge/Solana-14F195?logo=solana&logoColor=white)](https://solana.com) 7 | [![Ethereum](https://img.shields.io/badge/Ethereum-3C3C3D?logo=ethereum&logoColor=white)](https://ethereum.org) 8 | [![Stars](https://img.shields.io/github/stars/LaChance-Lab/Multi-Chain-Casino-Games?style=social)](https://github.com/LaChance-Lab/Multi-Chain-Casino-Games/stargazers) 9 | [![Forks](https://img.shields.io/github/forks/LaChance-Lab/Multi-Chain-Casino-Games?style=social)](https://github.com/LaChance-Lab/Multi-Chain-Casino-Games/network/members) 10 | [![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?logo=telegram&logoColor=white)](https://t.me/lachancelab) 11 | 12 | **10 Classic Casino Games • Multi-Chain Support • Provably Fair • Telegram Integration** 13 | 14 | [🎮 Games](#-games-suite) • [🔗 Chains](#-supported-chains) • [🛠️ Tech Stack](#-technology-stack) • [🚀 Quick Start](#-quick-start) • [📱 Contact](#-contact) 15 | 16 |
17 | 18 | https://github.com/user-attachments/assets/fe07fb7c-e5da-4bd8-bb4d-2d98565a9537 19 | 20 | --- 21 | 22 | ## 📋 Table of Contents 23 | 24 | - [Overview](#-overview) 25 | - [Games Suite](#-games-suite) 26 | - [Supported Chains](#-supported-chains) 27 | - [Technology Stack](#-technology-stack) 28 | - [Core Features](#-core-features) 29 | - [Security Features](#️-security-features) 30 | - [Token Integration](#-token-integration) 31 | - [Telegram Bot Features](#-telegram-bot-features) 32 | - [Multi-Chain Bridge](#-multi-chain-bridge) 33 | - [Contact](#-contact) 34 | 35 | --- 36 | 37 | ## 🌟 Overview 38 | 39 | A fully decentralized, provably fair casino platform supporting Solana, EVM-compatible, Bitcoin, Sui, Cardano Chains. Play classic casino games with transparent, verifiable outcomes powered by VRF (Verifiable Random Function) technology. (Built 10+ Casino Game Platforms) 40 | 41 | **Built for:** 42 | - 🎲 Casino operators looking for multi-chain support 43 | - 🏦 DeFi protocols integrating gaming features 44 | - 👥 Communities wanting to run their own casino 45 | - 💼 Token projects seeking utility and engagement 46 | 47 | --- 48 | ## 🏗️ Architecture 49 | 50 | ``` 51 | ┌────────────────────────────────────────────────┐ 52 | │ Frontend (Next.js) │ 53 | │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 54 | │ │ Phantom │ │ MetaMask │ │ Telegram │ │ 55 | │ └──────────┘ └──────────┘ └──────────┘ │ 56 | └────────────────────────────────────────────────┘ 57 | │ 58 | ┌─────────────┴─────────────┐ 59 | │ │ 60 | ┌───────▼────────┐ ┌───────▼────────┐ 61 | │ Solana Chain │ │ EVM Chains │ 62 | │ │ │ │ 63 | │ ┌────────────┐ │ │ ┌────────────┐ │ 64 | │ │ ORAO VRF │ │ │ │Chainlink │ │ 65 | │ └────────────┘ │ │ │ VRF │ │ 66 | │ │ │ └────────────┘ │ 67 | │ ┌────────────┐ │ │ ┌────────────┐ │ 68 | │ │Game │ │ │ │Game │ │ 69 | │ │Programs │ │ │ │Contracts │ │ 70 | │ └────────────┘ │ │ └────────────┘ │ 71 | └────────────────┘ └────────────────┘ 72 | ``` 73 | 74 | 75 | --- 76 | 77 | ## 🎮 Games Suite 78 | 79 | 80 | 81 | 91 | 101 | 102 | 103 | 104 | 115 | 125 | 126 | 127 | 128 | 139 | 150 | 151 | 152 | 153 | 163 | 173 | 174 | 175 | 176 | 186 | 196 | 197 |
82 | 83 | ### 🎯 Plinko 84 | Drop the ball and watch it bounce! 85 | - **Mechanics:** Ball path via VRF → multiplier 86 | - **Max Payout:** Up to 1000x 87 | - **Provably Fair:** ✅ 88 | - **Physics Engine:** Realistic bouncing animation 89 | 90 | 92 | 93 | ### 📈 Crash 94 | Cash out before it crashes! 95 | - **Mechanics:** Auto-cashout + VRF curve 96 | - **Max Multiplier:** Unlimited potential 97 | - **Provably Fair:** ✅ 98 | - **Live Stats:** Real-time crash history 99 | 100 |
105 | 106 | ### 🎲 Dice 107 | Roll under or over your target! 108 | - **Mechanics:** Under/Over + VRF 109 | - **Dynamic Multipliers:** Based on probability 110 | - **Animated Rolls:** Realistic physics 111 | - **Live Probability:** Real-time win chances 112 | - **Detailed History:** Visual indicators 113 | 114 | 116 | 117 | ### 💰 Jackpot 118 | Community progressive jackpot! 119 | - **Mechanics:** 5% rake → pool 120 | - **Prize Pool:** Grows with every bet 121 | - **Provably Fair:** ✅ 122 | - **Winner Selection:** Random VRF draw 123 | 124 |
129 | 130 | ### 🪙 Coin Flip 131 | Simple 50/50 chance! 132 | - **Payout:** 1.95x your bet 133 | - **Mechanics:** 50/50 + VRF 134 | - **Animation:** Realistic coin flip physics 135 | - **Instant Results:** Immediate settlement 136 | - **Enhanced Visuals:** History tracking 137 | 138 | 140 | 141 | ### 🎰 Slots 142 | Match 3 symbols to win big! 143 | - **Max Payout:** 25x your bet 144 | - **Features:** Immersive animations 145 | - **Sound Effects:** Spinning, stops, wins 146 | - **Jackpot Mode:** Confetti & flashing lights 147 | - **Payout Table:** Detailed odds display 148 | 149 |
154 | 155 | ### 🃏 Poker 156 | Texas Hold'em tournaments! 157 | - **Mode:** Multi-player tournaments 158 | - **Provably Fair:** ✅ 159 | - **Buy-ins:** Flexible stakes 160 | - **Prize Pools:** Tournament-based 161 | 162 | 164 | 165 | ### 🂡 Blackjack 166 | Beat the dealer to 21! 167 | - **Payout:** 3:2 on Blackjack 168 | - **Features:** Split, Double Down, Insurance 169 | - **Provably Fair:** ✅ 170 | - **Strategy Guide:** Included 171 | 172 |
177 | 178 | ### 🎡 Roulette 179 | Spin the wheel of fortune! 180 | - **Types:** European & American 181 | - **Bets:** Inside, Outside, Neighbors 182 | - **Provably Fair:** ✅ 183 | - **Live Animation:** Realistic wheel spin 184 | 185 | 187 | 188 | ### 🎟️ Lottery 189 | Draw your winning numbers! 190 | - **Mechanics:** Pick 6 numbers 191 | - **Jackpot:** Progressive prize pool 192 | - **Drawings:** Scheduled or instant 193 | - **Provably Fair:** ✅ 194 | 195 |
198 | 199 | --- 200 | 201 | ## 🔗 Supported Chains 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 |
BlockchainNetworkStatusTech Stack
🟣 SolanaMainnet Beta✅ LiveRust + Anchor
🔵 EthereumMainnet✅ LiveSolidity + Foundry
🔷 BaseMainnet✅ LiveSolidity + Hardhat
🔴 ArbitrumOne✅ LiveSolidity + Foundry
🟣 PolygonPoS✅ LiveSolidity + Hardhat
241 | 242 | --- 243 | 244 | ## 🛠️ Technology Stack 245 | 246 | ### Smart Contract Development 247 | 248 | | Platform | Languages | Frameworks | Testing | 249 | |----------|-----------|------------|---------| 250 | | **Solana** | Rust | Anchor | Anchor Test Suite | 251 | | **EVM Chains** | Solidity | Foundry, Hardhat | Forge, Mocha | 252 | 253 | ### Core Game Logic 254 | 255 | ```rust 256 | // Solana (Rust + Anchor) 257 | - VRF-based randomness generation 258 | - On-chain game state management 259 | - SPL token integration 260 | - Program-derived addresses (PDAs) 261 | ``` 262 | 263 | ```solidity 264 | // EVM (Solidity + Foundry/Hardhat) 265 | - Chainlink VRF integration 266 | - ERC-20 token support 267 | - Gas-optimized contracts 268 | - Upgradeable proxy patterns 269 | ``` 270 | 271 | ### Game Mechanics Breakdown 272 | 273 | | Game | Randomness Source | Payout Calculation | On-Chain State | 274 | |------|-------------------|-------------------|----------------| 275 | | 🎯 Plinko | VRF → Ball Path | Multiplier grid | Position + Result | 276 | | 📈 Crash | VRF → Curve | Time-based multiplier | Curve seed | 277 | | 🎲 Dice | VRF → Roll | Probability-based | Roll value | 278 | | 💰 Jackpot | VRF → Winner | Pool distribution | Pool size | 279 | | 🪙 CoinFlip | VRF → Side | 1.95x fixed | Flip result | 280 | | 🎰 Slots | VRF → Reels | Symbol matching | Reel positions | 281 | 282 | --- 283 | 284 | ## 🚀 Quick Start 285 | 286 | ### Prerequisites 287 | 288 | **Required:** 289 | - Node.js 18+ 290 | - Rust 1.70+ 291 | - Solana CLI 1.16+ 292 | - Anchor 0.29+ 293 | 294 | **For EVM:** 295 | - Foundry (recommended) OR Hardhat 296 | - MetaMask or similar wallet 297 | 298 | ### Installation 299 | 300 | #### 1️⃣ Clone Repository 301 | ```bash 302 | git clone https://github.com/LaChance-Lab/EVM-Solana-Casino-Games.git 303 | cd Multi-Chain-Casino-Games 304 | ``` 305 | 306 | #### 2️⃣ Solana Setup 307 | ```bash 308 | cd web3/solana 309 | npm install 310 | anchor build 311 | anchor test 312 | ``` 313 | 314 | #### 3️⃣ EVM Setup 315 | ```bash 316 | cd web3/evm 317 | forge install 318 | forge build 319 | forge test -vvv 320 | ``` 321 | 322 | #### 4️⃣ Frontend Setup 323 | ```bash 324 | cd frontend 325 | npm install 326 | npm run dev 327 | # Open http://localhost:3000 328 | ``` 329 | > Star ⭐ this repo to get notified whenever this repo is updated! 330 | 331 | ### Deploy to Testnet 332 | 333 | **Solana Devnet:** 334 | ```bash 335 | cd web3/solana 336 | anchor deploy --provider.cluster devnet 337 | ``` 338 | 339 | **Ethereum Sepolia:** 340 | ```bash 341 | cd web3/evm 342 | forge script script/Deploy.s.sol --rpc-url sepolia --broadcast --verify 343 | ``` 344 | 345 | --- 346 | 347 | ## 📂 Repository Structure 348 | 349 | ``` 350 | Multi-Chain-Casino-Games/ 351 | ├── 📄 README.md ← You are here 352 | ├── 📄 LICENSE ← MIT License 353 | ├── 📄 CONTRIBUTING.md ← Contribution guidelines 354 | ├── 📄 SECURITY.md ← Security policy 355 | │ 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 395 | │ 396 | ├── 📁 frontend/ ← Next.js frontend 397 | │ ├── src/ 398 | │ │ ├── components/ 399 | │ │ ├── pages/ 400 | │ │ └── lib/ 401 | │ ├── public/ 402 | │ └── package.json 403 | │ 404 | ├── 📁 telegram-bot/ ← Telegram integration 405 | │ ├── src/ 406 | │ └── package.json 407 | │ 408 | ├── 📁 docs/ ← Documentation 409 | │ ├── ARCHITECTURE.md 410 | │ ├── DEPLOYMENT.md 411 | │ ├── GAMES.md 412 | │ └── API.md 413 | │ 414 | └── 📁 examples/ ← Usage examples 415 | ├── solana/ 416 | └── evm/ 417 | ``` 418 | 419 | > **Status:** ✅ All smart contracts implemented and ready for deployment. 420 | 421 | 422 | ## ✨ Core Features 423 | 424 | ### 🎯 Provably Fair Gaming 425 | - **Verifiable Randomness:** VRF ensures true randomness 426 | - **Transparent Outcomes:** All results can be independently verified 427 | - **No House Edge Manipulation:** Smart contract enforced rules 428 | - **Audit Trail:** Complete game history on-chain 429 | 430 | ### 💎 Universal Token Support 431 | - **SPL Tokens:** Any Solana token supported 432 | - **ERC-20 Tokens:** Full EVM token compatibility 433 | - **Custom Pools:** Set your own liquidity and limits 434 | - **Dynamic Multipliers:** Auto-adjusted based on pool size 435 | 436 | ### 📊 Advanced Analytics 437 | - **Player Statistics:** Win/loss ratios, favorite games 438 | - **House Performance:** Revenue, payouts, edge 439 | - **Real-time Metrics:** Active players, total volume 440 | - **Historical Data:** Comprehensive reporting 441 | 442 | ### 🎨 Enhanced User Experience 443 | - **Realistic Animations:** Physics-based game visuals 444 | - **Sound Effects:** Immersive audio feedback 445 | - **Celebration Effects:** Win animations with confetti 446 | - **Responsive Design:** Mobile and desktop optimized 447 | 448 | --- 449 | 450 | ## 🛡️ Security Features 451 | 452 | ### Zero Trust Architecture 453 | 454 | 455 | 456 | 465 | 474 | 475 | 476 | 477 | 486 | 495 | 496 |
457 | 458 | #### 🔐 Treasury Protection 459 | - **Multi-signature Wallets:** Require multiple approvals 460 | - **Time-locks:** Delayed withdrawals for security 461 | - **Emergency Pause:** Circuit breaker for incidents 462 | - **Segregated Funds:** Player funds isolated 463 | 464 | 466 | 467 | #### 🚨 Anti-Cheat Systems 468 | - **Rate Limiting:** Prevent spam attacks 469 | - **Bet Size Limits:** Configurable maximums 470 | - **Pattern Detection:** AI-powered fraud detection 471 | - **IP Tracking:** Multi-account prevention 472 | 473 |
478 | 479 | #### 📈 Risk Management 480 | - **Automated Rebalancing:** Maintain healthy pools 481 | - **Dynamic Limits:** Adjust based on liquidity 482 | - **Reserve Requirements:** Ensure solvency 483 | - **Kelly Criterion:** Optimal bet sizing 484 | 485 | 487 | 488 | #### 🔍 Monitoring 489 | - **Real-time Alerts:** Suspicious activity notifications 490 | - **Audit Logs:** Complete transaction history 491 | - **Performance Metrics:** System health monitoring 492 | - **24/7 Surveillance:** Continuous security checks 493 | 494 |
497 | 498 | ### Professional Audits 499 | - ✅ Smart contract security audits 500 | - ✅ Penetration testing 501 | - ✅ Code review by security experts 502 | - ✅ Continuous monitoring and updates 503 | 504 | --- 505 | 506 | ## 💰 Token Integration 507 | 508 | ### Universal Compatibility 509 | 510 | ```typescript 511 | // Support for ANY token on supported chains 512 | - Solana: SPL tokens (USDC, USDT, custom tokens) 513 | - EVM: ERC-20 tokens (USDC, USDT, DAI, custom tokens) 514 | ``` 515 | 516 | ### Custom Liquidity Management 517 | 518 | | Feature | Description | Benefit | 519 | |---------|-------------|---------| 520 | | **Initial Liquidity** | Set starting pool size | Control risk exposure | 521 | | **Betting Limits** | Min/max bet configuration | Prevent whale manipulation | 522 | | **Dynamic Multipliers** | Auto-adjust payouts | Maintain house edge | 523 | | **Treasury Tools** | Built-in fund management | Easy operations | 524 | | **Profit Withdrawal** | Scheduled rake distribution | Automated revenue | 525 | 526 | ### Pool Configuration Example 527 | 528 | ```json 529 | { 530 | "token": "USDC", 531 | "initialLiquidity": "100000", 532 | "minBet": "1", 533 | "maxBet": "1000", 534 | "houseEdge": "2.5%", 535 | "maxPayout": "10000" 536 | } 537 | ``` 538 | 539 | --- 540 | 541 | ## 📱 Telegram Bot Features 542 | 543 | ### In-Chat Gaming Experience 544 | 545 | 546 | 547 | 560 | 572 | 573 |
548 | 549 | #### 🎮 Gaming Commands 550 | ``` 551 | /play - Start a game 552 | /balance - Check your balance 553 | /deposit - Get deposit address 554 | /withdraw - Withdraw funds 555 | /history - View game history 556 | /help - Command list 557 | ``` 558 | 559 | 561 | 562 | #### 🏆 Community Features 563 | ``` 564 | /leaderboard - Top players 565 | /bigwins - Recent big wins 566 | /stats - Global statistics 567 | /jackpot - Current jackpot size 568 | /house - House performance 569 | ``` 570 | 571 |
574 | 575 | ### Advanced Bot Features 576 | 577 | - **🎯 In-Group Gaming:** Play directly in Telegram groups 578 | - **💳 Wallet Management:** Non-custodial wallet integration 579 | - **📊 Live Leaderboards:** Real-time rankings and competitions 580 | - **⚡ Instant Payouts:** Automatic win settlements 581 | - **🔔 Notifications:** Win alerts, jackpot updates 582 | - **👥 Multiplayer:** Group tournaments and challenges 583 | - **🎁 Rewards:** Daily bonuses and loyalty programs 584 | - **📈 Analytics:** Personal statistics and insights 585 | 586 | --- 587 | 588 | ## 🌉 Multi-Chain Bridge 589 | 590 | ### Seamless Cross-Chain Experience 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 |
Bridge ProviderChains SupportedSpeedFeatures
WormholeSolana ↔ EVM~15 minToken bridging, NFTs
LayerZeroMulti-EVM~5 minOmnichain messaging
AxelarAll supported chains~10 minGeneral message passing
LI.FIAll supported chains~3 minBest route aggregation
624 | 625 | ### Bridge Features 626 | 627 | - **🔄 Automatic Routing:** Best path selection 628 | - **💰 Lowest Fees:** Cost optimization 629 | - **⚡ Fast Transfers:** Minimal wait times 630 | - **🔐 Secure:** Audited bridge protocols 631 | - **📱 User-Friendly:** One-click bridging 632 | - **💎 Asset Support:** Tokens and NFTs 633 | 634 | --- 635 | 636 | ## 🚧 Development Status 637 | 638 |
639 | 640 | | Component | Status | Progress | 641 | |-----------|--------|----------| 642 | | 🎮 Game Design | ✅ Complete | ████████████ 100% | 643 | | 🔧 Solana Contracts | 🔨 In Progress | ████████████ 100% | 644 | | 🔧 EVM Contracts | 🔨 In Progress | ████████████ 100% | 645 | | 🎨 Frontend | 🔨 In Progress |████████████ 100% | 646 | | 🤖 Telegram Bot | 🔜 Planned | ████░░░░░░░░ 20% | 647 | | 🔐 Security Audit | 🔜 Planned | ████████████ 100% | 648 | 649 | **Latest Update:** Nov 2025 650 | 651 |
652 | 653 | > 💡 **Want to contribute?** We're looking for developers! See [CONTRIBUTING.md](./CONTRIBUTING.md) 654 | 655 | --- 656 | 657 | ## 📱 Connect With Us 658 | 659 | ### 💼 Business & Partnerships 660 | 661 | **Looking to build your own casino platform?** 662 | 663 | We offer professional services: 664 | - 🎰 **White-label Solutions** - Launch your casino in weeks 665 | - 🔧 **Custom Game Development** - Unique games for your brand 666 | - 🌉 **Chain Integration** - Connect to any blockchain 667 | - 🎨 **UI/UX Design** - Beautiful, responsive interfaces 668 | - 🛡️ **Security Audits** - Professional smart contract audits 669 | - 📈 **Marketing & Launch** - Go-to-market strategy 670 | 671 | **Telegram:** [@lachancelab](https://t.me/lachancelab) 672 | 673 | --- 674 | 675 | ### 🤝 Open Source Collaboration 676 | 677 | Interested in collaboration or contributing? 678 | - 🔗 **Integrations** - Token/protocol partnerships 679 | - 🌉 **Bridges** - Cross-chain infrastructure 680 | - 🎮 **Platforms** - Gaming ecosystem partnerships 681 | - 💰 **DeFi** - Financial protocol integrations 682 | 683 | **Open an issue** or **join our Telegram** to discuss! 684 | 685 | --- 686 | 687 | ## ⚠️ Disclaimer 688 | 689 | **This platform is for entertainment purposes. Please gamble responsibly and comply with your local regulations. The house always has an edge—play for fun, not profit.** 690 | 691 | --- 692 | 693 | Made with ❤️ by LaChanceLab 694 | 695 | *Powered by Provably Fair Technology* 696 | 697 | 698 | --------------------------------------------------------------------------------