LaunchToken
0x08311116b264de1c8566dcf93867ac61dcbe4663
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
9,320 bytes
ABI entries
71
29 read · 16 write
License
none
Contract information
- Address
- 0x08311116b264de1c8566dcf93867ac61dcbe4663
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xeC180FC741…A869E02f90
- Creation tx
- 0x80c86a2fc0…65d6150af8
Token
- Name
- Infinite Money Glitch
- Symbol
- IMG
- Decimals
- 18
- Holders
- 102
- Market
- View token page →
Read contract (29)
MIN_DIVIDEND_SHARES() → uint256
accumulativeReflection(address) → uint256
accumulativeWeth(address) → uint256
allowance(address, address) → uint256
balanceOf(address) → uint256
decimals() → uint8
isExcluded(address) → bool
launcher() → address
lifter() → address
limitsActive() → bool
limitsExpiry() → uint40
limitsLifted() → bool
magReflectionPerShare() → uint256
magWethPerShare() → uint256
maxTxBaseAmount() → uint128
maxWalletBaseAmount() → uint128
name() → string
owner() → address
registry() → address
sharesOf(address) → uint256
symbol() → string
tokenURI() → string
totalEligibleTokens() → uint256
totalRaw() → uint256
totalShares() → uint256
totalSupply() → uint256
weth() → address
withdrawableReflection(address) → uint256
withdrawableWeth(address) → uint256
Events (8)
ApprovalLimitsLiftedReclassifiedReflectionClaimedReflectionDistributedTransferWethClaimedWethDistributed
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "string",
"name": "tokenURI_",
"type": "string"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"internalType": "address",
"name": "lifter_",
"type": "address"
},
{
"internalType": "address",
"name": "registry_",
"type": "address"
},
{
"internalType": "uint128",
"name": "maxTx_",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "maxWallet_",
"type": "uint128"
},
{
"internalType": "uint40",
"name": "startTime_",
"type": "uint40"
},
{
"internalType": "uint32",
"name": "limitsDuration_",
"type": "uint32"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientAllowance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientBalance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "approver",
"type": "address"
}
],
"name": "ERC20InvalidApprover",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC20InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "ERC20InvalidSender",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "ERC20InvalidSpender",
"type": "error"
},
{
"inputs": [],
"name": "EthTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientShares",
"type": "error"
},
{
"inputs": [],
"name": "MaxTxExceeded",
"type": "error"
},
{
"inputs": [],
"name": "MaxWalletExceeded",
"type": "error"
},
{
"inputs": [],
"name": "NoEligibleHolders",
"type": "error"
},
{
"inputs": [],
"name": "OnlyLifter",
"type": "error"
},
{
"inputs": [],
"name": "OnlyWeth",
"type": "error"
},
{
"inputs": [],
"name": "ReflectionUnbacked",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "SafeCastOverflowedUintToInt",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "LimitsLifted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "excluded",
"type": "bool"
}
],
"name": "Reclassified",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "holder",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ReflectionClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ReflectionDistributed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "holder",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WethClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WethDistributed",
"type": "event"
},
{
"inputs": [],
"name": "MIN_DIVIDEND_SHARES",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "accumulativeReflection",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "accumulativeWeth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "claim",
"outputs": [
{
"internalType": "uint256",
"name": "ethOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reflectionOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "holder",
"type": "address"
}
],
"name": "claimFor",
"outputs": [
{
"internalType": "uint256",
"name": "ethOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reflectionOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "holders",
"type": "address[]"
}
],
"name": "claimForMany",
"outputs": [
{
"internalType": "uint256",
"name": "ethTotal",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reflectionTotal",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "claimReflection",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "claimReflection",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "claimWeth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "claimWeth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "r",
"type": "uint256"
}
],
"name": "distributeReflection",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "w",
"type": "uint256"
}
],
"name": "distributeWeth",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "isExcluded",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launcher",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liftLimits",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "lifter",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "limitsActive",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "limitsExpiry",
"outputs": [
{
"internalType": "uint40",
"name": "",
"type": "uint40"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "limitsLifted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "magReflectionPerShare",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "magWethPerShare",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxTxBaseAmount",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxWalletBaseAmount",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "registry",
"outputs": [
{
"internalType": "contract ILimitExemptRegistry",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "value",
"type": "bool"
}
],
"name": "setPrivilegedTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "sharesOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "sync",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalEligibleTokens",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalRaw",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalShares",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "withdrawableReflection",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
}
],
"name": "withdrawableWeth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {FullMath} from "v4-core/libraries/FullMath.sol";
interface ILimitExemptRegistry {
function isLimitExempt(address account) external view returns (bool);
}
interface IWETH {
function withdraw(uint256 amount) external;
}
/// @notice Purpose-built, fixed-supply, ownerless launch token with two flywheel reward mechanics that
/// need per-holder accounting: (a) auto-rebasing native-token REFLECTIONS and (b) WETH DIVIDENDS — both
/// paid ONLY to EOA (non-contract) holders. It is NOT an OZ ERC-20 subclass: it implements IERC20 +
/// IERC20Metadata from scratch over a dual ledger.
///
/// Dual ledger (no set is ever enumerated):
/// - ELIGIBLE holders (EOAs, not registry-exempt) are stored as SHARES of an elastic token pool. Their
/// `balanceOf` = shares * totalEligibleTokens / totalShares, so a reflection (which grows
/// totalEligibleTokens) rebases every eligible balance up pro-rata with no per-holder writes.
/// - EXCLUDED holders (contracts, registry-exempt infra, address(0), 0xdead) are stored as RAW token
/// amounts that never rebase. This is what keeps the Uniswap pool's reserves sound: the PoolManager,
/// hook, launcher, and routers are all excluded, so a reflection never inflates the pool's balance.
///
/// Eligibility is evaluated LAZILY: every transfer (and mint) reclassifies both parties, converting a
/// holder's whole balance between the two ledgers at the current rate if its class changed. A
/// permissionless `sync(a)` forces a reclassification (handles a contract that received tokens during
/// its own constructor, when its code size still read as 0).
///
/// During the launch window it enforces max-tx / max-wallet limits on inbound transfers to non-exempt
/// wallets; the launchpad hook flips `limitsLifted` ONE-WAY the first time price crosses the target
/// market cap (or after an optional time backstop), after which limits never bind again.
contract LaunchToken is IERC20, IERC20Metadata, IERC20Errors {
using SafeERC20 for IERC20;
using SafeCast for uint256;
error MaxTxExceeded();
error MaxWalletExceeded();
error OnlyLifter();
error ReflectionUnbacked();
error NoEligibleHolders();
/// A WETH distribution was attempted while the eligible base is below MIN_DIVIDEND_SHARES.
error InsufficientShares();
/// Native ETH was sent to the token by anyone other than the WETH contract (an unwrap refund).
error OnlyWeth();
/// The native-ETH payout of a claimed dividend failed.
error EthTransferFailed();
event LimitsLifted();
/// Native-token reflection: `amount` bought-back tokens distributed pro-rata to eligible (EOA)
/// holders' shares as a CLAIMABLE stream (custodied until claimed), or burned if there were none yet.
event ReflectionDistributed(uint256 amount);
/// WETH dividend of `amount` distributed pro-rata to eligible (EOA) holders' shares.
event WethDistributed(uint256 amount);
event WethClaimed(address indexed holder, address indexed to, uint256 amount);
/// A holder's accrued reflection tokens (`amount`) were claimed/pushed to `to`.
event ReflectionClaimed(address indexed holder, address indexed to, uint256 amount);
/// A holder crossed the EOA/contract boundary and had its whole balance moved between ledgers.
event Reclassified(address indexed account, bool excluded);
/// @dev Magnitude scaler for the WETH-dividend accumulator (classic magnified-dividend pattern).
uint256 private constant MAG = 2 ** 128;
address private constant DEAD = 0x000000000000000000000000000000000000dEaD;
/// @notice Minimum eligible share base required to accept a WETH distribution. Distributing while
/// `_totalShares` is tiny would blow `_magWethPerShare` up astronomically (dividing a large WETH
/// amount by a sub-token share base); flooring the denominator at 1 token of shares keeps the
/// accumulator bounded so it can never lose precision or overflow on a later distribution. The hook
/// reads this to ROLL a reward FORWARD instead of distributing below the floor (public so callers can
/// reference it as a compile-time constant, no external call needed). See distributeWeth / harvest.
uint256 public constant MIN_DIVIDEND_SHARES = 1e18;
// ---------------------------------------------------------------- immutables / config
/// The launcher that deployed this token; the entire supply is minted to it for seeding.
address public immutable launcher;
/// The hook allowed to permanently lift limits AND to distribute reflections / WETH dividends.
address public immutable lifter;
/// Source of the shared infra exempt-set (PoolManager, routers, hook…).
ILimitExemptRegistry public immutable registry;
/// The reward / quote currency (WETH). The token custodies dividend WETH until holders claim it.
address public immutable weth;
uint128 public immutable maxTxBaseAmount;
uint128 public immutable maxWalletBaseAmount;
/// Optional time backstop; 0 = limits only lift on the price latch.
uint40 public immutable limitsExpiry;
bool public limitsLifted;
/// Set true only during a privileged transfer (hook fee payout or launcher dev-vesting release) so
/// those bypass the launch-window limits — never true during a swap. See setPrivilegedTransfer.
bool private _privileged;
string private _name;
string private _symbol;
string private _tokenURI;
// ---------------------------------------------------------------- dual ledger
uint256 private _supply; // totalSupply(); INVARIANT: _supply == _totalEligibleTokens + _totalRaw
uint256 private _totalEligibleTokens; // token backing behind the shares ledger
uint256 private _totalRaw; // sum of excluded holders' raw balances
uint256 private _totalShares; // aggregate shares
mapping(address => uint256) private _shares; // eligible holders' shares
mapping(address => uint256) private _raw; // excluded holders' raw token balance
mapping(address => bool) private _excluded; // current STORED class of each account
mapping(address => mapping(address => uint256)) private _allowances;
// ---------------------------------------------------------------- WETH magnified dividends
uint256 private _magWethPerShare; // scaled by MAG
mapping(address => int256) private _corr; // per-holder correction, kept exact on every share change
mapping(address => uint256) private _withdrawn; // WETH already claimed by a holder
// ---------------------------------------------------------------- reflection magnified dividends
// Reflections are bought back from the flywheel's ETH share by the hook and distributed CLAIMABLY
// (not rebased): the bought tokens are custodied by this contract and paid out per-share on claim,
// exactly like the WETH stream. Keeps `balanceOf` a standard (non-elastic) ERC-20 balance.
uint256 private _magReflectionPerShare; // scaled by MAG
mapping(address => int256) private _corrReflection;
mapping(address => uint256) private _withdrawnReflection; // reflection tokens already claimed
constructor(
string memory name_,
string memory symbol_,
string memory tokenURI_,
uint256 supply,
address lifter_,
address registry_,
uint128 maxTx_,
uint128 maxWallet_,
uint40 startTime_,
uint32 limitsDuration_,
address weth_
) {
launcher = msg.sender;
lifter = lifter_;
registry = ILimitExemptRegistry(registry_);
weth = weth_;
maxTxBaseAmount = maxTx_;
maxWalletBaseAmount = maxWallet_;
// Derive the limits backstop from deterministic args only. `block.timestamp` is read here (not a
// constructor arg), so it does NOT affect the CREATE2 address — the token address stays mineable
// off-chain for the vanity suffix.
uint40 activation = startTime_ == 0 ? uint40(block.timestamp) : startTime_;
limitsExpiry = limitsDuration_ == 0 ? 0 : uint40(uint256(activation) + limitsDuration_);
_name = name_;
_symbol = symbol_;
_tokenURI = tokenURI_;
if (maxTx_ == 0 && maxWallet_ == 0) limitsLifted = true; // nothing to enforce
// The token custodies the claimable reflection pool in its OWN raw balance. Mark it excluded up
// front: during construction `address(this).code.length == 0`, so `_isExcludedNow` would misread
// it as eligible — set the flag explicitly so `balanceOf(this)` reports the raw pool and reflection
// funds are never mis-accounted onto a phantom shares ledger.
_excluded[address(this)] = true;
_mint(msg.sender, supply); // launcher is a contract => excluded/raw
}
// ---------------------------------------------------------------- ERC20 metadata
function name() external view returns (string memory) {
return _name;
}
function symbol() external view returns (string memory) {
return _symbol;
}
function decimals() external pure returns (uint8) {
return 18;
}
function tokenURI() external view returns (string memory) {
return _tokenURI;
}
// ---------------------------------------------------------------- ERC20 views
function totalSupply() external view returns (uint256) {
return _supply;
}
/// @notice Elastic for eligible holders (shares * rate), raw for excluded holders.
function balanceOf(address account) public view returns (uint256) {
if (_excluded[account]) return _raw[account];
return _sharesToTokens(_shares[account]);
}
function allowance(address owner, address spender) external view returns (uint256) {
return _allowances[owner][spender];
}
// ---------------------------------------------------------------- ERC20 mutators
function approve(address spender, uint256 value) external returns (bool) {
if (spender == address(0)) revert ERC20InvalidSpender(address(0));
_allowances[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transfer(address to, uint256 value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
_spendAllowance(from, msg.sender, value);
_transfer(from, to, value);
return true;
}
/// @notice Burn caller's tokens (launcher burns seeding dust; anyone may burn their own).
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
// ---------------------------------------------------------------- launch-window limits
/// @notice Permanently disable launch-window limits. Callable once by the hook.
function liftLimits() external {
if (msg.sender != lifter) revert OnlyLifter();
if (!limitsLifted) {
limitsLifted = true;
emit LimitsLifted();
}
}
/// @notice Launcher/hook-only: temporarily exempt a privileged transfer (a fee-withdrawal by the
/// hook, or a dev-vesting release by the launcher) from the launch limits. The caller toggles this
/// true→transfer→false within a single call, so it is never observable during a swap and cannot be
/// used to bypass trader buy limits.
function setPrivilegedTransfer(bool value) external {
if (msg.sender != lifter && msg.sender != launcher) revert OnlyLifter();
_privileged = value;
}
/// @notice True while limits still bind (the time backstop is honored live, not just on lift).
function limitsActive() public view returns (bool) {
if (limitsLifted) return false;
if (limitsExpiry != 0 && block.timestamp >= limitsExpiry) return false;
return true;
}
// ---------------------------------------------------------------- reclassification
/// @notice Force `a` into its correct ledger. Permissionless; supply-conserving. Needed when a
/// contract received tokens during its own constructor (code size read 0, so it was mis-stored as
/// eligible) — this, or its next transfer, moves it to the excluded/raw ledger where it can't rebase.
function sync(address a) external {
_reclassify(a);
}
/// @notice Whether `a` is CURRENTLY stored in the excluded (raw) ledger.
function isExcluded(address a) external view returns (bool) {
return _excluded[a];
}
/// @notice The token is permanently ownerless: fixed supply (no mint after construction), no pause,
/// blacklist, or admin of any kind. Exposed as a zero owner so explorers / safety scanners that key
/// on `owner()` correctly report it as renounced.
function owner() external pure returns (address) {
return address(0);
}
// ---------------------------------------------------------------- flywheel distribution (lifter-only)
/// @notice Distribute `r` bought-back tokens pro-rata to eligible (EOA) holders' shares as a CLAIMABLE
/// stream. The hook must have first sent this token `r` tokens (held as raw against the lifter); they
/// are moved into this token's own claimable pool and credited to the per-share reflection accumulator,
/// so holders (or the keeper via claimFor) collect them later — balances are NOT rebased. If there are
/// no eligible holders yet, `r` is burned so nothing is stranded. Reverts below MIN_DIVIDEND_SHARES so
/// the accumulator stays bounded (the hook rolls the reflection forward, exactly like distributeWeth).
function distributeReflection(uint256 r) external {
if (msg.sender != lifter) revert OnlyLifter();
if (r == 0) return;
if (_raw[lifter] < r) revert ReflectionUnbacked();
if (_totalShares == 0) {
_raw[lifter] -= r;
_totalRaw -= r;
_supply -= r; // no eligible holders: burn instead of stranding
emit Transfer(lifter, address(0), r);
return;
}
if (_totalShares < MIN_DIVIDEND_SHARES) revert InsufficientShares();
// Move the bought-back tokens from the hook into this token's claimable reflection pool. Both the
// lifter and address(this) are excluded, so _totalRaw (and _supply) are unchanged — the tokens are
// merely custodied here until claimed.
_raw[lifter] -= r;
_raw[address(this)] += r;
_magReflectionPerShare += FullMath.mulDiv(r, MAG, _totalShares);
emit ReflectionDistributed(r);
}
/// @notice Distribute `w` WETH pro-rata to eligible holders' shares. The hook must have already sent
/// this token `w` WETH (the token custodies it until holders claim). Reverts if there are no eligible
/// holders, or if the eligible base is below MIN_DIVIDEND_SHARES (the hook checks `totalShares()`
/// first and rolls the WETH forward in either case). Flooring the share base keeps `_magWethPerShare`
/// bounded: it only ever grows by `mulDiv(w, MAG, _totalShares)` with `_totalShares >= 1e18`, so for
/// uint128-bounded `w` the accumulator (and every product it feeds) stays well within uint256.
/// Uses FullMath.mulDiv (512-bit intermediate) so the store itself can never overflow.
function distributeWeth(uint256 w) external {
if (msg.sender != lifter) revert OnlyLifter();
if (w == 0) return;
if (_totalShares == 0) revert NoEligibleHolders();
if (_totalShares < MIN_DIVIDEND_SHARES) revert InsufficientShares();
_magWethPerShare += FullMath.mulDiv(w, MAG, _totalShares);
emit WethDistributed(w);
}
// ---------------------------------------------------------------- WETH dividend claims
/// @notice WETH earned by `a` that has not yet been withdrawn (0 for excluded holders).
function withdrawableWeth(address a) public view returns (uint256) {
uint256 acc = _accumulativeWeth(a);
uint256 w = _withdrawn[a];
return acc > w ? acc - w : 0;
}
/// @notice Cumulative WETH ever allocated to `a` (before withdrawals).
function accumulativeWeth(address a) external view returns (uint256) {
return _accumulativeWeth(a);
}
/// @notice Reflection tokens earned by `a` that have not yet been claimed (0 for excluded holders).
function withdrawableReflection(address a) public view returns (uint256) {
uint256 acc = _accumulativeReflection(a);
uint256 w = _withdrawnReflection[a];
return acc > w ? acc - w : 0;
}
/// @notice Cumulative reflection tokens ever allocated to `a` (before claims).
function accumulativeReflection(address a) external view returns (uint256) {
return _accumulativeReflection(a);
}
/// @notice Per-share reflection accumulator (scaled by 2**128), for off-chain reconstruction.
function magReflectionPerShare() external view returns (uint256) {
return _magReflectionPerShare;
}
/// @notice Only the WETH contract may send this token native ETH — an unwrap refund from a claim.
receive() external payable {
if (msg.sender != weth) revert OnlyWeth();
}
/// @notice Claim the caller's accrued WETH dividend as native ETH.
function claimWeth() external returns (uint256) {
return _claimWethTo(msg.sender, msg.sender);
}
/// @notice Claim the caller's accrued WETH dividend, sending the native ETH to `to`.
function claimWeth(address to) external returns (uint256) {
return _claimWethTo(msg.sender, to);
}
/// @notice Claim the caller's accrued reflection tokens.
function claimReflection() external returns (uint256) {
return _claimReflectionTo(msg.sender, msg.sender);
}
/// @notice Claim the caller's accrued reflection tokens, sending them to `to`.
function claimReflection(address to) external returns (uint256) {
return _claimReflectionTo(msg.sender, to);
}
/// @notice Claim BOTH of the caller's reward streams: native ETH + reflection tokens.
function claim() external returns (uint256 ethOut, uint256 reflectionOut) {
ethOut = _claimWethTo(msg.sender, msg.sender);
reflectionOut = _claimReflectionTo(msg.sender, msg.sender);
}
/// @notice Push `holder`'s accrued rewards to THEM — native ETH + reflection tokens. Permissionless,
/// so a keeper can deliver rewards on holders' behalf with no action from the holder. Safe: only EOAs
/// are ever eligible — `_reclassify` demotes any contract to the excluded ledger (whose withdrawable is
/// 0), so rewards only ever reach code-less accounts, and each payout follows checks-effects-
/// interactions (the withdrawn total is banked before the transfer).
function claimFor(address holder) external returns (uint256 ethOut, uint256 reflectionOut) {
ethOut = _claimWethTo(holder, holder);
reflectionOut = _claimReflectionTo(holder, holder);
}
/// @notice Batch {@link claimFor} for many holders in a single call (keeper convenience). Skips
/// holders with nothing to claim; returns the totals pushed.
function claimForMany(address[] calldata holders)
external
returns (uint256 ethTotal, uint256 reflectionTotal)
{
uint256 n = holders.length;
for (uint256 i; i < n; ++i) {
ethTotal += _claimWethTo(holders[i], holders[i]);
reflectionTotal += _claimReflectionTo(holders[i], holders[i]);
}
}
/// @dev Pays `holder`'s accrued dividend to `to` as native ETH: the token unwraps its custodied WETH
/// and forwards the ETH. `_reclassify` first demotes any now-contract holder (making its withdrawable
/// 0), so a push can only ever reach an EOA — no external-call reentrancy surface. `_withdrawn` is
/// updated before the unwrap/transfer (checks-effects-interactions).
function _claimWethTo(address holder, address to) private returns (uint256 amount) {
_reclassify(holder); // reclassify against the current infra allowlist (wallets remain eligible)
amount = withdrawableWeth(holder);
if (amount == 0) return 0;
_withdrawn[holder] += amount; // effects before interactions
if (to.code.length > 0) {
// Contract recipient (an AA / Safe / multisig wallet, etc.): pay the reward as WETH. An ERC-20
// transfer has no receiver callback, so it can never revert — a wallet that rejects native ETH
// cannot brick a claimForMany batch. The holder can unwrap the WETH itself.
IERC20(weth).safeTransfer(to, amount);
} else {
IWETH(weth).withdraw(amount); // EOA: unwrap and forward native ETH (best UX), received via receive()
(bool ok,) = payable(to).call{value: amount}("");
if (!ok) revert EthTransferFailed();
}
emit WethClaimed(holder, to, amount);
}
/// @dev Pays `holder`'s accrued reflection tokens to `to` from the token's custodied reflection pool.
/// `_reclassify(holder)` first demotes any now-contract holder (making its withdrawable 0). The claimed
/// total is banked before the tokens move (checks-effects-interactions); the move itself is a plain
/// token transfer (no external call), so there is no reentrancy surface.
function _claimReflectionTo(address holder, address to) private returns (uint256 amount) {
_reclassify(holder);
amount = withdrawableReflection(holder);
if (amount == 0) return 0;
_withdrawnReflection[holder] += amount;
_moveReflectionOut(to, amount);
emit ReflectionClaimed(holder, to, amount);
}
/// @dev Move `amount` tokens out of the custodied reflection pool (address(this), excluded) to `to`,
/// crediting whichever ledger `to` belongs to. Supply-conserving: the pool shrinks and `to` grows by
/// the same amount (`_supply == _totalEligibleTokens + _totalRaw` is preserved).
function _moveReflectionOut(address to, uint256 amount) private {
_raw[address(this)] -= amount;
_totalRaw -= amount;
_reclassify(to);
if (_excluded[to]) {
_raw[to] += amount;
_totalRaw += amount;
} else {
uint256 sh = _tokensToShares(amount); // rate read before mutating the eligible pool
_totalEligibleTokens += amount;
_incShares(to, sh);
}
emit Transfer(address(this), to, amount);
}
// ---------------------------------------------------------------- ledger introspection
function totalShares() external view returns (uint256) {
return _totalShares;
}
function totalEligibleTokens() external view returns (uint256) {
return _totalEligibleTokens;
}
function totalRaw() external view returns (uint256) {
return _totalRaw;
}
function sharesOf(address a) external view returns (uint256) {
return _shares[a];
}
function magWethPerShare() external view returns (uint256) {
return _magWethPerShare;
}
// ================================================================ internals
function _sharesToTokens(uint256 s) private view returns (uint256) {
if (_totalShares == 0) return 0;
return Math.mulDiv(s, _totalEligibleTokens, _totalShares);
}
function _tokensToShares(uint256 t) private view returns (uint256) {
// Bootstrap 1:1 when the elastic pool is empty (first eligible holder sets the rate).
if (_totalEligibleTokens == 0) return t;
return Math.mulDiv(t, _totalShares, _totalEligibleTokens);
}
/// @dev Add `dv` shares to `a`, keeping its WETH-dividend correction consistent. The correction is
/// stored in WETH units (`mulDiv(_magWethPerShare, dv, MAG)`, a 512-bit intermediate) so it can never
/// overflow no matter how large `_magWethPerShare` is. Increasing shares SUBTRACTS (the new shares
/// must not retroactively earn past distributions). Because the base term
/// (`_accumulativeWeth`) floors independently, we round this subtraction UP so every holder's
/// accumulative stays <= its true entitlement — this makes `sum(accumulative) <= distributed` hold
/// EXACTLY (no dust over-credit), which keeps the token provably solvent for dividend claims.
function _incShares(address a, uint256 dv) private {
_shares[a] += dv;
_totalShares += dv;
// Same treatment for both magnified-dividend streams (WETH and reflection tokens): new shares
// must not retroactively earn past distributions, rounded UP so accumulative stays <= entitlement.
_corr[a] -= FullMath.mulDivRoundingUp(_magWethPerShare, dv, MAG).toInt256();
_corrReflection[a] -= FullMath.mulDivRoundingUp(_magReflectionPerShare, dv, MAG).toInt256();
}
/// @dev Remove `dv` shares from `a`, keeping BOTH dividend corrections consistent (WETH + reflection;
/// unit-denominated 512-bit intermediates — cannot overflow). Decreasing shares ADDS back the
/// already-earned slice, rounded DOWN so the credit can never exceed what was truly earned (solvent).
function _decShares(address a, uint256 dv) private {
_shares[a] -= dv;
_totalShares -= dv;
_corr[a] += FullMath.mulDiv(_magWethPerShare, dv, MAG).toInt256();
_corrReflection[a] += FullMath.mulDiv(_magReflectionPerShare, dv, MAG).toInt256();
}
/// @dev Cumulative WETH allocated to `a`, in WETH units. `mulDiv(_magWethPerShare, _shares[a], MAG)`
/// uses a 512-bit intermediate, so however large `_magWethPerShare` has grown the WETH-unit result
/// (bounded by total WETH distributed) fits uint256 — it can never overflow or trip `toInt256`.
function _accumulativeWeth(address a) private view returns (uint256) {
if (_excluded[a]) return 0;
int256 v = int256(FullMath.mulDiv(_magWethPerShare, _shares[a], MAG)) + _corr[a];
return v <= 0 ? 0 : uint256(v);
}
/// @dev Cumulative reflection TOKENS allocated to `a` (same bounded 512-bit math as the WETH stream).
function _accumulativeReflection(address a) private view returns (uint256) {
if (_excluded[a]) return 0;
int256 v = int256(FullMath.mulDiv(_magReflectionPerShare, _shares[a], MAG)) + _corrReflection[a];
return v <= 0 ? 0 : uint256(v);
}
/// @dev Excluded (no reward accrual) = the zero/dead sinks, this token's own reflection pool, and any
/// address the registry marks limit-exempt (PoolManager, routers, hook, launcher, PositionManager —
/// plus any bridge/CEX/farm the operator opts to exclude). Intentionally NO `code.length > 0` rule:
/// smart-contract WALLETS (ERC-4337 AA, Safe, multisig) are real holders and DO earn. `address(this)`
/// MUST stay excluded so the custodied reflection pool never earns its own reflections.
function _isExcludedNow(address a) private view returns (bool) {
return a == address(0) || a == DEAD || a == address(this) || registry.isLimitExempt(a);
}
/// @dev Exempt from the launch-window limits (distinct from the ledger-exclusion set).
function _isExempt(address account) private view returns (bool) {
return account == launcher || account == lifter || registry.isLimitExempt(account);
}
/// @dev If `a`'s live class differs from its stored class, move its whole balance between ledgers at
/// the current rate. Supply-conserving; leaves the WETH-dividend correction exact.
function _reclassify(address a) private {
bool shouldExclude = _isExcludedNow(a);
if (shouldExclude == _excluded[a]) return;
if (shouldExclude) {
// eligible -> excluded: freeze the current elastic balance into raw
uint256 sh = _shares[a];
uint256 bal = _sharesToTokens(sh);
if (sh != 0) _decShares(a, sh);
_totalEligibleTokens -= bal;
_raw[a] += bal;
_totalRaw += bal;
_excluded[a] = true;
} else {
// excluded -> eligible: mint shares for the raw balance at the current rate
uint256 bal = _raw[a];
_totalRaw -= bal;
_raw[a] = 0;
_excluded[a] = false;
uint256 dv = _tokensToShares(bal); // rate BEFORE adding bal to the pool
_incShares(a, dv);
_totalEligibleTokens += bal;
}
emit Reclassified(a, shouldExclude);
}
/// @dev Mint by class of `to`; updates `_supply`. Emits Transfer(0, to).
function _mint(address to, uint256 value) private {
_reclassify(to);
if (_excluded[to]) {
_raw[to] += value;
_totalRaw += value;
} else {
_incShares(to, _tokensToShares(value));
_totalEligibleTokens += value;
}
_supply += value;
emit Transfer(address(0), to, value);
}
/// @dev Burn by class of `from`; updates `_supply`. Emits Transfer(from, 0).
function _burn(address from, uint256 value) private {
_reclassify(from);
if (_excluded[from]) {
uint256 r = _raw[from];
if (value > r) revert ERC20InsufficientBalance(from, r, value);
_raw[from] = r - value;
_totalRaw -= value;
} else {
uint256 bal = _sharesToTokens(_shares[from]);
if (value > bal) revert ERC20InsufficientBalance(from, bal, value);
uint256 dv = _tokensToShares(value);
uint256 fromShares = _shares[from];
if (dv > fromShares) dv = fromShares; // clamp rounding
_decShares(from, dv);
_totalEligibleTokens -= value;
}
_supply -= value;
emit Transfer(from, address(0), value);
}
/// @dev Class-pair transfer with exact share conservation. Reclassifies both parties, enforces the
/// launch-window limits, then routes by (fromClass, toClass) — never a generic burn+mint.
function _transfer(address from, address to, uint256 value) private {
if (from == address(0)) revert ERC20InvalidSender(address(0));
if (to == address(0)) revert ERC20InvalidReceiver(address(0));
_reclassify(from);
_reclassify(to);
// Enforce only on transfers into a non-exempt wallet while limits are active. The hook, launcher,
// and registry-listed infra (PoolManager, routers) are all exempt.
if (!_privileged && limitsActive() && !_isExempt(to)) {
if (maxTxBaseAmount != 0 && value > maxTxBaseAmount) revert MaxTxExceeded();
if (maxWalletBaseAmount != 0 && balanceOf(to) + value > maxWalletBaseAmount) revert MaxWalletExceeded();
}
bool fromEx = _excluded[from];
bool toEx = _excluded[to];
if (!fromEx && !toEx) {
// eligible -> eligible: move shares only; totals unchanged (invariant under reflection).
uint256 bal = _sharesToTokens(_shares[from]);
if (value > bal) revert ERC20InsufficientBalance(from, bal, value);
uint256 dv = _tokensToShares(value);
uint256 fromShares = _shares[from];
if (dv > fromShares) dv = fromShares; // clamp rounding
// Route through _dec/_incShares so BOTH dividend corrections (WETH + reflection) stay exact
// and conservatively rounded (`from` keeps its earned slice floored, `to` never inherits it,
// rounded up). The dec+inc net-zero on totalShares, so the eligible base is unchanged.
_decShares(from, dv);
_incShares(to, dv);
} else if (!fromEx && toEx) {
// eligible -> excluded
uint256 bal = _sharesToTokens(_shares[from]);
if (value > bal) revert ERC20InsufficientBalance(from, bal, value);
uint256 dv = _tokensToShares(value);
uint256 fromShares = _shares[from];
if (dv > fromShares) dv = fromShares;
_decShares(from, dv);
_totalEligibleTokens -= value;
_raw[to] += value;
_totalRaw += value;
} else if (fromEx && !toEx) {
// excluded -> eligible
uint256 r = _raw[from];
if (value > r) revert ERC20InsufficientBalance(from, r, value);
_raw[from] = r - value;
_totalRaw -= value;
_incShares(to, _tokensToShares(value)); // rate BEFORE the add below
_totalEligibleTokens += value;
} else {
// excluded -> excluded
uint256 r = _raw[from];
if (value > r) revert ERC20InsufficientBalance(from, r, value);
_raw[from] = r - value;
_raw[to] += value;
}
emit Transfer(from, to, value);
}
function _spendAllowance(address owner, address spender, uint256 value) private {
uint256 current = _allowances[owner][spender];
if (current != type(uint256).max) {
if (current < value) revert ERC20InsufficientAllowance(spender, current, value);
unchecked {
_allowances[owner][spender] = current - value;
}
}
}
}
Chain explorer2586msChain node71ms