RainLaunchToken

0x8d69607d1ee9e9a9786f2d8d949b4bc8b8937776

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
5,029 bytes
ABI entries
41
24 read · 4 write
License
none

Contract information

Address
0x8d69607d1ee9e9a9786f2d8d949b4bc8b8937776
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0x8b64e5386a…2e3ef4a664

Token

Name
phis
Symbol
phis
Decimals
18
Holders
64

Read contract (24)

allowance(address, address)uint256
balanceOf(address)uint256
decimals()uint8
deployer()address
description()string
dexFactory()address
getTokenInfo()address, string, string, tuple
launchBlock()uint256
launchFactory()address
liquidityPool()address
logo()string
maxWalletAmount()uint256
maxWalletBps()uint16
maxWalletLimit()uint256
name()string
pairToken()address
poolFee()uint24
positionManager()address
restrictionBlocks()uint256
restrictionEndBlock()uint256
restrictionKeyOf(address)address
socials()string, string, string, string, string
symbol()string
totalSupply()uint256

Events (2)

ApprovalTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "logo_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "description_",
        "type": "string"
      },
      {
        "components": [
          {
            "internalType": "string",
            "name": "twitter",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "telegram",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "discord",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "website",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "farcaster",
            "type": "string"
          }
        ],
        "internalType": "struct RainLaunchToken.Socials",
        "name": "socials_",
        "type": "tuple"
      },
      {
        "internalType": "address",
        "name": "deployer_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "dexFactory_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pairToken_",
        "type": "address"
      },
      {
        "internalType": "uint24",
        "name": "poolFee_",
        "type": "uint24"
      },
      {
        "internalType": "uint256",
        "name": "supply_",
        "type": "uint256"
      },
      {
        "internalType": "uint16",
        "name": "maxWalletBps_",
        "type": "uint16"
      },
      {
        "internalType": "uint32",
        "name": "restrictionBlocks_",
        "type": "uint32"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "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": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "LaunchBlockBuyBlocked",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "limit",
        "type": "uint256"
      }
    ],
    "name": "MaxWalletExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotLaunchFactory",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "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": [
      {
        "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": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deployer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "description",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "dexFactory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getTokenInfo",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokenDeployer",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "tokenLogo",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "tokenDescription",
        "type": "string"
      },
      {
        "components": [
          {
            "internalType": "string",
            "name": "twitter",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "telegram",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "discord",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "website",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "farcaster",
            "type": "string"
          }
        ],
        "internalType": "struct RainLaunchToken.Socials",
        "name": "tokenSocials",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchFactory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "liquidityPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "logo",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWalletAmount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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": "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": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "restrictionBlocks",
    "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": "restrictionKeyOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setInitialBuyRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "socials",
    "outputs": [
      {
        "internalType": "string",
        "name": "twitter",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "telegram",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "discord",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "website",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "farcaster",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "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 {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IUniswapV3Factory} from "./interfaces/IExternal.sol";
import {IUniswapV3PoolImmutablesLike} from "./interfaces/IRainLaunch.sol";

contract RainLaunchToken is ERC20 {
    struct Socials {
        string twitter;
        string telegram;
        string discord;
        string website;
        string farcaster;
    }

    error LaunchBlockBuyBlocked(address recipient);
    error MaxWalletExceeded(address account, uint256 balance, uint256 limit);
    error NotLaunchFactory();
    error ZeroAddress();

    address public immutable deployer;
    address public immutable launchFactory;
    address public immutable dexFactory;
    address public immutable positionManager;
    address public immutable pairToken;
    uint24 public immutable poolFee;

    uint256 public immutable launchBlock;
    uint256 public immutable restrictionBlocks;
    uint256 public immutable restrictionEndBlock;
    uint16 public immutable maxWalletBps;

    string public logo;
    string public description;

    Socials private _socials;
    address private _initialBuyRecipient;

    constructor(
        string memory name_,
        string memory symbol_,
        string memory logo_,
        string memory description_,
        Socials memory socials_,
        address deployer_,
        address dexFactory_,
        address positionManager_,
        address pairToken_,
        uint24 poolFee_,
        uint256 supply_,
        uint16 maxWalletBps_,
        uint32 restrictionBlocks_
    ) ERC20(name_, symbol_) {
        if (
            deployer_ == address(0) || dexFactory_ == address(0) || positionManager_ == address(0)
                || pairToken_ == address(0)
        ) {
            revert ZeroAddress();
        }

        deployer = deployer_;
        launchFactory = msg.sender;
        dexFactory = dexFactory_;
        positionManager = positionManager_;
        pairToken = pairToken_;
        poolFee = poolFee_;
        launchBlock = block.number;
        restrictionBlocks = restrictionBlocks_;
        restrictionEndBlock = block.number + restrictionBlocks_;
        maxWalletBps = maxWalletBps_;
        logo = logo_;
        description = description_;
        _socials = socials_;

        _mint(msg.sender, supply_);
    }

    function liquidityPool() public view returns (address) {
        return IUniswapV3Factory(dexFactory).getPool(address(this), pairToken, poolFee);
    }

    function socials()
        external
        view
        returns (
            string memory twitter,
            string memory telegram,
            string memory discord,
            string memory website,
            string memory farcaster
        )
    {
        Socials memory values = _socials;
        return (values.twitter, values.telegram, values.discord, values.website, values.farcaster);
    }

    function getTokenInfo()
        external
        view
        returns (
            address tokenDeployer,
            string memory tokenLogo,
            string memory tokenDescription,
            Socials memory tokenSocials
        )
    {
        return (deployer, logo, description, _socials);
    }

    function maxWalletLimit() public view returns (uint256) {
        return (totalSupply() * maxWalletBps) / 10_000;
    }

    function maxWalletAmount() external view returns (uint256) {
        return maxWalletLimit();
    }

    /// The address whose holdings the launch-window cap is enforced against — ALWAYS the actual
    /// token recipient. It previously returned tx.origin for contract recipients (K84), but that
    /// let a contract buy-and-HOLD past the cap: the cap read balanceOf(tx.origin), which stayed
    /// ~0 while the tokens piled up in the contract. Keying on the recipient bounds the address
    /// that truly holds the tokens. RainSwapper reads this to size partial-fill room, so the
    /// token and the swapper stay in lock-step.
    function restrictionKeyOf(address recipient) public pure returns (address) {
        return recipient;
    }

    function setInitialBuyRecipient(address recipient) external {
        if (msg.sender != launchFactory) revert NotLaunchFactory();
        _initialBuyRecipient = recipient;
    }

    function _update(address from, address to, uint256 value) internal override {
        if (from != address(0) && to != address(0) && block.number <= restrictionEndBlock) {
            bool isRestrictedBuy = _isPairPool(from);
            if (!isRestrictedBuy) {
                super._update(from, to, value);
                return;
            }

            bool isAtomicLaunchBuy =
                block.number == launchBlock && _initialBuyRecipient != address(0) && to == _initialBuyRecipient;
            if (!isAtomicLaunchBuy && block.number == launchBlock) {
                revert LaunchBlockBuyBlocked(to);
            }

            if (!isAtomicLaunchBuy) {
                address key = restrictionKeyOf(to);

                // The ONE launch-window rule: the RECIPIENT may never HOLD more than maxWallet
                // (2%). Checked against the recipient's live balance, so selling frees the room
                // again — buy 2% -> sell -> buy 2% works. Keying on the recipient (not tx.origin)
                // means a contract that buys and keeps the tokens is bounded by its own balance.
                uint256 walletLimit = maxWalletLimit();
                uint256 resultingBalance = balanceOf(key) + value;
                if (resultingBalance > walletLimit) {
                    revert MaxWalletExceeded(key, resultingBalance, walletLimit);
                }
            }
        }

        super._update(from, to, value);
    }

    function _isPairPool(address candidate) private view returns (bool) {
        address canonicalPool = liquidityPool();
        if (candidate == canonicalPool && canonicalPool != address(0)) return true;
        if (candidate.code.length == 0) return false;

        (bool feeRead, bytes memory feeData) =
            candidate.staticcall(abi.encodeCall(IUniswapV3PoolImmutablesLike.fee, ()));
        if (!feeRead || feeData.length < 32) return false;

        uint24 candidateFee = abi.decode(feeData, (uint24));
        return IUniswapV3Factory(dexFactory).getPool(address(this), pairToken, candidateFee) == candidate;
    }
}
Chain explorer1597msChain node76ms