RainFeeLocker

0x57086be6d8009c23d817a387bfeb91ecf02a66d3

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
5,417 bytes
ABI entries
49
15 read · 9 write
License
none

Contract information

Address
0x57086be6d8009c23d817a387bfeb91ecf02a66d3
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0xa06a801e76…72ec2d92c3

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (15)

PROTOCOL_FEE_SHARE()uint256
deployerTokenCount(address)uint256
deployerTokens(address, uint256)address
factory()address
feeRecipientTokenCount(address)uint256
feeRecipientTokens(address, uint256)address
feeRedirects(address)address
getLaunchedToken(address)tuple
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
pendingEth(address)uint256
pendingOwner()address
protocolFeeRecipient()address
tokenProtocolFeeShares(address)uint256
totalPendingEth()uint256

Events (9)

FactoryUpdatedFeePaymentDeferredFeeRedirectUpdatedFeesClaimedOwnershipTransferStartedOwnershipTransferredPendingFeesWithdrawnPositionLockedProtocolFeeRecipientUpdated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "initialProtocolFeeRecipient",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyInitialized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EthTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoFeesToCollect",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAuthorized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotDeployer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFactory",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PositionAlreadyLocked",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PositionNotHeld",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TokenNotFound",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "factory",
        "type": "address"
      }
    ],
    "name": "FactoryUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "FeePaymentDeferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newFeeWallet",
        "type": "address"
      }
    ],
    "name": "FeeRedirectUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethTotal",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensBurned",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "creatorEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolEth",
        "type": "uint256"
      }
    ],
    "name": "FeesClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "PendingFeesWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "dexId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pairToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "positionManager",
        "type": "address"
      }
    ],
    "name": "PositionLocked",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "ProtocolFeeRecipientUpdated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "PROTOCOL_FEE_SHARE",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "collectFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethTotal",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokensBurned",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "deployer_",
        "type": "address"
      }
    ],
    "name": "deployerTokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "deployerTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokens",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient_",
        "type": "address"
      }
    ],
    "name": "feeRecipientTokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "feeRecipientTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokens",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "feeRedirects",
    "outputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getLaunchedToken",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "deployer",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pairedToken",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "dexId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "launchConfigId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "restrictionsEndBlock",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "supply",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "isToken0",
            "type": "bool"
          },
          {
            "internalType": "uint24",
            "name": "poolFee",
            "type": "uint24"
          },
          {
            "internalType": "bool",
            "name": "exists",
            "type": "bool"
          },
          {
            "internalType": "uint256",
            "name": "initialBuyAmount",
            "type": "uint256"
          }
        ],
        "internalType": "struct IRainLaunchFactory.LaunchedToken",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "factory_",
        "type": "address"
      }
    ],
    "name": "initialize",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "lockPosition",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "pendingEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolFeeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "newFeeWallet",
        "type": "address"
      }
    ],
    "name": "setFeeRedirect",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setProtocolFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "tokenProtocolFeeShares",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "share",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalPendingEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawPendingFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {INonfungiblePositionManager, IWETH9} from "./interfaces/IExternal.sol";
import {IRainLaunchFactory} from "./interfaces/IRainLaunch.sol";

