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
- Creator
- 0x26D9E20b26…2630198418
- Creation tx
- 0x871e772fc2…20dfed5557
Token
- Name
- CASHKITTEN
- Symbol
- CASHKITTEN
- Decimals
- 18
- Holders
- 66
- Market
- View token page →
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