RewardsCoin

0xea58e3f89892cac6b6c9e1519c6acb29e6e87931

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
12,648 bytes
ABI entries
67
27 read · 18 write
License
none

Contract information

Address
0xea58e3f89892cac6b6c9e1519c6acb29e6e87931
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x871e772fc2…20dfed5557

Token

Name
CASHKITTEN
Symbol
CASHKITTEN
Decimals
18
Holders
66

Read contract (27)

PERMIT2()address
POOL_MANAGER()address
POSITION_MANAGER()address
accumulativeDividendOf(address)uint256
allowance(address, address)uint256
balanceOf(address)uint256
currentIndex()uint256
decimals()uint8
dividendRecipient(address)address
excludedBalance()uint256
excludedFromDividends(address)bool
holderIndex(address)uint256
holders(uint256)address
hook()address
launched()bool
magnifiedDividendPerShare()uint256
minDistribution()uint256
name()string
owner()address
processGas()uint256
rewardToken()address
symbol()string
taxExempt(address)bool
totalDividendsDistributed()uint256
totalSupply()uint256
withdrawableDividendOf(address)uint256
withdrawnDividends(address)uint256

Events (14)

ApprovalDividendClaimedDividendRecipientUpdatedDividendsDistributedEmergencyWithdrawExcludedFromDividendsUpdatedHookSetLaunchedMinDistributionUpdatedOwnershipTransferredProcessGasUpdatedRewardTokenSetTaxExemptUpdatedTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "permit2_",
        "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"
  },
  {
    "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": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "DividendClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "DividendRecipientUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "DividendsDistributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "EmergencyWithdraw",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "addr",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "excluded",
        "type": "bool"
      }
    ],
    "name": "ExcludedFromDividendsUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "hook",
        "type": "address"
      }
    ],
    "name": "HookSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      }
    ],
    "name": "Launched",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newMin",
        "type": "uint256"
      }
    ],
    "name": "MinDistributionUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newGas",
        "type": "uint256"
      }
    ],
    "name": "ProcessGasUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "rewardToken",
        "type": "address"
      }
    ],
    "name": "RewardTokenSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "addr",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "isExempt",
        "type": "bool"
      }
    ],
    "name": "TaxExemptUpdated",
    "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"
  },
  {
    "inputs": [],
    "name": "PERMIT2",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_MANAGER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POSITION_MANAGER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      }
    ],
    "name": "accumulativeDividendOf",
    "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": [],
    "name": "claim",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "currentIndex",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "distributeRewards",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "dividendRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "emergencyWithdrawETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "excludedBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "excludedFromDividends",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "holderIndex",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "holders",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hook",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launched",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "magnifiedDividendPerShare",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "minDistribution",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "gasBudget",
        "type": "uint256"
      }
    ],
    "name": "process",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "processGas",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "provideLiquidity",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_index",
        "type": "uint256"
      }
    ],
    "name": "setCurrentIndex",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setDividendRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "addr",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "excluded",
        "type": "bool"
      }
    ],
    "name": "setExcludedFromDividends",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_hook",
        "type": "address"
      }
    ],
    "name": "setHook",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_min",
        "type": "uint256"
      }
    ],
    "name": "setMinDistribution",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_gas",
        "type": "uint256"
      }
    ],
    "name": "setProcessGas",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_rewardToken",
        "type": "address"
      }
    ],
    "name": "setRewardToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "addr",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "isExempt",
        "type": "bool"
      }
    ],
    "name": "setTaxExempt",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "addrs",
        "type": "address[]"
      },
      {
        "internalType": "bool",
        "name": "isExempt",
        "type": "bool"
      }
    ],
    "name": "setTaxExemptBatch",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "taxExempt",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalDividendsDistributed",
    "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": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      }
    ],
    "name": "withdrawableDividendOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "withdrawnDividends",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol";

interface IAllowanceTransfer {
    function approve(address token, address spender, uint160 amount, uint48 expiration) external;
}

interface IPositionManager {
    function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable;
}

interface IERC20Minimal {
    function balanceOf(address) external view returns (uint256);
}