contract RainFeeLocker is Ownable2Step, ReentrancyGuard, IERC721Receiver {
    using SafeERC20 for IERC20;

    uint256 public constant PROTOCOL_FEE_SHARE = 50;
    address private constant DEAD = 0x000000000000000000000000000000000000dEaD;

    error NotFactory();
    error NotAuthorized();
    error EthTransferFailed();
    error NotDeployer();
    error TokenNotFound();
    error PositionNotHeld();
    error PositionAlreadyLocked();
    error NoFeesToCollect();
    error AlreadyInitialized();
    error ZeroAddress();

    event FactoryUpdated(address indexed factory);
    event PositionLocked(
        address indexed token,
        address indexed deployer,
        uint256 indexed dexId,
        address pairToken,
        uint256 positionId,
        address positionManager
    );
    event FeesClaimed(
        address indexed token,
        address indexed caller,
        uint256 ethTotal,
        uint256 tokensBurned,
        uint256 creatorEth,
        uint256 protocolEth
    );
    event FeePaymentDeferred(address indexed recipient, uint256 amount);
    event PendingFeesWithdrawn(address indexed recipient, uint256 amount);
    event FeeRedirectUpdated(address indexed token, address indexed newFeeWallet);
    event ProtocolFeeRecipientUpdated(address recipient);

    address public factory;
    address public protocolFeeRecipient;

    /// ETH owed to recipients whose direct payout failed (reverting/gas-heavy receiver).
    /// A hostile fee wallet can therefore never block collectFees for anyone — its share is
    /// parked here and withdrawn separately. totalPendingEth is excluded from every future
    /// split so parked ETH is never redistributed.
    uint256 public totalPendingEth;
    mapping(address recipient => uint256 amount) public pendingEth;

    mapping(address token => address recipient) public feeRedirects;
    mapping(address token => uint256 share) public tokenProtocolFeeShares;
    mapping(address deployer => address[] tokens) public deployerTokens;
    mapping(address recipient => address[] tokens) public feeRecipientTokens;
    mapping(address token => uint256 indexPlusOne) private _feeRecipientTokenIndexes;
    mapping(address token => bool locked) private _lockedTokens;

    constructor(address initialOwner, address initialProtocolFeeRecipient) Ownable(initialOwner) {
        if (initialProtocolFeeRecipient == address(0) || initialProtocolFeeRecipient == address(this)) {
            revert ZeroAddress();
        }
        protocolFeeRecipient = initialProtocolFeeRecipient;
    }

    modifier onlyFactory() {
        if (msg.sender != factory) revert NotFactory();
        _;
    }

    receive() external payable {}

    function initialize(address factory_) external onlyOwner {
        if (factory != address(0)) revert AlreadyInitialized();
        if (factory_ == address(0)) revert ZeroAddress();
        factory = factory_;
        emit FactoryUpdated(factory_);
    }

    function onERC721Received(address operator, address from, uint256, bytes calldata)
        external
        view
        returns (bytes4)
    {
        if (operator != factory || from != factory) revert NotFactory();
        return IERC721Receiver.onERC721Received.selector;
    }

    function lockPosition(address token) external onlyFactory {
        if (_lockedTokens[token]) revert PositionAlreadyLocked();

        IRainLaunchFactory.LaunchedToken memory launched = IRainLaunchFactory(factory).getLaunchedToken(token);
        if (!launched.exists || launched.token != token) revert TokenNotFound();

        address nftOwner = INonfungiblePositionManager(launched.positionManager).ownerOf(launched.positionId);
        if (nftOwner != address(this)) revert PositionNotHeld();

        _lockedTokens[token] = true;
        tokenProtocolFeeShares[token] = PROTOCOL_FEE_SHARE;
        deployerTokens[launched.deployer].push(token);

        emit PositionLocked(
            token,
            launched.deployer,
            launched.dexId,
            launched.pairedToken,
            launched.positionId,
            launched.positionManager
        );
    }

    function collectFees(address token) external nonReentrant returns (uint256 ethTotal, uint256 tokensBurned) {
        IRainLaunchFactory.LaunchedToken memory launched = getLaunchedToken(token);
        if (!launched.exists || !_lockedTokens[token]) revert TokenNotFound();

        address recipient = feeRedirects[token];
        if (recipient == address(0)) recipient = launched.deployer;
        if (msg.sender != launched.deployer && msg.sender != recipient && msg.sender != protocolFeeRecipient) {
            revert NotAuthorized();
        }

        (uint256 amount0, uint256 amount1) = INonfungiblePositionManager(launched.positionManager).collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: launched.positionId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );
        if (amount0 == 0 && amount1 == 0) revert NoFeesToCollect();

        uint256 wethAmount;
        (tokensBurned, wethAmount) = launched.isToken0 ? (amount0, amount1) : (amount1, amount0);

        if (tokensBurned > 0) IERC20(token).safeTransfer(DEAD, tokensBurned);

        // ethTotal is EXACTLY the freshly-unwrapped WETH, never the raw balance. Deriving it from
        // balance would sweep unrelated ETH — parked pull-claims, forced selfdestruct donations, a
        // self-paying recipient — into this token's split. withdraw() credits precisely wethAmount.
        if (wethAmount > 0) IWETH9(launched.pairedToken).withdraw(wethAmount);
        ethTotal = wethAmount;
        if (ethTotal > 0) {
            uint256 protocolEth = (ethTotal * tokenProtocolFeeShares[token]) / 100;
            uint256 creatorEth = ethTotal - protocolEth;
            if (protocolEth > 0) _payOrDefer(protocolFeeRecipient, protocolEth);
            if (creatorEth > 0) _payOrDefer(recipient, creatorEth);
            emit FeesClaimed(token, msg.sender, ethTotal, tokensBurned, creatorEth, protocolEth);
            return (ethTotal, tokensBurned);
        }
        emit FeesClaimed(token, msg.sender, 0, tokensBurned, 0, 0);
    }

    /// Pays out directly when the recipient accepts; otherwise parks the amount as a pull-claim.
    /// The gas cap keeps a hostile receiver from burning the collector's gas — any receiver that
    /// needs more than 100k gas simply gets deferred and withdraws itself.
    function _payOrDefer(address to, uint256 amount) private {
        (bool ok,) = to.call{value: amount, gas: 100_000}("");
        if (!ok) {
            pendingEth[to] += amount;
            totalPendingEth += amount;
            emit FeePaymentDeferred(to, amount);
        }
    }

    /// Withdraws ETH parked by a failed direct payout in collectFees.
    function withdrawPendingFees() external nonReentrant {
        uint256 amount = pendingEth[msg.sender];
        if (amount == 0) revert NoFeesToCollect();
        pendingEth[msg.sender] = 0;
        totalPendingEth -= amount;
        (bool ok,) = msg.sender.call{value: amount}("");
        if (!ok) revert EthTransferFailed();
        emit PendingFeesWithdrawn(msg.sender, amount);
    }

    function getLaunchedToken(address token) public view returns (IRainLaunchFactory.LaunchedToken memory) {
        if (factory == address(0)) revert TokenNotFound();
        return IRainLaunchFactory(factory).getLaunchedToken(token);
    }

    function setFeeRedirect(address token, address newFeeWallet) external {
        IRainLaunchFactory.LaunchedToken memory launched = getLaunchedToken(token);
        if (!launched.exists) revert TokenNotFound();
        if (msg.sender != launched.deployer && msg.sender != factory) revert NotDeployer();
        _setFeeRedirect(token, newFeeWallet);
    }

    function setProtocolFeeRecipient(address recipient) external onlyOwner {
        if (recipient == address(0) || recipient == address(this)) revert ZeroAddress();
        protocolFeeRecipient = recipient;
        emit ProtocolFeeRecipientUpdated(recipient);
    }

    function deployerTokenCount(address deployer_) external view returns (uint256) {
        return deployerTokens[deployer_].length;
    }

    function feeRecipientTokenCount(address recipient_) external view returns (uint256) {
        return feeRecipientTokens[recipient_].length;
    }

    function _setFeeRedirect(address token, address newFeeWallet) private {
        if (newFeeWallet == address(this)) revert ZeroAddress();
        address previousFeeWallet = feeRedirects[token];
        if (previousFeeWallet == newFeeWallet) return;

        uint256 previousIndexPlusOne = _feeRecipientTokenIndexes[token];
        if (previousFeeWallet != address(0) && previousIndexPlusOne != 0) {
            address[] storage previousTokens = feeRecipientTokens[previousFeeWallet];
            uint256 previousIndex = previousIndexPlusOne - 1;
            uint256 lastIndex = previousTokens.length - 1;
            if (previousIndex != lastIndex) {
                address movedToken = previousTokens[lastIndex];
                previousTokens[previousIndex] = movedToken;
                _feeRecipientTokenIndexes[movedToken] = previousIndex + 1;
            }
            previousTokens.pop();
            delete _feeRecipientTokenIndexes[token];
        }

        feeRedirects[token] = newFeeWallet;
        if (newFeeWallet != address(0)) {
            feeRecipientTokens[newFeeWallet].push(token);
            _feeRecipientTokenIndexes[token] = feeRecipientTokens[newFeeWallet].length;
        }
        emit FeeRedirectUpdated(token, newFeeWallet);
    }

}
Chain explorer950msChain node84ms