Smart Contract
Last updated
Last updated
USDC Jackpot (Base Mainnet)
USDC TEST Jackpot (Base Mainnet) **TESTING ONLY**
USDC TESTNET Jackpot (Base Sepolia) **TESTING ONLY**
Security Audits
The Megapot protocol above operates autonomously, enabling anyone to build on it, or use it, without permission.
The Megapot app at is a user-facing interface to the protocol and abides by relevant gaming regulations.
Players can buy jackpot tickets at any time, for themselves or for others.
Players can view their tickets and winnings, as well as withdraw winnings and referrer fees.
Liquidity providers deposit liquidity at any time. Their liquidity starts guaranteeing a minimum jackpot starting the next jackpot.
The jackpot drawing occurs every 24 hours. A winning ticket is drawn, liquidity providers' balances are updated, fees are distributed to LPs, and then a new minimum jackpot is realized. Learn how this is
LPs can edit their position or withdraw liquidity at any time, but it processes after the current jackpot drawing completes.
Go to the contract on Basescan here:
Click the button in the red box to connect your wallet.
Call the functions below!
purchaseTickets
takes in address referrer
, uint256 value
, address recipient
.
referrer
[default: 0x0x0000000000000000000000000000
] is the entity that got a user to buy a ticket. This can be an influencer or an app (often known as a frontend or interface). Requiring a referrer
incentivizes KOLs and users to drive traffic to the protocol, while providing baseline revenue for apps.
value
[default: 1000000
is a $1 ticket] is the amount of USDC that the user wants to spend on tickets. This uses Szabo notation, so 1000000 is 1 USDC (1 ticket).
recipient
[default: your address, beginning with 0x
] is the recipient of tickets. It should be your address, but also lets you to purchase tickets on behalf of another address. This can be used for gifting tickets to an address, or for more complex integrations to assign tickets to a specific wallet address (eg. cross-chain transactions).
Notes:
Tickets can only be purchased in increments of ticketPrice
, which is 1 USDC (1_000_000 szabo).
LP fees and referrer fees are set aside at ticket purchase. The remainder is turned into tickets, where 10000 tickets equal 1 ticketPrice
. Thus, a user receives 10000 - feeBps
number of tickets.
For all cases counting tickets, since they are multiplied by 10000, Bps
is added to the var name for clarity (eg. ticketCountTotalBps
) This has no impact on Szabo-denominated var like userPoolTotal
Players can purchase tickets at any time, except for ~1 minute per day when the jackpot is drawing a winner.
Users can view their tickets and winnings directly from the contract.
usersInfo
takes in an address
[your address, starting with 0x
]. It returns:
ticketsPurchasedTotalBps
is the amount of tickets a user bought. 1 ticket is shown here as 7000, 2 tickets would show 14000, etc.
winningsClaimable
is the amount of winnings that a user has, in USDC, with six decimal places.
active
is a boolean that is true if the user has tickets, false if they have no tickets.
withdrawWinnings
lets a user withdraw their winnings.
Check if the address has any amount to withdraw via usersInfo
--> winningsClaimable(address)
.
withdrawReferralFees
lets a user withdraw their referrer fees immediately after a ticket is purchased.
Check if the address has any amount to withdraw via referralFeesClaimable(address)
Note: referralFeesTotal
is amount from all referrers collected from the current jackpot.
lpDeposit
which takes in an integer riskPercentage
and a USDC amount value
lpDeposit(uint256 riskPercentage, uint256 value)
riskPercentage
is the variance that an LP chooses. This percentage of the liquidity guarantees the minimum jackpot and must be between 1 and 100.
At deposit time, the entirety of the deposits goes into the LP reserves, known as principal
Your risk percentage determines how much of your principal is used to guarantee the jackpot.
Notes:
The risk amount of LP reserves turns into tickets in stakeLps
, which is rebalanced after each daily jackpot drawing through runJackpot
lpDeposit
is also used when an LP deposits more.
lpAdjustRiskPercentage
which takes in an integer riskPercentage
between 1 and 100.
lpAdjustRiskPercentage(uint256 riskPercentage)
You can adjust your risk percentage at any time.
Risk adjustment occurs after the next jackpot drawing.
withdrawAllLP
LP reserves can only be withdrawn in full, and cannot be withdrawn partially. This two-step process requires one jackpot drawing to occur before liquidity can be withdrawn.
If LP has any liquidity in range, the contract will set riskPercentage = 0
. The function ends at this time.
After the jackpot is run, LP should have no liquidity in range (lp.stake = 0
) because stakeLps
does not allocate any liquidity if riskPercentage = 0
. At this time LP can call withdrawAllLP
again to have their liquidity returned.
Context:
Users buy tickets from the contract. The total number of tickets is represented as userPoolTotal
LPs' guarantee for the jackpot is represented as lpPoolTotal
Every 24 hours, we schedule a backend request to run the jackpot.
Note: This is sufficiently decentralized. Anyone is able to run the jackpot after 24 hours has elapsed. This means if our backend service stops working, anyone can run the jackpot. We will improve on this more in the future.
To request a random number, we send a transaction with a random 32-byte hexadecimal number generated off-chain and receive a sequence number. Entropy calls back to the contract with the generated random number once the request is fulfilled by the provider.
The raffle drawing occurs in runJackpot
and there are three situations:
Players bought more tickets than the LPs' jackpot guarantee
Players bought less tickets than the LPs jackpot guarantee, and player wins
Players bought less tickets than the LPs' jackpot guarantee, and LPs win
Before each situation, LP fees are distributed to LPs via distributeLpFees
. If no LPs have position in range for that round, LP fees are distributed to the userPoolTotal.
Case #1: Players bought more tickets than the LPs' jackpot guarantee
A random number is generated between 1 and the number of tickets bought by users.
The jackpot amount userPoolTotal
, which already does not include LP fees and referrer fees, is set to be claimable by the user.
LPs get their guarantee back fully via returnLpPoolBackToLps
Case #2: Players bought less tickets than the LPs jackpot guarantee, and players win
A random number is generated between 1 and the number of tickets guaranteed by LPs. Note, this is multiplied by 10000 to match the number of tickets per ticketPrice
.
If the random number is less than the number of tickets bought by users, represented as ticketCountTotal
, then users win!
Jackpot amount lpPoolTotal
is set to be claimable by the user.
Users' entries are distributed to LPs via distributeUserPoolToLps
Case #3: Players bought less tickets than the LPs jackpot guarantee, and LPs win
Users' entries are distributed to LPs via distributeUserPoolToLps
LP pool is returned to LPs in returnLpPoolBackToLps
After one of the above cases is run:
Reset all variables.
Initialize the next guaranteed jackpot in stakeLps
. For every active LP, we take their reserves (lp.principal
) and multiply it by riskPercentage
, in lp.stake
Our contract is decentralized -- anyone can run the jackpot every 24 hour. The core team has a scheduled run that does it, in case anything happens, anyone can run it. Here are the steps:
Kick off the jackpot run, which generates a random number from Pyth
For runJackpot - payableAmount (ether)
, enter in 0.000015000000000002
Convert this from WEI to ETH, thus, divide by 10^18
For userRandomNumber - bytes32
, enter in 0x55fb29339a98ca25bedb7b5aa225041f669ca1407e926a95ce4a9b080ac66907
Note: reusing this is fine, Pyth generates randomness, this seed further maximizes randomness
You can generate your own with Web3.utils.randomHex(32)
or openssl rand -hex 32
. Prefix with 0x
Request callback from Pyth, which runs the jackpot
NOTE: This should happen automatically, but if it doesn't, you can manually run it.
For these arguments:
provider (address)
sequenceNumber (uint64)
userRandomNumber (bytes32). Add 0x
in front
Fetch the values from the RequestedWithCallback
log from above. This event is emitted with the runJackpot
function when our contract is called. Make sure to pass them with the correct signatures.
For the last argument, providerRevelation (bytes32):
You will find the providerRevelation
in the data field. Add 0x
in front
There is a protocol fee that is only enabled when this protocol reaches massive scale, defined as LP fees are greater than 10,000 USDC per day. With a LP fee of 30%, that means ~$33k - 50k of tickets are sold. Only then will 1/10 of LP fees be distributed to the protocol treasury.
Why is the contract upgrade-able?
We are launching a new protocol and there are many things we cannot anticipate. We want to have the ability to make changes to benefit LPs, apps, and players.
Our contract is secured by a multi signature wallet, with each signer using a cold wallet that is only used for Megapot operations.
We take operational security seriously. Our founder is doxxed (@Patrick_Lung on X), has a strong reputation (ex. Uniswap, Microsoft), and has been in crypto for years.
What changes do you want to make to the smart contract?
Increase LP and user limits
Optimize search functionality. We have a limit on LPs and user limits so we don't hit block gas limits
Allow for delegation for LPs
Enable LPs to deposit liquidity from any chain. This allows direct deposits from CEX or fiat.
Ticket-based drawings where users can pick numbers
Side prizes in addition to the jackpot
Dynamic fees to balance the multi-sided network
, trusted by Uniswap and Coinbase , led YOLO Games' audit , Polygon Labs partner , trusted by Solana Foundation
To ensure randomness, we use an entropy protocol.
Go to Basescan > Write Proxy Contract >
Fetch this from Blastscan > Read Proxy Contract >
Go to Basescan > Pyth's Base contract >
Go to the following endpoint: and use the sequence number: