EquifoldRestrictedToken

0xe30144f6a87208275b809a8bc04c70523fbf45f1

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
5,959 bytes
ABI entries
51
25 read · 6 write
License
none

Contract information

Address
0xe30144f6a87208275b809a8bc04c70523fbf45f1
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x0194bc7aa6…6b32ca2205

Token

Name
Kaiten
Symbol
KAITEN
Decimals
18
Holders
16

Read contract (25)

MAX_DECIMALS()uint8
MIN_DECIMALS()uint8
TOKEN_VERSION()uint32
allowance(address, address)uint256
balanceOf(address)uint256
creator()address
decimals()uint8
dexFactory()address
factory()address
isBurnable()bool
launchBlock()uint256
launchContract()address
launchTimestamp()uint48
maxBuysLimit()uint256
maxTxBps()uint16
maxWalletBps()uint16
maxWalletLimit()uint256
metadataURI()string
name()string
pairToken()address
poolFee()uint24
projectNumber()uint256
restrictionEndBlock()uint256
symbol()string
totalSupply()uint256

Events (2)

ApprovalTransfer

ABI

[
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "uint8",
            "name": "decimals",
            "type": "uint8"
          },
          {
            "internalType": "uint256",
            "name": "initialSupply",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "burnable",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "projectNumber",
            "type": "uint256"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "launchContract",
            "type": "address"
          }
        ],
        "internalType": "struct EquifoldToken.Initialization",
        "name": "initial",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "dexFactory",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pairToken",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "poolFee",
            "type": "uint24"
          },
          {
            "internalType": "uint16",
            "name": "maxWalletBps",
            "type": "uint16"
          },
          {
            "internalType": "uint16",
            "name": "maxTxBps",
            "type": "uint16"
          },
          {
            "internalType": "uint32",
            "name": "restrictionBlocks",
            "type": "uint32"
          }
        ],
        "internalType": "struct EquifoldRestrictedToken.RestrictionConfig",
        "name": "restriction",
        "type": "tuple"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BurningDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "allowance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientAllowance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "approver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidApprover",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSpender",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidDecimals",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidMetadata",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidProject",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRestrictionConfig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSupply",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "LaunchBlockBuyBlocked",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "cumulative",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "limit",
        "type": "uint256"
      }
    ],
    "name": "MaxBuysExceeded",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "resultingBalance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "limit",
        "type": "uint256"
      }
    ],
    "name": "MaxWalletExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotLaunchContract",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_DECIMALS",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MIN_DECIMALS",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_VERSION",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burn",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burnFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "dexFactory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "isBurnable",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchContract",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchTimestamp",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxBuysLimit",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxTxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletLimit",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "metadataURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pairToken",
    "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": "projectNumber",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "restrictionEndBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setInitialBuyRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

import { EquifoldToken } from "./EquifoldToken.sol";
import {
    IUniswapV3FactoryMinimal,
    IUniswapV3PoolMinimal
} from "../interfaces/IUniswapV3Minimal.sol";

