PadQuiverDistributor

0xe94588a1944c1f91fd2488e3b4821e66584a1781

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
11,575 bytes
ABI entries
47
33 read · 7 write
License
none

Contract information

Address
0xe94588a1944c1f91fd2488e3b4821e66584a1781
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x3abdd2dbb7…427bd8351c

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (33)

FEE_DENOMINATOR()uint256
WEBSITE()string
accumulativeDividendOf(address)uint256
callerTipBps()uint256
excludedFromDividends(address)bool
factory()address
holderCount()uint256
lastProcessedIndex()uint256
magnifiedDividendPerShare()uint256
maxSlippageBps()uint256
minimumBalanceForDividends()uint256
onERC721Received(address, address, uint256, bytes)bytes4
pendingDistribution()uint256
platformFeeBps()uint256
platformWallet()address
pool()address
poolFee()uint24
positionManager()address
positionTokenId()uint256
rewardPool()address
rewardPoolFee()uint24
rewardToken()address
shares(address)uint256
swapRouter()address
token()address
tokenIsToken0()bool
totalDividendsDistributed()uint256
totalDividendsWithdrawn()uint256
totalShares()uint256
twapWindow()uint32
weth()address
withdrawableDividendOf(address)uint256
withdrawnDividends(address)uint256

Events (4)

DividendWithdrawFailedDividendWithdrawnDividendsDistributedHarvested

ABI

[
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "factory",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "swapRouter",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "weth",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "rewardToken",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pool",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "rewardPool",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "poolFee",
            "type": "uint24"
          },
          {
            "internalType": "uint24",
            "name": "rewardPoolFee",
            "type": "uint24"
          },
          {
            "internalType": "bool",
            "name": "tokenIsToken0",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "platformWallet",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "platformFeeBps",
            "type": "uint256"
          },
          {
            "internalType": "uint32",
            "name": "twapWindow",
            "type": "uint32"
          },
          {
            "internalType": "uint256",
            "name": "maxSlippageBps",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "callerTipBps",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "minimumBalanceForDividends",
            "type": "uint256"
          }
        ],
        "internalType": "struct PadQuiverDistributor.Config",
        "name": "c",
        "type": "tuple"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "DividendWithdrawFailed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "DividendWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "DividendsDistributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenCollected",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethCollected",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "platformCut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "callerTip",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "rewardsDistributed",
        "type": "uint256"
      }
    ],
    "name": "Harvested",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "FEE_DENOMINATOR",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WEBSITE",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "accumulativeDividendOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "callerTipBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claim",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "excluded",
        "type": "bool"
      }
    ],
    "name": "excludeFromDividends",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "excludedFromDividends",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "harvest",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "rewardsDistributed",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "holderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "initialize",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastProcessedIndex",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "magnifiedDividendPerShare",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxSlippageBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "minimumBalanceForDividends",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingDistribution",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platformFeeBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platformWallet",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolFee",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "gasLimit",
        "type": "uint256"
      }
    ],
    "name": "process",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "iterations",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "claims",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "processAccount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardPoolFee",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      }
    ],
    "name": "setShare",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "shares",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "contract ISwapRouter",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "token",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenIsToken0",
    "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": "totalDividendsWithdrawn",
    "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": "twapWindow",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "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"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {INonfungiblePositionManager, ISwapRouter} from "./interfaces/IUniswapV3.sol";
import {V3Twap} from "./libraries/V3Twap.sol";

interface IPadQuiverFactory {
    function owner() external view returns (address);
}

/**
 * @title PadQuiverDistributor
 * @author PadQuiver Launchpad — https://padquiver.com/
 * @custom:website https://padquiver.com/
 * @notice The reward engine for a PadQuiverToken. Holds the position NFT (liquidity
 *         PERMANENTLY LOCKED). BOT-FREE / OPERATOR-FREE design:
 *         - harvest() is PUBLIC (no keeper).
 *         - Swaps compute the minimum output THEMSELVES from the pool's TWAP => any atomic
 *           self-sandwich PROFIT is BOUNDED by ~maxSlippageBps (not zero); no trusted
 *           keeper is needed. (On thin reward pools the window/tolerance matter.)
 *         - A small tip is paid to whoever triggers harvest => the market triggers it, no bot needed.
 *         - There is no owner/admin (the platform can only perform secondary-pool exclusion).
 *         Reward accounting follows the CCAT DividendTracker (saturating pending, skip-if-fail).
 */
