PonsLaunchLocker

0x736d76699c26d0d966744cae304c000d471f7f35

Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
5,426 bytes
ABI entries
49
15 read · 10 write
License
none

Contract information

Address
0x736d76699c26d0d966744cae304c000d471f7f35
Chain
Robinhood Chain (4663)
Compiler
v0.8.30+commit.73712a01
Optimization
Enabled
Creation tx
0xb12910e168…d42f483c70

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (15)

MAX_PROTOCOL_FEE_SHARE()uint256
deployerTokenCount(address)uint256
deployerTokens(address, uint256)address
factory()address
feeCollectors(address)bool
feeRecipientTokenCount(address)uint256
feeRecipientTokens(address, uint256)address
feeRedirects(address)address
getLaunchedToken(address)tuple
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
pendingOwner()address
protocolFeeRecipient()address
protocolFeeShare()uint256
tokenProtocolFeeShares(address)uint256

Events (9)

FactoryUpdatedFeeCollectorUpdatedFeeRedirectUpdatedFeesClaimedOwnershipTransferStartedOwnershipTransferredPositionLockedProtocolFeeRecipientUpdatedProtocolFeeUpdated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "initialProtocolFeeRecipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "initialProtocolFeeShare",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyInitialized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidProtocolFee",
    "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": "collector",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "FeeCollectorUpdated",
    "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": "address",
        "name": "token0",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token1",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "recipientAmount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "recipientAmount1",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolAmount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolAmount1",
        "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": "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"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "share",
        "type": "uint256"
      }
    ],
    "name": "ProtocolFeeUpdated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_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": "amount0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1",
        "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": "collector",
        "type": "address"
      }
    ],
    "name": "feeCollectors",
    "outputs": [
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "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 IPonsLaunchFactory.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": [],
    "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": "protocolFeeShare",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "collector",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setFeeCollector",
    "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": "uint256",
        "name": "share",
        "type": "uint256"
      }
    ],
    "name": "setProtocolFeeShare",
    "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": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {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 {IERC721ReceiverLike, INonfungiblePositionManagerLike, IPonsLaunchFactory} from "./interfaces/ILaunchpad.sol";

/**
 * @title PonsLaunchLocker
 * @notice Permanently holds launch position NFTs and distributes accrued fees.
 * The contract intentionally exposes no position withdrawal or arbitrary-call
 * function, so registered launch liquidity cannot be removed by an administrator.
 */
contract PonsLaunchLocker is Ownable2Step, ReentrancyGuard, IERC721ReceiverLike {
    using SafeERC20 for IERC20;

    uint256 public constant MAX_PROTOCOL_FEE_SHARE = 50;

    error NotFactory();
    error NotAuthorized();
    error NotDeployer();
    error TokenNotFound();
    error PositionNotHeld();
    error PositionAlreadyLocked();
    error NoFeesToCollect();
    error InvalidProtocolFee();
    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,
        address token0,
        address token1,
        uint256 recipientAmount0,
        uint256 recipientAmount1,
        uint256 protocolAmount0,
        uint256 protocolAmount1
    );
    event FeeRedirectUpdated(address indexed token, address indexed newFeeWallet);
    event FeeCollectorUpdated(address indexed collector, bool enabled);
    event ProtocolFeeRecipientUpdated(address recipient);
    event ProtocolFeeUpdated(uint256 share);

    address public factory;
    address public protocolFeeRecipient;
    uint256 public protocolFeeShare;

    mapping(address collector => bool enabled) public feeCollectors;
    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;

    /**
     * @param initialOwner Administrative owner for fee policy and collectors.
     * @param initialProtocolFeeRecipient Recipient of the protocol fee share.
     * @param initialProtocolFeeShare Percentage from 0 through 100.
     */
    constructor(address initialOwner, address initialProtocolFeeRecipient, uint256 initialProtocolFeeShare)
        Ownable(initialOwner)
    {
        if (initialProtocolFeeRecipient == address(0)) revert ZeroAddress();
        if (initialProtocolFeeShare > MAX_PROTOCOL_FEE_SHARE) revert InvalidProtocolFee();
        protocolFeeRecipient = initialProtocolFeeRecipient;
        protocolFeeShare = initialProtocolFeeShare;
    }

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

    /**
     * @notice Binds the locker to one immutable launch factory.
     */
    function initialize(address factory_) external onlyOwner {
        if (factory != address(0)) revert AlreadyInitialized();
        if (factory_ == address(0)) revert ZeroAddress();
        factory = factory_;
        emit FactoryUpdated(factory_);
    }

    /**
     * @notice Accepts only launch NFTs transferred by the configured factory.
     */
    function onERC721Received(address operator, address from, uint256, bytes calldata) external view returns (bytes4) {
        if (operator != factory || from != factory) revert NotFactory();
        return IERC721ReceiverLike.onERC721Received.selector;
    }

    /**
     * @notice Registers and verifies permanent custody of a launched position.
     */
    function lockPosition(address token) external onlyFactory {
        if (_lockedTokens[token]) revert PositionAlreadyLocked();

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

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

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

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

    /**
     * @notice Collects V3 fees and splits both assets under the configured policy.
     */
    function collectFees(address token) external nonReentrant returns (uint256 amount0, uint256 amount1) {
        IPonsLaunchFactory.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 != owner() && msg.sender != launched.deployer && msg.sender != recipient
                && !feeCollectors[msg.sender]
        ) {
            revert NotAuthorized();
        }

        INonfungiblePositionManagerLike manager = INonfungiblePositionManagerLike(launched.positionManager);
        address token0;
        address token1;
        (,, token0, token1,,,,,,,,) = manager.positions(launched.positionId);

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

        uint256 tokenProtocolFeeShare = tokenProtocolFeeShares[token];
        uint256 protocolAmount0 = (amount0 * tokenProtocolFeeShare) / 100;
        uint256 protocolAmount1 = (amount1 * tokenProtocolFeeShare) / 100;
        uint256 recipientAmount0 = amount0 - protocolAmount0;
        uint256 recipientAmount1 = amount1 - protocolAmount1;

        _transferIfPositive(token0, protocolFeeRecipient, protocolAmount0);
        _transferIfPositive(token1, protocolFeeRecipient, protocolAmount1);
        _transferIfPositive(token0, recipient, recipientAmount0);
        _transferIfPositive(token1, recipient, recipientAmount1);

        emit FeesClaimed(
            token, msg.sender, token0, token1, recipientAmount0, recipientAmount1, protocolAmount0, protocolAmount1
        );
    }

    /**
     * @notice Returns the factory record for a launch token.
     */
    function getLaunchedToken(address token) public view returns (IPonsLaunchFactory.LaunchedToken memory) {
        if (factory == address(0)) revert TokenNotFound();
        return IPonsLaunchFactory(factory).getLaunchedToken(token);
    }

    /**
     * @notice Redirects the creator share for one token.
     * @dev Callable by the launch deployer or the factory during launch setup.
     */
    function setFeeRedirect(address token, address newFeeWallet) external {
        IPonsLaunchFactory.LaunchedToken memory launched = getLaunchedToken(token);
        if (!launched.exists) revert TokenNotFound();
        if (msg.sender != launched.deployer && msg.sender != factory) revert NotDeployer();
        _setFeeRedirect(token, newFeeWallet);
    }

    /**
     * @notice Grants or revokes fee collection permission.
     */
    function setFeeCollector(address collector, bool enabled) external onlyOwner {
        if (collector == address(0)) revert ZeroAddress();
        feeCollectors[collector] = enabled;
        emit FeeCollectorUpdated(collector, enabled);
    }

    /**
     * @notice Changes the protocol fee recipient.
     */
    function setProtocolFeeRecipient(address recipient) external onlyOwner {
        if (recipient == address(0)) revert ZeroAddress();
        protocolFeeRecipient = recipient;
        emit ProtocolFeeRecipientUpdated(recipient);
    }

    /**
     * @notice Changes the fee share snapshotted by future token launches.
     */
    function setProtocolFeeShare(uint256 share) external onlyOwner {
        if (share > MAX_PROTOCOL_FEE_SHARE) revert InvalidProtocolFee();
        protocolFeeShare = share;
        emit ProtocolFeeUpdated(share);
    }

    /**
     * @notice Returns the number of tokens indexed for one deployer.
     */
    function deployerTokenCount(address deployer_) external view returns (uint256) {
        return deployerTokens[deployer_].length;
    }

    /**
     * @notice Returns the number of tokens indexed for one fee recipient.
     */
    function feeRecipientTokenCount(address recipient_) external view returns (uint256) {
        return feeRecipientTokens[recipient_].length;
    }

    /**
     * @notice Stores a creator-fee redirect and indexes the recipient for Profile reads.
     */
    function _setFeeRedirect(address token, address newFeeWallet) private {
        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);
    }

    function _transferIfPositive(address token, address recipient, uint256 amount) private {
        if (amount != 0) IERC20(token).safeTransfer(recipient, amount);
    }
}
Chain explorer5558msChain node83ms