My very cool projects

Go back

Decentralized coinflip

Want to flip a coin to see who gets it all? Me neither. Still made it though!!
Let's users flip a coin against eachother to see who keeps all the money in the jar. Generates pseudo random numbers based on the transactions of both users, the contract's state & previous generated random numbers.

Flip-the-coin coin

So yeah, it seems like whenever I'm bored I keep on going back to creating gambling projects. Here's a new one: the flip-the-coin coin project (coinflip). This project has only 2 parts: the smart contract and the frontend.

omg pseudo random????
This contract will make use of pseudo random numbers instead of VRF or me just pushing a random number using a transaction. Why pay more for the transaction if it's just flipping a coin? - probably because it's safer. But frick it, it's just me having fun right now!!

So, just like the previous time, let's set some variables first

struct Game {
    address starter;
    uint starterAmount;
    uint starterInputSeed;
    uint starterSeed;

    address joiner;
    uint joinerAmount;
    uint joinerInputSeed;
    uint joinerSeed;

    uint result;
    uint closed;
    uint blockCreated;
}

uint gameCount = 0;
uint public fee = 1;
uint public minimumBet = 1 ether;
uint internet jackpotSeed = 0;
uint public totalWon = 0;

mapping(uint => Game) internal games;
uint[] internal openGames;

Soooo, I feel like the variable names are pretty self-explanatory here. Except for totalWon, that's just a count of the amount of $ has been won in total. Whenever this contract gets initialized we'll need to have a seed to start with. That's what this comes in place for:

contructor(uint _jackpotSeed) {
    uint contractGeneratedSeed = uint(keccak256(abi.encodePacked(
        _jackpotSeed,
        gameCount,
        0,
        0,
        0,
        block.coinbase,
        block.difficulty,
        block.gaslimit,
        block.number,
        block.timestamp,
        msg.data,
        msg.sender,
        msg.sig
    )));

    jackpotSeed = contractGeneratedSeed;
}

As I said, PSEUDO RANDOMNESS: Smack as many variables together to generate some number and use it as seed. So now, we got a seed ready to go, let's create the function to create a game.

function createGame(uint seed) public payable returns(uint gameId) {
    uint amount = msg.value;
    require(amount >= minimumBet, string.concat("You need to bet at least ", uint2str(minimumBet / 1 ether)));

    uint contractGeneratedSeed = uint(keccak256(abi.encodePacked(
        seed,
        gameCount,
        0,
        0,
        0,
        block.coinbase,
        block.difficulty,
        block.gaslimit,
        block.number,
        block.timestamp,
        msg.data,
        msg.sender,
        msg.sig
    )));

    games[gameCount] = Game(msg.sender, amount, seed, contractGeneratedSeed, msg.sender, 0, 0, 0, 0, 0, block.number);
    openGames.push(gameCount);

    gameCount++;
    return gameCount - 1;
}

THe function above creates a new seed based on the seed the user gave, and other details of the block and message data. Then it creates a new game saving the current seed, the new seed and other information which is necessary for the game.
Pew pew let's go to the joining game function

function joinGame(uint gameId, uint seed) public {
    uint amount = msg.value;
    Game storage game = games[gameId];

    require(amount*100 >= game.starterAmount*95 && amount*100 <= game.starterAmount*105, "Maximum bet difference of 5%");
    require(msg.sender != game.starter, "You can't join your own game");
    require(game.closed == 0, "Game is closed");
    require(game.starter == game.joiner, "Someone else already joined this game");

    removeGameFromOpenGames(gameId); // does exactly what the function name describes
    game.closed = 1;
    game.joiner = msg.sender;
    game.joinerAmount = amount;
    game.joinerInputSeed = seed;

    uint contractGeneratedSeed = uint(keccak256(abi.encodePacked(
        seed,
        gameCount,
        0,
        0,
        0,
        block.coinbase,
        block.difficulty,
        block.gaslimit,
        block.number,
        block.timestamp,
        msg.data,
        msg.sender,
        msg.sig
    )));
    game.joinerSeed = contractGeneratedSeed;

    uint total = game.starterAmount + game.joinerAmount;
    uint feeAmount = total / 100 * fee;
    uint totalAfterFee = total - feeAmount;
    totalWon += totalAfterFee;

    game.result = uint(keccak256(abi.encodePacked(
        game.starterSeed,
        game.joinerSeed,
        total,
        totalAfterFee
    )));

    uint draw = game.result % total + 1;
    if (game.starterAmount < draw) {
        (bool success, ) = payable(game.starter).call{value: totalAfterFee}("");
        require(success, "Failed to send Ether");
    } else {
        (bool success, ) = payable(game.joiner).call{value: totalAfterFee}("");
        require(success, "Failed to send Ether");
    }
}

Yet again: is this safe? no, does it work? probably, should you use it in production? absolutely not.

The code above first does some checks, like making sure you don't bet too much, somehow join your own game (why?????), and don't join a game that's already started.
Then it creates another pseudo random seed and adds it to the game. After which it calculates the amount of $ the winner will get. It then adds that number + the random seeds to generate another seed.
This seed gets used to draw a winner. Winner gets all.

This project also had a frontend that I've never finished 💀💀💀