PadQuiverDistributor

0xef759acc1673a8869792f6646704a00a9c3aa431

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

Contract information

Address
0xef759acc1673a8869792f6646704a00a9c3aa431
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xf478569d24…9fe4c6268e

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;

    // WETH on which the platform fee was already taken but not yet swapped (stranded) — never taxed again
    uint256 private taxedWethPending;

    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");
        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 the ENTIRE token balance to WETH (TWAP min)
        uint256 tokenBal = IERC20(token).balanceOf(address(this));
        if (tokenBal > 0) {
            _swapTwapProtected(pool, token, weth, poolFee, tokenBal);
        }

        // 2) Platform share (WETH) + convert WETH into the reward token
        uint256 platformCut;
        if (rewardToken == weth) {
            uint256 bal = IERC20(weth).balanceOf(address(this));
            uint256 owed = pendingDistribution();
            uint256 newVal = bal > owed ? bal - owed : 0;
            platformCut = (newVal * platformFeeBps) / FEE_DENOMINATOR;
            if (platformCut > 0 && platformWallet != address(0)) {
                IERC20(weth).safeTransfer(platformWallet, platformCut);
            }
        } else {
            // Take the platform fee only from NEW WETH (don't re-tax stranded/already-taxed WETH)
            uint256 wethBal = IERC20(weth).balanceOf(address(this));
            uint256 newWeth = wethBal > taxedWethPending ? wethBal - taxedWethPending : 0;
            platformCut = (newWeth * platformFeeBps) / FEE_DENOMINATOR;
            if (platformCut > 0 && platformWallet != address(0)) {
                IERC20(weth).safeTransfer(platformWallet, platformCut);
            }
            uint256 toSwap = IERC20(weth).balanceOf(address(this));
            if (toSwap > 0) {
                _swapTwapProtected(rewardPool, weth, rewardToken, rewardPoolFee, toSwap);
            }
            // If the swap fails, the remaining WETH is already taxed; the next harvest won't tax it again
            taxedWethPending = IERC20(weth).balanceOf(address(this));
        }

        // 3) Tip + distribution — ONLY if there are genuinely distributable new rewards
        //    (no tip is paid while totalShares==0 => a stranded balance can't be ground down)
        uint256 rewardBal = IERC20(rewardToken).balanceOf(address(this));
        uint256 credited = pendingDistribution();
        uint256 fresh = rewardBal > credited ? rewardBal - credited : 0;
        uint256 tip;
        if (fresh > 0 && totalShares > 0) {
            tip = (fresh * callerTipBps) / FEE_DENOMINATOR;
            if (tip > 0) IERC20(rewardToken).safeTransfer(msg.sender, tip);
            rewardsDistributed = fresh - tip;
            magnifiedDividendPerShare += (rewardsDistributed * MAGNITUDE) / totalShares;
            totalDividendsDistributed += rewardsDistributed;
            emit DividendsDistributed(rewardsDistributed);
        }
        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 amountOut)
    {
        uint256 minOut = V3Twap.minOutFromTwap(twapPool, twapWindow, amountIn, tokenIn, tokenOut, maxSlippageBps);
        IERC20(tokenIn).forceApprove(address(swapRouter), amountIn);
        try swapRouter.exactInputSingle(
            ISwapRouter.ExactInputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: fee,
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: minOut,
                sqrtPriceLimitX96: 0
            })
        ) returns (uint256 out) {
            amountOut = out;
        } catch {
            IERC20(tokenIn).forceApprove(address(swapRouter), 0);
        }
    }

    // ---------------------------------------------------------------------
    // 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 explorer5630msChain node82ms