Smart Contract
USDC Jackpot (Base Mainnet)
Contract Audits
This is the high-level overview of the audited smart contract:
Liquidity providers deposit liquidity at any time. Their liquidity starts guaranteeing a minimum jackpot starting the next jackpot.
Players can buy jackpot tickets at any time, except when awaiting drawing results.
Users can buy jackpot tickets on behalf of other users/wallets.
Referrers can claim fees instantly after a ticket is purchased.
The jackpot drawing is done 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.
LPs can initiate the process to withdraw liquidity at any time, but it takes one jackpot drawing to complete.
Deposit Liquidity
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 throughrunJackpot
lpDeposit
is also used when an LP deposits more.
LP Risk Adjustment
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.
Purchase Tickets
purchaseTickets
which takes in a referrer
, value
, and a recipient
purchaseTickets(address referrer, uint256 value, address recipient)
referrer
is the entity that got a user to buy a ticket. This can be an influencer or a front end to the raffle protocol. Requiring areferrer
incentivizes KOLs and users to drive traffic to the protocol, while providing baseline revenue for frontends.value
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
allows 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.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 receives10000 - 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 likeuserPoolTotal
Claim Referrer Fees
withdrawReferralFees
If the address has any amount to withdraw via
referralFeesClaimable(address)
, it can be withdrawn immediately.referralFeeTotal
is amount from all referrers collected from the current jackpot.
Jackpot Drawing
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 ensure randomness, we use Pyth an entropy protocol.
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 byriskPercentage
, inlp.stake
How to run the Jackpot
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
Go to Basescan > Write Proxy Contract > RunJackpot
For
runJackpot - payableAmount (ether)
, enter in0.000015000000000002
Fetch this from Blastscan > Read Proxy Contract > getJackpotFee
Convert this from WEI to ETH, thus, divide by 10^18
For
userRandomNumber - bytes32
, enter in0x55fb29339a98ca25bedb7b5aa225041f669ca1407e926a95ce4a9b080ac66907
Note: reusing this is fine, Pyth generates randomness, this seed further maximizes randomness
You can generate your own with
Web3.utils.randomHex(32)
oropenssl rand -hex 32
. Prefix with0x
Request callback from Pyth, which runs the jackpot
NOTE: This should happen automatically, but if it doesn't, you can manually run it.
Go to Basescan > Pyth's Base contract > RevealwithCallback
For these arguments:
provider (address)
sequenceNumber (uint64)
userRandomNumber (bytes32). Add
0x
in frontFetch the values from the
RequestedWithCallback
log from above. This event is emitted with therunJackpot
function when our contract is called. Make sure to pass them with the correct signatures.
For the last argument, providerRevelation (bytes32):
Go to the following endpoint: and use the sequence number: https://fortuna.dourolabs.app/v1/chains/base/revelations/[sequenceNumber]
You will find the
providerRevelation
in the data field. Add0x
in front
Withdraw Liquidity
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.
Withdraw Winnings
withdrawWinnings
Users can claim their winnings at any time.
Protocol Fee
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.
FAQ
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
Last updated