My very cool projects

Go back

Decentralized jackpot

I was bored. So wouldn't it be cool to create a Jackpot game on the blockchain? So I did, yes.

A jackpot?!?!?!!!!

I was bored. Wanted to do something. But on the same time make money??? OMG making a jackpot website will probably work! This project had 3 different parts. The contract, the backend & the website (frontend ig)

The contract

The contract does a lot for the project. The ability to bet on the jackpot is built in here. This is what makes it "decentralized". These were the variables I set on the contract:

struct Bet {
    address by;
    uint amount;
    uint result;
    string ticket;
}
    
address internal owner;
uint public bet_count = 0;
uint public bet_count_since_last_win = 0;
uint public amount = 0;
uint public pending_amount = 0;
uint internal total_deposit = 0;

mapping(uint => Bet) public bets;
mapping(address => address) internal referredBy;
mapping(address => uint) internal isReferred;

Yeap, so let me try to explain what this all is.
Bet is a struct that contains who did the bet, the amount of money they bet, what the result is and what the ticket is.
owner is a variable with my address in it
bet_count is a variable with a number that counts up with every bet
bet_count_since_last_win does exactly the same, but resets to 0 whenever someone wins
amount contains what the jackpot is at right now
pending_amount is the total amount of the bets that are still pending
total_deposit the amount of money I've deposited as owner into the contract
bets is a mapping with Bet as value
referredBy keeps track who got referred by whom
isReferred is a mapping that'll tell if the person is referred in general
That's it!!!

Now we're done with that, I should totally tell you how this jackpot works exactly. You can bet with any amount for the jackpot, with a maximum of 1% of the jackpot amount. If the jackpot is 100$, you can bet a maximum of 1$. If you bet 1$ you'll have a 0.5% chance to win. The win chance is calculated by (bet amount / jackpot amount / 2).

OK, now create a function that people can call to do a bet (with their money)

function bet(address referral) public payable returns(uint) {
    require(msg.value >= 20000000000000000, "You need to bet more to cover random number generation transaction fees");
    require(msg.value <= (amount + (pending_amount / 2)) / 100, "Bet value can only be maximum of 1% of jackpot value");

    if (referral != owner || isReferred[msg.sender] == 1) {
        if (isReferred[msg.sender] == 0) {
            isReferred[msg.sender] = 1;
            referredBy[msg.sender] = referral;
        }
        
        (bool success, ) = payable(referredBy[msg.sender]).call{value: msg.value / 10}("");
        require(success, "Failed to send Ether");
    }

    bets[bet_count] = Bet(msg.sender, msg.value, 0, '');

    bet_count += 1;
    bet_count_since_last_win += 1;
    pending_amount += msg.value;
    return bet_count - 1;
}

Whenever someone calls the function above, they'll create a new bet. If they got referred by someone, they'll receive 10% of their bet amount. And it just saves all the values to the contract.

But why only saving???
This is where the backend (eventually) comes in place. When a new bet is created, the backend will generate a new random number with random.org. (yes I know VRF exists, but it's a bit expensive for this use case). And will need to submit the random number into the contract:

function setNumber(uint betId, uint result, string memory ticket) public {
    require(msg.sender == owner, "Only the owner can do this");
    require(betId < bet_count, "Bet does not exist");

    Bet storage bEt = bets[betId];
    bEt.result = result;
    bEt.ticket = ticket;

    // if user wins
    if (result < bEt.amount / 2000000000000000) {
        uint payout = amount / 100 * 80; // pay out 80% of jackpot
        (bool success, ) = payable(bEt.by).call{value: payout}("");
        require(success, "Failed to send Ether");

        amount -= payout;
        bet_count_since_last_win = 0;
    }

    // pay out 50% of bet amount to owner (-10% possible referrer fee)
    uint fee = bEt.amount / 2;
    uint referred = 0;
    if (isReferred[bEt.by] == 1) {
        referred = bEt.amount / 10;
    }

    (bool success2, ) = payable(owner).call{value: fee - referred}("");
    require(success2, "Failed to send Ether");

    pending_amount -= bEt.amount;
    amount += bEt.amount - fee;
}

This function will only be callable by the owner. It sets the result and ticket id (so the user can verify the random number). If the user wins it'll pay out 80% of the jackpot. After which it'll pay out the 50% fee to the owner of the contract. It also removes the 10% that has been given to the person that referred the user.

The contract has more functions like the owner being able to deposit money, withdraw the money they've deposited, and the function to change the owner. But let's continue to the backend.

The backend

The backend is an important part of this system. It waits for someone to do a bet, and then send a new transaction to the contract with a random number that got generated. I've written this part in Python for ease. Let's first initialize some stuff.

import json, binascii, asyncio
from web3 import Web3
from web3.middleware import geth_poa_middleware
from rdoclient import RandomOrgClient

with open('abi.json') as f:
    abi = json.loads(f.read())

w3 = Web3(Web3.WebsocketProvider('<PROVIDER WAS HERE>'))
w3.middleware_onion.inject(geth_poa_middleware, layer=0) # because Polygon
jackpot = w3.eth.contract(address="0xC9BC6e7B74b4B07a58DDb4DecF1eE21122Ac4051", abi=abi)

private_key = binascii.a2b_hex("<MY PRIVATE KEY WAS HERE>")
r = RandomOrgClient("<RANDOM.ORG API KEY WAS HERE>")

That's a lot of initializing.. Now let's actually do some stuff with this. So whenever a new bet has been created, it'll get a random number first. Let's create that function:

def getNewNumber(betId):
    amount = jackpot.functions.amount().call()
    maxNumber = round(amount / 1000000000000000)
    ticket = r.create_tickets(1, False)
    ticketId = ticket[0]['ticketId']
    number_generated = r.generate_signed_integers(1, 0, maxNumber, ticket_id=ticketId)
    setNumber(betId, number_generated['data'][0], ticketId)

The function above will create a new ticket at random.org. Then consume the ticket to generate 1 new integer. After that, it sends the bet id, the number that got generated and the ticket used to the function which'll send the transaction.

def setNumber(betId, result, ticket):
    nonce = w3.eth.get_transaction_count("0x7982985F05a9dabD3F26dC81CB161f440BE48eE5")

    txn = jackpot.functions.setNumber(
        betId,
        result,
        ticket
    ).buildTransaction({
        "chainId": 137,
        "nonce": nonce,
        "from": "0x7982985F05a9dabD3F26dC81CB161f440BE48eE5",
        "gas": 130000
    })

    signed_txn = w3.eth.account.sign_transaction(txn, private_key=private_key)
    w3.eth.send_raw_transaction(signed_txn.rawTransaction)

The function above received the bet id, result number & ticket that got consumed. Creates a new transaction with all the information (to the contract). Signs the transaction with my private key. And sends the transaction to the Blockchain.

After this you'll obviously need some type of looping. But I feel like I've said enough about this project already.

Oh!! And don't forget the frontend!!!!!!!!