/// @title Equifold restricted launch token (gen3 V3-native)
/// @author Equifold
/// @notice EquifoldToken whose pool BUYS are limited during a fixed launch window: launch-block
///         buys are blocked entirely (except the factory's atomic creator dev buy), then each
///         recipient is capped at {maxWalletBps} balance and {maxTxBps} cumulative pool buys until
///         {restrictionEndBlock}. Sells and wallet-to-wallet transfers are never restricted, and
///         after the window the token behaves as a plain EquifoldToken.
contract EquifoldRestrictedToken is EquifoldToken {
    error InvalidRestrictionConfig();
    error LaunchBlockBuyBlocked(address recipient);
    error MaxBuysExceeded(address recipient, uint256 cumulative, uint256 limit);
    error MaxWalletExceeded(address recipient, uint256 resultingBalance, uint256 limit);
    error NotLaunchContract();

    struct RestrictionConfig {
        address dexFactory;
        address pairToken;
        uint24 poolFee;
        uint16 maxWalletBps;
        uint16 maxTxBps;
        uint32 restrictionBlocks;
    }

    uint16 private constant BPS_DENOMINATOR = 10_000;

    address public immutable dexFactory;
    address public immutable pairToken;
    uint24 public immutable poolFee;
    uint16 public immutable maxWalletBps;
    uint16 public immutable maxTxBps;
    uint256 public immutable launchBlock;
    uint256 public immutable restrictionEndBlock;

    address private _initialBuyRecipient;
    mapping(address recipient => uint256 bought) private _restrictedPoolBuys;

    constructor(
        Initialization memory initial,
        RestrictionConfig memory restriction
    ) EquifoldToken(initial) {
        if (
            restriction.dexFactory == address(0) || restriction.pairToken == address(0)
                || restriction.dexFactory.code.length == 0 || restriction.poolFee == 0
                || restriction.maxWalletBps == 0 || restriction.maxWalletBps > BPS_DENOMINATOR
                || restriction.maxTxBps < restriction.maxWalletBps
                || restriction.maxTxBps > BPS_DENOMINATOR || restriction.restrictionBlocks == 0
        ) revert InvalidRestrictionConfig();
        dexFactory = restriction.dexFactory;
        pairToken = restriction.pairToken;
        poolFee = restriction.poolFee;
        maxWalletBps = restriction.maxWalletBps;
        maxTxBps = restriction.maxTxBps;
        launchBlock = block.number;
        restrictionEndBlock = block.number + restriction.restrictionBlocks;
    }

    /// @notice Maximum recipient balance during the restricted launch window.
    function maxWalletLimit() public view returns (uint256) {
        return (totalSupply() * maxWalletBps) / BPS_DENOMINATOR;
    }

    /// @notice Cumulative pool-buy limit per recipient during the restricted window.
    function maxBuysLimit() public view returns (uint256) {
        return (totalSupply() * maxTxBps) / BPS_DENOMINATOR;
    }

    /// @notice Opens or closes the single-recipient exemption used by the launch module's atomic
    ///         creator dev buy. Only callable by the launch module bound at deploy. The exemption
    ///         only bypasses the caps while `block.number == launchBlock`; if left set afterward
    ///         it has no further effect (see {_update}).
    function setInitialBuyRecipient(
        address recipient
    ) external {
        if (msg.sender != launchContract) revert NotLaunchContract();
        _initialBuyRecipient = recipient;
    }

    /// @dev Restricts only pool→user transfers (buys) during the launch window. The
    ///      {_initialBuyRecipient} exemption is scoped to the launch block itself, so it can only
    ///      ever cover the launch module's atomic creator dev buy — never the 3%/3.3% caps for
    ///      the rest of the restriction window, even if left set by mistake.
    function _update(
        address from,
        address to,
        uint256 value
    ) internal override {
        if (
            from != address(0) && to != address(0) && block.number <= restrictionEndBlock
                && _isPairPool(from)
        ) {
            bool exemptBuy = block.number == launchBlock && _initialBuyRecipient != address(0)
                && to == _initialBuyRecipient;
            if (!exemptBuy) {
                if (block.number == launchBlock) revert LaunchBlockBuyBlocked(to);
                uint256 resultingBalance = balanceOf(to) + value;
                uint256 walletLimit = maxWalletLimit();
                if (resultingBalance > walletLimit) {
                    revert MaxWalletExceeded(to, resultingBalance, walletLimit);
                }
                uint256 cumulative = _restrictedPoolBuys[to] + value;
                uint256 buysLimit = maxBuysLimit();
                if (cumulative > buysLimit) {
                    revert MaxBuysExceeded(to, cumulative, buysLimit);
                }
                _restrictedPoolBuys[to] = cumulative;
            }
        }
        super._update(from, to, value);
    }

    /// @dev A "pair pool" is the canonical (token, pairToken, poolFee) V3 pool or any other
    ///      fee-tier pool of the same pair registered on the same factory, so buys cannot dodge
    ///      the window through an alternate tier.
    function _isPairPool(
        address candidate
    ) private view returns (bool) {
        if (candidate.code.length == 0) return false;
        address canonicalPool =
            IUniswapV3FactoryMinimal(dexFactory).getPool(address(this), pairToken, poolFee);
        if (candidate == canonicalPool && canonicalPool != address(0)) return true;
        (bool ok, bytes memory data) =
            candidate.staticcall(abi.encodeCall(IUniswapV3PoolMinimal.fee, ()));
        if (!ok || data.length < 32) return false;
        uint24 candidateFee = abi.decode(data, (uint24));
        return IUniswapV3FactoryMinimal(dexFactory).getPool(address(this), pairToken, candidateFee)
            == candidate;
    }
}
Chain explorer2507msChain node79ms