Roboomhood
A token with a built-in buy/sell lottery: every trade takes a 10% fee, feeding an LP vault that backs the payouts, a ticket system that draws with verifiable randomness (VRF-style), and a dividend paid straight to holders.
Mechanics
Trading fee
Every buy or sell takes a 10% fee, split three ways: the Vault (funds the lottery), token holders (dividend), and the team wallet (used for buybacks).
Ticket on every buy
Every buy earns one ticket for a random draw, with a jackpot worth up to 50x the ETH spent.
Max buy cap
Every buy is capped relative to the current pool size, to stop a whale from swinging the price or draining the pool with an oversized win.
LP Vault
Anyone can deposit ETH into the Vault to become a liquidity provider, earn a share of the trading fees, and withdraw it back at any time in proportion to their contribution.
Holder dividend
Just holding the token earns a share of the dividend from trading fees, claimable any time, no action required.
Refunds on failure
If the randomness system fails or takes too long to resolve, buyers never lose their fee unfairly. It either gets refunded, or was simply never taken in the first place.
Provable fairness
No one, including the team running the project, can predict or manipulate a draw's outcome.
Full mechanics
1. Trading fee
Every buy or sell takes a 10% fee, split three ways according to fixed ratios in the contract:
uint256 public constant FEE_BPS = 1_000; // 10% of every trade
uint256 public constant VAULT_SHARE_OF_FEE_BPS = 8_000; // 80% of the fee -> 8% of volume
uint256 public constant DIVIDEND_SHARE_OF_FEE_BPS = 1_000; // 10% of the fee -> 1% of volume
// the remainder (1% of volume) goes to the team wallet
function _split(uint256 fee) internal pure returns (uint256 vaultShare, uint256 dividendShare, uint256 devShare) {
vaultShare = (fee * VAULT_SHARE_OF_FEE_BPS) / 10_000;
dividendShare = (fee * DIVIDEND_SHARE_OF_FEE_BPS) / 10_000;
devShare = fee - vaultShare - dividendShare;
}Selling takes the exact same 10% fee, split the same 8/1/1 way, just without earning a ticket.
2. Ticket on every buy
Every BUY (not sells) earns one ticket for a random draw, landing in exactly one of 7 fixed reward tiers:
// tierMultiplier[i] / MULT_SCALE(100) = payout multiplier uint256[7] public tierMultiplier = [uint256(5), 10, 50, 100, 500, 2500, 5000]; // 0.05x 0.1x 0.5x 1x 5x 25x 50x
A ticket is always attributed to whichever wallet actually receives the tokens, whether the trade goes through directly or via a bot or router. The system listens for the real token transfer event to determine the buyer, rather than relying on the transaction'smsg.sender.
3. Max buy cap
To stop a single oversized buy from manipulating the price or draining the pool on a big win, every buy is capped by a percentage of the Vault's current ETH balance. The bigger the pool, the higher the cap. Exceeding it reverts the trade entirely, right at the moment of the transaction:
function rawPoolValue() public view returns (uint256) {
return address(this).balance;
}
function maxBuyVolume() external view returns (uint256) {
uint256 poolValue = rawPoolValue();
if (poolValue == 0) return 0;
(uint256 capBps,) = _tierParams(poolValue);
return (poolValue * capBps) / 10_000;
}This is a hard cap, distinct from buying too little or too much for a ticket to make sense: in that case the trade still succeeds normally, only the ticket gets skipped instead of reverting:
if (volume < MIN_TICKET_VOLUME || poolValueBefore == 0) {
emit TicketSkipped(volume, true);
return 0; // the trade still succeeds, it just gets no ticket
}
...
if (volume > maxVolume) {
emit TicketSkipped(volume, false);
return 0; // above the ticket's own cap -- still no revert
}The team's own initial buy at token launch, used to seed a starting position, is exempt from the hard cap above, since it's a one-time action at launch rather than an ordinary buyer's trade:
if (sender != launcher) {
uint256 maxAllowed = vault.maxBuyVolume();
if (ethIn > maxAllowed) revert BuyExceedsMaxCap(ethIn, maxAllowed);
}4. Timeouts, refunds & forgotten payouts
If a ticket has no result after 15 minutes, a rare case, it automatically expires and the fee taken for that buy is refunded to the buyer in full. Separately, if the randomness system can't take the request at all, say because of a temporary outage, the buy still succeeds as normal. That trade alone just doesn't take a fee, gets no ticket, and there's nothing to refund because nothing was ever taken. If a winner or a refund recipient forgets to claim for too long, the project owner can sweep that amount back into the shared pool for LPs, so no ETH is ever lost for good.
5. Holder dividend
1% of every trade's fee accrues into a dividend pool, split in proportion to token balance, claimable at any time. The project's own wallets (the liquidity pool, the hook, the vault, and the launcher) are excluded from the dividend split, so this share goes entirely to real holders:
function _isProjectWallet(address account) internal view returns (bool) {
return account == poolManager || account == hook || account == vault || account == launcher;
}
function circulatingSupply() public view returns (uint256) {
return totalSupply() - balanceOf(poolManager) - balanceOf(hook) - balanceOf(vault) - balanceOf(launcher);
}6. LP Vault
Anyone can deposit ETH into the Vault to become an LP, receiving shares in proportion to their contribution (with protection against share-price manipulation on the very first deposit). This is the pool that backs the lottery's payouts. LPs earn from the 8% fee flowing in, but also carry the risk of a large win, and can't withdraw funds already confirmed owed to a winner who hasn't claimed yet.
7. Provable fairness of the draw
Draw results use a commit-reveal scheme: a secret hash chain is generated in advance, and only the hash of its very tip is made public. Each draw has to "unlock" the correct next secret value, mixed with a delayed blockhash, meaning a block that doesn't exist yet at the time of purchase. That combination means no one, not even the team running the keeper, can predict or manipulate a result:
function reveal(bytes32 preimage) external returns (uint256 requestId, uint256 randomWord) {
if (msg.sender != server) revert OnlyServer();
if (pendingCount() == 0) revert NoPendingRequest();
if (keccak256(abi.encodePacked(preimage)) != headCommitment) revert BadReveal();
requestId = _queue[_queueHead];
uint256 seedBlock = seedBlockOf[requestId];
if (block.number <= seedBlock) revert SeedNotReady(); // the seed didn't exist yet at purchase time
bytes32 blockSeed = blockhash(seedBlock);
if (blockSeed == bytes32(0)) revert SeedUnavailable();
...
}If a ticket's reveal window is ever missed, a rare occurrence, the system has a way to safely skip it so it doesn't wedge the entire queue behind it. A skipped ticket still gets refunded normally through the timeout mechanism in section 4:
function skipStalled(uint256 requestId) external {
if (pendingCount() == 0) revert NoPendingRequest();
if (_queue[_queueHead] != requestId) revert NotHead();
uint256 seedBlock = seedBlockOf[requestId];
if (block.number <= seedBlock) revert SeedNotReady();
if (blockhash(seedBlock) != bytes32(0)) revert SeedStillRecoverable(); // still recoverable, not eligible to skip yet
...
}8. Pausing & abuse prevention
The project owner can pause new tickets in an emergency. This never blocks LP deposits/withdrawals or pending reward/refund claims. Separately, each transaction may trigger at most one ticket; if multiple buys are bundled into the same transaction, a technique commonly used to game reward systems, the whole transaction reverts instead of silently ignoring the extras.
9. Pool creation
Only the project team is allowed to create the token's initial trading pool, which blocks a third party from creating it first at an unfavorable starting price for holders:
function beforeInitialize(address sender, PoolKey calldata key, uint160)
external override onlyPoolManager returns (bytes4)
{
if (sender != launcher) revert NotLauncher();
...
}There is no admin function anywhere in the Vault or Hook that lets the owner withdraw LP funds directly. Any ETH sitting in either contract only ever moves through the paths described above: LP withdrawals, ticket payouts, refunds, and dividend claims.