/// @notice ERC20 with pull-based reward-coin dividends and a baked-in V4
///         self-launch. The hook buys `rewardToken` with collected ETH fees
///         and pushes it in via `distributeRewards`; holders accrue the
///         reward coin proportional to balance. `provideLiquidity()`
///         initializes the pool and burns the LP NFT.
contract RewardsCoin is ERC20 {
    // ── Roles ─────────────────────────────────────────────────────────────────
    address public owner;
    address public hook;

    /// @notice The ERC20 that dividends are paid in (bought by the hook).
    address public rewardToken;

    // ── V4 wiring (set by constructor) ───────────────────────────────────────
    address public immutable POOL_MANAGER;
    address public immutable POSITION_MANAGER;
    address public immutable PERMIT2;

    // ── Hardcoded launch config ──────────────────────────────────────────────
    uint160 internal constant LAUNCH_SQRT_PRICE_X96 = 2505411999795360582221170761428213;
    int24   internal constant LAUNCH_TICK_LOWER = -887200;
    int24   internal constant LAUNCH_TICK_UPPER = 207200;
    uint256 internal constant LAUNCH_AMOUNT0 = 0;
    uint256 internal constant LAUNCH_AMOUNT1 = 1_000_000_000 * 10**18;

    address internal constant DEAD = 0x000000000000000000000000000000000000dEaD;
    uint24  internal constant LP_FEE = 0;
    int24   internal constant TICK_SPACING = 200;
    uint8   internal constant ACTION_MINT_POSITION = 0x02;
    uint8   internal constant ACTION_SETTLE_PAIR   = 0x0d;

    bool public launched;

    // ── Dividend accounting ───────────────────────────────────────────────────
    uint256 internal constant MAGNITUDE = 2 ** 128;
    uint256 public magnifiedDividendPerShare;
    uint256 public totalDividendsDistributed;
    uint256 public excludedBalance;

    mapping(address => int256)  internal magnifiedCorrections;
    mapping(address => uint256) public  withdrawnDividends;
    mapping(address => bool)    public  excludedFromDividends;

    mapping(address => bool) public taxExempt;

    // ── Auto-distribution (cursor-based push to all holders) ─────────────────
    address[] public holders;
    mapping(address => uint256) public holderIndex;     // 1-indexed; 0 = not a holder
    mapping(address => address) public dividendRecipient; // optional override per holder
    uint256 public currentIndex;                        // cursor
    uint256 public processGas       = 500_000;          // gas budget per process() call
    uint256 public minDistribution  = 1e13;             // skip pushes below this (in rewardToken units)
    bool    private _processing;                        // reentrancy guard for process()

    // ── Events ────────────────────────────────────────────────────────────────
    event DividendsDistributed(address indexed from, uint256 amount);
    event DividendClaimed(address indexed holder, uint256 amount);
    event ExcludedFromDividendsUpdated(address indexed addr, bool excluded);
    event TaxExemptUpdated(address indexed addr, bool isExempt);
    event HookSet(address indexed hook);
    event RewardTokenSet(address indexed rewardToken);
    event OwnershipTransferred(address indexed from, address indexed to);
    event EmergencyWithdraw(address indexed to, uint256 amount);
    event Launched(uint128 liquidity);
    event ProcessGasUpdated(uint256 newGas);
    event MinDistributionUpdated(uint256 newMin);
    event DividendRecipientUpdated(address indexed holder, address indexed recipient);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    constructor(
        string memory name_,
        string memory symbol_,
        address poolManager_,
        address positionManager_,
        address permit2_
    ) ERC20(name_, symbol_) {
        owner = msg.sender;
        POOL_MANAGER = poolManager_;
        POSITION_MANAGER = positionManager_;
        PERMIT2 = permit2_;

        if (poolManager_ != address(0))     _markExcluded(poolManager_, true);
        if (positionManager_ != address(0)) _markExcluded(positionManager_, true);
        if (permit2_ != address(0))         _markExcluded(permit2_, true);
        _markExcluded(DEAD, true);
        _markExcluded(address(this), true);

        _mint(address(this), LAUNCH_AMOUNT1);
    }

    // ─── Owner config ─────────────────────────────────────────────────────────

    function setHook(address _hook) external onlyOwner {
        require(_hook != address(0), "Zero address");
        require(hook == address(0), "Hook already set");
        hook = _hook;
        if (!excludedFromDividends[_hook] && balanceOf(_hook) == 0) {
            _markExcluded(_hook, true);
        }
        emit HookSet(_hook);
    }

    /// @notice One-time: the ERC20 dividends are paid in. Set-once because the
    ///         whole dividend ledger (per-share accumulator, withdrawn amounts)
    ///         is denominated in this token.
    function setRewardToken(address _rewardToken) external onlyOwner {
        require(_rewardToken != address(0), "Zero address");
        require(_rewardToken != address(this), "Cannot be self");
        require(rewardToken == address(0), "Reward token already set");
        rewardToken = _rewardToken;
        emit RewardTokenSet(_rewardToken);
    }

    // ─── Self-launch ─────────────────────────────────────────────────────────

    /// @notice Initialize the ETH/<this> V4 pool and burn the LP NFT.
    function provideLiquidity() external onlyOwner {
        require(!launched, "Already launched");
        require(hook != address(0), "Hook not set");
        require(POOL_MANAGER != address(0) && POSITION_MANAGER != address(0) && PERMIT2 != address(0), "V4 not configured");
        launched = true;

        _approve(address(this), PERMIT2, type(uint256).max);
        IAllowanceTransfer(PERMIT2).approve(address(this), POSITION_MANAGER, type(uint160).max, type(uint48).max);
        IAllowanceTransfer(PERMIT2).approve(address(this), POOL_MANAGER,     type(uint160).max, type(uint48).max);

        PoolKey memory pool = PoolKey({
            currency0: Currency.wrap(address(0)),
            currency1: Currency.wrap(address(this)),
            fee: LP_FEE,
            tickSpacing: TICK_SPACING,
            hooks: IHooks(hook)
        });
        IPoolManager(POOL_MANAGER).initialize(pool, LAUNCH_SQRT_PRICE_X96);

        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
            LAUNCH_SQRT_PRICE_X96,
            TickMath.getSqrtPriceAtTick(LAUNCH_TICK_LOWER),
            TickMath.getSqrtPriceAtTick(LAUNCH_TICK_UPPER),
            LAUNCH_AMOUNT0,
            LAUNCH_AMOUNT1
        );
        require(liquidity > 0, "Zero liquidity");

        bytes memory actions = abi.encodePacked(ACTION_MINT_POSITION, ACTION_SETTLE_PAIR);
        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(
            pool,
            LAUNCH_TICK_LOWER,
            LAUNCH_TICK_UPPER,
            liquidity,
            LAUNCH_AMOUNT0,
            LAUNCH_AMOUNT1,
            owner,
            bytes("")
        );
        params[1] = abi.encode(pool.currency0, pool.currency1);

        IPositionManager(POSITION_MANAGER).modifyLiquidities{value: LAUNCH_AMOUNT0}(
            abi.encode(actions, params),
            block.timestamp + 120
        );

        emit Launched(liquidity);
    }

    function setExcludedFromDividends(address addr, bool excluded) external onlyOwner {
        require(excludedFromDividends[addr] != excluded, "no change");

        uint256 bal = balanceOf(addr);
        if (excluded) {
            // Refuse to silently nuke unclaimed dividends.
            require(withdrawableDividendOf(addr) == 0, "claim first");
            excludedBalance += bal;
        } else {
            excludedBalance -= bal;
            // Reset accounting so future DPS growth credits this balance.
            magnifiedCorrections[addr] = -int256(magnifiedDividendPerShare * bal);
            withdrawnDividends[addr] = 0;
        }
        excludedFromDividends[addr] = excluded;
        _trackHolder(addr);
        emit ExcludedFromDividendsUpdated(addr, excluded);
    }

    function setTaxExempt(address addr, bool isExempt) external onlyOwner {
        taxExempt[addr] = isExempt;
        emit TaxExemptUpdated(addr, isExempt);
    }

    function setProcessGas(uint256 _gas) external onlyOwner {
        require(_gas <= 1_000_000, "Too high");
        processGas = _gas;
        emit ProcessGasUpdated(_gas);
    }

    function setMinDistribution(uint256 _min) external onlyOwner {
        minDistribution = _min;
        emit MinDistributionUpdated(_min);
    }

    /// @notice Manually move the rotation cursor. Pass 0 to restart from the
    ///         beginning, or any index up to holders.length to skip ahead.
    function setCurrentIndex(uint256 _index) external onlyOwner {
        require(_index <= holders.length, "Out of range");
        currentIndex = _index;
    }

    function setTaxExemptBatch(address[] calldata addrs, bool isExempt) external onlyOwner {
        for (uint256 i = 0; i < addrs.length; i++) {
            taxExempt[addrs[i]] = isExempt;
            emit TaxExemptUpdated(addrs[i], isExempt);
        }
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /// @notice Pulls stray ETH. Dividends are paid in the reward token, so any
    ///         ETH here is unclaimable by holders. Does not touch dividend ledgers.
    function emergencyWithdrawETH(address to) external onlyOwner {
        require(to != address(0), "Zero address");
        uint256 bal = address(this).balance;
        require(bal > 0, "Nothing to withdraw");
        (bool ok, ) = payable(to).call{value: bal}("");
        require(ok, "Transfer failed");
        emit EmergencyWithdraw(to, bal);
    }

    // ─── Dividend math ────────────────────────────────────────────────────────

    function accumulativeDividendOf(address holder) public view returns (uint256) {
        if (excludedFromDividends[holder]) return 0;
        int256 m = int256(magnifiedDividendPerShare * balanceOf(holder)) + magnifiedCorrections[holder];
        if (m < 0) return 0;
        return uint256(m) / MAGNITUDE;
    }

    function withdrawableDividendOf(address holder) public view returns (uint256) {
        uint256 acc = accumulativeDividendOf(holder);
        uint256 wd = withdrawnDividends[holder];
        return acc > wd ? acc - wd : 0;
    }

    function claim() external returns (uint256) {
        uint256 amount = withdrawableDividendOf(msg.sender);
        if (amount == 0) return 0;
        address recipient = dividendRecipient[msg.sender];
        if (recipient == address(0)) recipient = msg.sender;
        withdrawnDividends[msg.sender] += amount;
        emit DividendClaimed(msg.sender, amount);
        require(_sendReward(recipient, amount), "Send failed");
        return amount;
    }

    /// @notice Set an alternate address to receive your dividends. Useful when
    ///         the holder is a contract that can't hold the reward token.
    ///         Permissionless — only your own slot, no admin involvement.
    function setDividendRecipient(address recipient) external {
        dividendRecipient[msg.sender] = recipient;
        emit DividendRecipientUpdated(msg.sender, recipient);
    }

    /// @dev Non-reverting reward-token transfer. Tolerates tokens that return
    ///      nothing (USDT-style) as well as standard bool returners.
    function _sendReward(address to, uint256 amount) internal returns (bool) {
        if (rewardToken == address(0)) return false;
        (bool ok, bytes memory ret) = rewardToken.call(
            abi.encodeWithSignature("transfer(address,uint256)", to, amount)
        );
        return ok && (ret.length == 0 || (ret.length >= 32 && abi.decode(ret, (bool))));
    }

    /// @dev Best-effort push. If the transfer fails, the ledger rolls back so
    ///      the holder can still claim() manually — never reverts the caller.
    function _tryPush(address holder) internal {
        uint256 amount = withdrawableDividendOf(holder);
        if (amount < minDistribution) return;
        address recipient = dividendRecipient[holder];
        if (recipient == address(0)) recipient = holder;
        withdrawnDividends[holder] += amount;
        if (!_sendReward(recipient, amount)) {
            withdrawnDividends[holder] -= amount;
            return;
        }
        emit DividendClaimed(holder, amount);
    }

    /// @notice Cursor-based push to all holders. Iterates `holders[]` from
    ///         `currentIndex` and pushes pending dividends to each, until the
    ///         gas budget is consumed. Cursor wraps around — over enough
    ///         calls, every holder gets paid passively.
    /// @dev    External so it can be called via try/catch from `_update`,
    ///         which isolates OOG from the parent transfer. Permissionless;
    ///         keeper bots / frontends can prod the rotation manually.
    function process(uint256 gasBudget) external {
        if (_processing) return;
        _processing = true;

        uint256 n = holders.length;
        if (n != 0 && gasBudget != 0) {
            uint256 gasLeft = gasleft();
            uint256 gasUsed = 0;
            uint256 iterations = 0;
            uint256 idx = currentIndex;

            while (gasUsed < gasBudget && iterations < n) {
                if (idx >= n) idx = 0;
                _tryPush(holders[idx]);
                unchecked {
                    gasUsed += gasLeft - gasleft();
                    gasLeft = gasleft();
                    idx++;
                    iterations++;
                }
            }
            currentIndex = idx;
        }

        _processing = false;
    }

    function _trackHolder(address h) internal {
        if (h == address(0)) return;
        bool isHolder = !excludedFromDividends[h] && balanceOf(h) > 0;
        bool tracked  = holderIndex[h] != 0;

        if (isHolder && !tracked) {
            holders.push(h);
            holderIndex[h] = holders.length;
        } else if (!isHolder && tracked) {
            uint256 idx = holderIndex[h] - 1;
            uint256 lastIdx = holders.length - 1;
            if (idx != lastIdx) {
                address last = holders[lastIdx];
                holders[idx] = last;
                holderIndex[last] = idx + 1;
            }
            holders.pop();
            holderIndex[h] = 0;
        }
    }

    // ─── Distribution intake ──────────────────────────────────────────────────

    /// @notice Pull `amount` of the reward token from the caller and credit it
    ///         to all eligible holders pro-rata. The hook calls this after
    ///         each buy; permissionless, so anyone can donate rewards too.
    ///         Requires prior approval. Credits the amount actually received,
    ///         so fee-on-transfer reward tokens stay solvent.
    function distributeRewards(uint256 amount) external {
        require(rewardToken != address(0), "Reward token not set");
        require(amount > 0, "Zero amount");
        uint256 eligible = totalSupply() - excludedBalance;
        require(eligible > 0, "No eligible supply");

        uint256 balBefore = IERC20Minimal(rewardToken).balanceOf(address(this));
        (bool ok, bytes memory ret) = rewardToken.call(
            abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, address(this), amount)
        );
        require(ok && (ret.length == 0 || (ret.length >= 32 && abi.decode(ret, (bool)))), "Pull failed");
        uint256 received = IERC20Minimal(rewardToken).balanceOf(address(this)) - balBefore;
        require(received > 0, "Nothing received");

        magnifiedDividendPerShare += (received * MAGNITUDE) / eligible;
        totalDividendsDistributed += received;
        emit DividendsDistributed(msg.sender, received);
    }

    /// @dev Dividends are paid in the reward token now — any ETH sent here
    ///      just parks until the owner rescues it via emergencyWithdrawETH.
    receive() external payable {}

    // ─── ERC20 transfer hook ──────────────────────────────────────────────────

    function _update(address from, address to, uint256 value) internal override {
        super._update(from, to, value);

        bool fromExcluded = from == address(0) || excludedFromDividends[from];
        bool toExcluded   = to   == address(0) || excludedFromDividends[to];

        // Defensive int256 cast: 0.8.x reverts on uint256 overflow naturally,
        // but a value between 2^255 and 2^256 would silently wrap to a negative
        // int256 and corrupt every holder's correction. Catch that explicitly.
        uint256 magShiftU = magnifiedDividendPerShare * value;
        require(magShiftU <= uint256(type(int256).max), "shift overflow");
        int256 magShift = int256(magShiftU);
        if (!fromExcluded) magnifiedCorrections[from] += magShift;
        if (!toExcluded)   magnifiedCorrections[to]   -= magShift;

        if (from == address(0)) {
            if (excludedFromDividends[to]) excludedBalance += value;
        } else if (to == address(0)) {
            if (excludedFromDividends[from]) excludedBalance -= value;
        } else if (excludedFromDividends[from] != excludedFromDividends[to]) {
            if (excludedFromDividends[from]) excludedBalance -= value;
            else excludedBalance += value;
        }

        // Maintain holders list based on the new balances.
        _trackHolder(from);
        _trackHolder(to);

        // Active trader gets paid instantly.
        if (!fromExcluded) _tryPush(from);
        if (!toExcluded)   _tryPush(to);

        // Cursor-based rotation pays everyone else passively. Routed through
        // an external try/catch so an OOG inside process() can't revert the
        // transfer — the worst case is "this swap didn't rotate the cursor".
        if (processGas > 0 && holders.length > 0) {
            try this.process(processGas) {} catch {}
        }
    }

    function _markExcluded(address addr, bool excluded) internal {
        excludedFromDividends[addr] = excluded;
        emit ExcludedFromDividendsUpdated(addr, excluded);
    }
}
Chain explorer1992msChain node78ms