contract PadQuiverDistributor is ReentrancyGuard, IERC721Receiver {
    using SafeERC20 for IERC20;

    /// @notice Official PadQuiver launchpad website (readable on-chain).
    string public constant WEBSITE = "https://padquiver.com/";

    uint256 private constant MAGNITUDE = 2 ** 128;
    uint256 public constant FEE_DENOMINATOR = 10_000;
    address private constant DEAD = 0x000000000000000000000000000000000000dEaD;

    INonfungiblePositionManager public immutable positionManager;
    ISwapRouter public immutable swapRouter;
    address public immutable token;
    address public immutable weth;
    address public immutable rewardToken;
    address public immutable pool; // token/WETH pool
    address public immutable rewardPool; // WETH/rewardToken pool (0 if rewardToken == WETH)
    uint24 public immutable poolFee;
    uint24 public immutable rewardPoolFee;
    bool public immutable tokenIsToken0;

    address public immutable platformWallet;
    uint256 public immutable platformFeeBps;
    address public immutable factory; // platform admin (secondary-pool exclusion) comes from here
    uint32 public immutable twapWindow; // seconds
    uint256 public immutable maxSlippageBps; // allowed deviation from TWAP
    uint256 public immutable callerTipBps; // reward to whoever calls harvest
    // Reward threshold: only holders ABOVE this balance earn rewards (creator-chosen).
    // Low = everyone; high = only large holders. Uses the trustless magnified model.
    uint256 public immutable minimumBalanceForDividends;

    uint256 public positionTokenId;
    bool private _initialized;

    uint256 public totalShares;
    uint256 public magnifiedDividendPerShare;
    uint256 public totalDividendsDistributed;
    uint256 public totalDividendsWithdrawn;

    mapping(address => uint256) public shares;
    mapping(address => int256) private magnifiedDividendCorrections;
    mapping(address => uint256) public withdrawnDividends;
    mapping(address => bool) public excludedFromDividends;

    address[] private holders;
    mapping(address => uint256) private holderIndexPlusOne;
    uint256 public lastProcessedIndex;

    // Post-fee WETH from a harvest whose reward-swap couldn't complete (already taxed) — carried, never re-taxed.
    uint256 private taxedWethPending;
    // Reward tokens genuinely produced by harvests but not yet distributed (no eligible holders yet).
    // Tracked explicitly so a DONATION of the reward token can never be counted as reward / inflate perShare.
    uint256 private pendingReward;
    // Collected token whose token->WETH swap couldn't complete this harvest — carried and retried next
    // time (symmetric with taxedWethPending) so a partial/chunked swap never permanently strands fees.
    uint256 private tokenPending;

    event Harvested(uint256 tokenCollected, uint256 wethCollected, uint256 platformCut, uint256 callerTip, uint256 rewardsDistributed);
    event DividendsDistributed(uint256 amount);
    event DividendWithdrawn(address indexed account, uint256 amount);
    event DividendWithdrawFailed(address indexed account, uint256 amount);

    modifier onlyToken() {
        require(msg.sender == token, "RRD: only token");
        _;
    }

    struct Config {
        address factory; // the real PadQuiverFactory (not the deployer)
        address token;
        address positionManager;
        address swapRouter;
        address weth;
        address rewardToken;
        address pool;
        address rewardPool;
        uint24 poolFee;
        uint24 rewardPoolFee;
        bool tokenIsToken0;
        address platformWallet;
        uint256 platformFeeBps;
        uint32 twapWindow;
        uint256 maxSlippageBps;
        uint256 callerTipBps;
        uint256 minimumBalanceForDividends;
    }

    constructor(Config memory c) {
        require(c.token != address(0) && c.rewardToken != address(0) && c.pool != address(0), "RRD: zero");
        require(c.platformFeeBps <= 2_000, "RRD: platform fee too high");
        require(c.maxSlippageBps <= 1_000, "RRD: slippage too high"); // <= 10%
        require(c.callerTipBps <= 100, "RRD: tip too high"); // <= 1%
        require(c.twapWindow >= 60 && c.twapWindow <= 3600, "RRD: bad window");
        require(c.factory != address(0), "RRD: zero factory");
        factory = c.factory;
        token = c.token;
        positionManager = INonfungiblePositionManager(c.positionManager);
        swapRouter = ISwapRouter(c.swapRouter);
        weth = c.weth;
        rewardToken = c.rewardToken;
        pool = c.pool;
        rewardPool = c.rewardPool;
        poolFee = c.poolFee;
        rewardPoolFee = c.rewardPoolFee;
        tokenIsToken0 = c.tokenIsToken0;
        platformWallet = c.platformWallet;
        platformFeeBps = c.platformFeeBps;
        twapWindow = c.twapWindow;
        maxSlippageBps = c.maxSlippageBps;
        callerTipBps = c.callerTipBps;
        minimumBalanceForDividends = c.minimumBalanceForDividends;

        excludedFromDividends[c.pool] = true;
        excludedFromDividends[c.positionManager] = true;
        excludedFromDividends[c.swapRouter] = true;
        excludedFromDividends[address(this)] = true;
        excludedFromDividends[c.factory] = true;
        excludedFromDividends[address(0)] = true;
        excludedFromDividends[DEAD] = true;
    }

    function initialize(uint256 tokenId) external {
        require(msg.sender == factory, "RRD: only factory");
        require(!_initialized, "RRD: initialized");
        require(positionManager.ownerOf(tokenId) == address(this), "RRD: not position owner");
        _initialized = true;
        positionTokenId = tokenId;
    }

    /// @notice Excludes addresses that shouldn't earn rewards, such as secondary pools/contracts.
    ///         Only the PLATFORM (factory owner) — NOT the customer (this prevents a rug).
    ///         SECURITY (M1): only CONTRACT addresses can be excluded; the owner cannot exclude
    ///         real (EOA) holders and redirect the rewards to a single wallet.
    function excludeFromDividends(address account, bool excluded) external {
        require(msg.sender == IPadQuiverFactory(factory).owner(), "RRD: only platform");
        // System addresses are PERMANENTLY excluded in the constructor and can NEVER be toggled. This
        // stops an owner (by accident or malice) re-including the pool — which would balloon totalShares
        // to the whole supply and permanently strand every holder's rewards in the pool address.
        require(
            account != pool && account != address(positionManager) && account != address(swapRouter)
                && account != factory && account != address(this) && account != DEAD && account != address(0),
            "RRD: system address is permanently excluded"
        );
        require(!excluded || account.code.length > 0, "RRD: can only exclude contracts");
        excludedFromDividends[account] = excluded;
        if (excluded && shares[account] > 0) {
            uint256 oldShare = shares[account];
            magnifiedDividendCorrections[account] += int256(magnifiedDividendPerShare * oldShare);
            totalShares -= oldShare;
            shares[account] = 0;
            _removeHolder(account);
        }
    }

    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    // ---------------------------------------------------------------------
    // Harvest — PUBLIC, TWAP-protected, tips the caller
    // ---------------------------------------------------------------------

    /// @notice Collects the accrued LP fees, converts them into the reward coin via
    ///         TWAP-protected swaps, tips the caller, and distributes to holders. No bot needed.
    function harvest() external nonReentrant returns (uint256 rewardsDistributed) {
        require(_initialized, "RRD: not initialized");

        (uint256 amount0, uint256 amount1) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: positionTokenId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );
        uint256 tokenCollected = tokenIsToken0 ? amount0 : amount1;
        uint256 wethCollected = tokenIsToken0 ? amount1 : amount0;

        // 1) Convert ONLY the token actually COLLECTED into WETH. Bounding by tokenCollected (not
        //    balanceOf) means a donated token balance can neither be counted as reward nor force an
        //    oversized swap; the swap is chunked so a huge amount can never "revert forever".
        uint256 swapTok = tokenCollected + tokenPending; // + token a prior harvest couldn't swap yet
        uint256 tokBal = IERC20(token).balanceOf(address(this));
        if (swapTok > tokBal) swapTok = tokBal; // (also caps at balance => a token donation is excluded)
        uint256 wethFromToken;
        if (swapTok > 0) {
            uint256 tokUsed;
            (tokUsed, wethFromToken) = _swapTwapProtected(pool, token, weth, poolFee, swapTok);
            tokenPending = swapTok - tokUsed; // unswapped collected token carried to the next harvest
        } else {
            tokenPending = 0;
        }
        uint256 newWeth = wethCollected + wethFromToken; // WETH genuinely produced this harvest

        // 2) Platform fee ONLY on newly-produced WETH (carried/donated WETH is never re-taxed), then
        //    turn the post-fee WETH into the reward token.
        // No platform wallet => take NO cut at all (otherwise it would be subtracted from rewards but
        // never transferred, stranding that WETH). With a wallet, fee is on newly-produced WETH only.
        uint256 platformCut = platformWallet == address(0) ? 0 : (newWeth * platformFeeBps) / FEE_DENOMINATOR;
        if (platformCut > 0) {
            IERC20(weth).safeTransfer(platformWallet, platformCut);
        }
        uint256 producedReward;
        if (rewardToken == weth) {
            producedReward = newWeth - platformCut;
        } else {
            uint256 toSwap = (newWeth - platformCut) + taxedWethPending; // + WETH carried from a prior failed swap
            uint256 wethUsed;
            if (toSwap > 0) {
                (wethUsed, producedReward) = _swapTwapProtected(rewardPool, weth, rewardToken, rewardPoolFee, toSwap);
            }
            taxedWethPending = toSwap - wethUsed; // unswapped post-fee WETH carried to next harvest (already taxed)
        }

        // 3) Distribute ONLY the reward genuinely produced (+ any held from a no-holders window). A
        //    donation of the reward token is NOT counted here, so it can never inflate perShare.
        uint256 distributable = producedReward + pendingReward;
        pendingReward = 0;
        uint256 tip;
        if (distributable > 0 && totalShares > 0) {
            tip = (distributable * callerTipBps) / FEE_DENOMINATOR;
            if (tip > 0) IERC20(rewardToken).safeTransfer(msg.sender, tip);
            rewardsDistributed = distributable - tip;
            magnifiedDividendPerShare += (rewardsDistributed * MAGNITUDE) / totalShares;
            totalDividendsDistributed += rewardsDistributed;
            emit DividendsDistributed(rewardsDistributed);
        } else if (distributable > 0) {
            // no eligible holders yet: hold it for the next harvest (not lost, not re-taxed, no tip)
            pendingReward = distributable;
        }
        emit Harvested(tokenCollected, wethCollected, platformCut, tip, rewardsDistributed);
    }

    /// @dev Computes minOut from the pool's TWAP (time-weighted => resistant to atomic
    ///      manipulation). If there isn't enough oracle history, consultTick reverts (harvest
    ///      is too early; it's retried later). If the swap reverts due to price deviation, the
    ///      revert is swallowed, the token stays inside, and the next harvest retries.
    function _swapTwapProtected(address twapPool, address tokenIn, address tokenOut, uint24 fee, uint256 amountIn)
        private
        returns (uint256 amountConsumed, uint256 amountOut)
    {
        uint256 attempt = amountIn;
        // Try the full amount; if the TWAP-bounded swap reverts (price deviation, or the amount is too
        // large for the pool within the slippage cap — e.g. a griefer's oversized donation), halve and
        // retry. Whatever swaps, swaps; the unswapped remainder is carried to the next harvest, so a
        // too-large balance can never make harvest "revert forever".
        for (uint256 i = 0; i < 6; i++) {
            if (attempt == 0) break;
            uint256 minOut = V3Twap.minOutFromTwap(twapPool, twapWindow, attempt, tokenIn, tokenOut, maxSlippageBps);
            IERC20(tokenIn).forceApprove(address(swapRouter), attempt);
            try swapRouter.exactInputSingle(
                ISwapRouter.ExactInputSingleParams({
                    tokenIn: tokenIn,
                    tokenOut: tokenOut,
                    fee: fee,
                    recipient: address(this),
                    deadline: block.timestamp,
                    amountIn: attempt,
                    amountOutMinimum: minOut,
                    sqrtPriceLimitX96: 0
                })
            ) returns (uint256 out) {
                IERC20(tokenIn).forceApprove(address(swapRouter), 0);
                return (attempt, out);
            } catch {
                IERC20(tokenIn).forceApprove(address(swapRouter), 0);
                attempt /= 2;
            }
        }
    }

    // ---------------------------------------------------------------------
    // Reward ledger
    // ---------------------------------------------------------------------

    function setShare(address account, uint256 balance) external onlyToken {
        if (excludedFromDividends[account]) return;

        uint256 newShare = balance >= minimumBalanceForDividends ? balance : 0;
        uint256 oldShare = shares[account];
        if (newShare == oldShare) return;

        if (newShare > oldShare) {
            uint256 delta = newShare - oldShare;
            magnifiedDividendCorrections[account] -= int256(magnifiedDividendPerShare * delta);
            totalShares += delta;
            if (oldShare == 0) _addHolder(account);
        } else {
            uint256 delta = oldShare - newShare;
            magnifiedDividendCorrections[account] += int256(magnifiedDividendPerShare * delta);
            totalShares -= delta;
            if (newShare == 0) _removeHolder(account);
        }
        shares[account] = newShare;
    }

    function claim() external nonReentrant returns (uint256) {
        return _withdrawDividend(msg.sender);
    }

    function processAccount(address account) external nonReentrant returns (uint256) {
        return _withdrawDividend(account);
    }

    function process(uint256 gasLimit) external nonReentrant returns (uint256 iterations, uint256 claims) {
        uint256 numHolders = holders.length;
        if (numHolders == 0) return (0, 0);

        uint256 gasUsed = 0;
        uint256 gasLeftStart = gasleft();
        uint256 index = lastProcessedIndex;

        while (gasUsed < gasLimit && iterations < numHolders) {
            if (index >= holders.length) index = 0;
            if (_withdrawDividend(holders[index]) > 0) claims++;
            index++;
            iterations++;
            gasUsed = gasLeftStart - gasleft();
        }
        lastProcessedIndex = index >= holders.length ? 0 : index;
    }

    function withdrawableDividendOf(address account) public view returns (uint256) {
        return accumulativeDividendOf(account) - withdrawnDividends[account];
    }

    function accumulativeDividendOf(address account) public view returns (uint256) {
        return uint256(
            int256(magnifiedDividendPerShare * shares[account]) + magnifiedDividendCorrections[account]
        ) / MAGNITUDE;
    }

    function pendingDistribution() public view returns (uint256) {
        uint256 d = totalDividendsDistributed;
        uint256 w = totalDividendsWithdrawn;
        return d > w ? d - w : 0;
    }

    function holderCount() external view returns (uint256) {
        return holders.length;
    }

    function _withdrawDividend(address account) private returns (uint256) {
        uint256 withdrawable = withdrawableDividendOf(account);
        if (withdrawable == 0) return 0;
        uint256 available = IERC20(rewardToken).balanceOf(address(this));
        if (available == 0) return 0;
        if (withdrawable > available) withdrawable = available;

        withdrawnDividends[account] += withdrawable;
        totalDividendsWithdrawn += withdrawable;

        (bool ok, bytes memory data) = rewardToken.call(
            abi.encodeWithSelector(IERC20.transfer.selector, account, withdrawable)
        );
        if (!ok || (data.length > 0 && !abi.decode(data, (bool)))) {
            withdrawnDividends[account] -= withdrawable;
            totalDividendsWithdrawn -= withdrawable;
            emit DividendWithdrawFailed(account, withdrawable);
            return 0;
        }
        emit DividendWithdrawn(account, withdrawable);
        return withdrawable;
    }

    function _addHolder(address account) private {
        if (holderIndexPlusOne[account] != 0) return;
        holders.push(account);
        holderIndexPlusOne[account] = holders.length;
    }

    function _removeHolder(address account) private {
        uint256 indexPlusOne = holderIndexPlusOne[account];
        if (indexPlusOne == 0) return;
        uint256 index = indexPlusOne - 1;
        uint256 lastIndex = holders.length - 1;
        if (index != lastIndex) {
            address lastHolder = holders[lastIndex];
            holders[index] = lastHolder;
            holderIndexPlusOne[lastHolder] = indexPlusOne;
        }
        holders.pop();
        holderIndexPlusOne[account] = 0;
    }
}
Chain explorer3894msChain node80ms