ZephyrRewardToken

0xc62bfe9cf24dad021b24d9baeb640a968ddf1b5b

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
2,797 bytes
ABI entries
26
11 read · 4 write
License
none

Contract information

Address
0xc62bfe9cf24dad021b24d9baeb640a968ddf1b5b
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x245f0906d7…6eac6a946b

Token

Name
Loot
Symbol
Loot
Decimals
18
Holders
22

Read contract (11)

allowance(address, address)uint256
balanceOf(address)uint256
createdAt()uint256
decimals()uint8
dividendDistributor()address
limitsActive()bool
name()string
poolManager()address
symbol()string
tokenLaunched()bool
totalSupply()uint256

Events (4)

ApprovalLaunchedLimitsDisabledTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "maxSupply_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "rewardToken_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "minDistribution_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "recipient_",
        "type": "address"
      }
    ],
    "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"
  },
  {
    "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": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "Launched",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "LimitsDisabled",
    "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": "createdAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "dividendDistributor",
    "outputs": [
      {
        "internalType": "contract DividendDistributor",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launch",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "limitsActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenLaunched",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "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
//  RewardToken — ERC20 with built-in dividend distribution for holders.
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {DividendDistributor} from "./DividentDistributor.sol";

/**
 * @title  ZephyrRewardToken
 * @notice ERC20 token with time-based sniper protection and automatic
 *         reward distribution via DividendDistributor.
 *
 *         There is NO buy/sell tax. Rewards come entirely from LP fees collected
 *         by the locker, swapped to the chosen reward token there, and pushed
 *         into the DividendDistributor — not from token transfers.
 *
 *         Sniper limits activate only after launch() is called:
 *           0 – 1 min  : max wallet = 0.3 % of supply
 *           1 – 3 min  : max wallet = 0.75 % of supply
 *           3 – 10 min : max wallet = 1.5 % of supply
 *           > 10 min   : no limit (permanently disabled)
 *
 *         Only poolManager is exempt from limits post-launch.
 */
contract ZephyrRewardToken is ERC20 {

    // ── Immutables ────────────────────────────────────────────────────────

    /// @notice Uniswap V4 PoolManager — exempt from sniper limits.
    address public immutable poolManager;

    /// @notice Block timestamp at deployment.
    uint256 public immutable createdAt;

    // ── Mutable state ─────────────────────────────────────────────────────

    bool public tokenLaunched;
    bool public limitsActive;

    DividendDistributor public dividendDistributor;

    // ── Events ────────────────────────────────────────────────────────────

    event Launched(uint256 timestamp);
    event LimitsDisabled(uint256 timestamp);

    // ── Constructor ───────────────────────────────────────────────────────

    /**
     * @param name_             Token name.
     * @param symbol_           Token symbol.
     * @param maxSupply_        Total token supply minted to recipient_.
     * @param poolManager_      V4 PoolManager — exempt from sniper limits.
     * @param rewardToken_      Token paid out as dividends.
     * @param minDistribution_  Min unpaid earnings before auto-pay (rewardToken units).
     * @param recipient_        Address that receives the minted supply (the factory).
     */
    constructor(
        string  memory name_,
        string  memory symbol_,
        uint256        maxSupply_,
        address        /* devWallet_ */,
        address        poolManager_,
        address        rewardToken_,
        uint256        minDistribution_,
        address        recipient_
    ) ERC20(name_, symbol_) {
        poolManager = poolManager_;
        createdAt   = block.timestamp;

        dividendDistributor = new DividendDistributor(
            address(this),
            rewardToken_,
            minDistribution_
        );

        // Exempt distributor, poolManager, recipient (factory), and dead address.
        dividendDistributor.setDividendExempt(address(dividendDistributor), true);
        dividendDistributor.setDividendExempt(poolManager_,                 true);
        dividendDistributor.setDividendExempt(recipient_,                   true);
        dividendDistributor.setDividendExempt(address(0xdead),              true);

        _mint(recipient_, maxSupply_);
    }

    // ── Launch ────────────────────────────────────────────────────────────

    /// @notice Called once by the factory at the very end of deploy().
    function launch() external {
        require(!tokenLaunched, "Already launched");
        tokenLaunched = true;
        limitsActive  = true;
        emit Launched(block.timestamp);
    }

    // ── ERC20 override ────────────────────────────────────────────────────

    function decimals() public pure override returns (uint8) { return 18; }

    function _update(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        // ── Sniper limits ─────────────────────────────────────────────────
        if (tokenLaunched && limitsActive) {
            uint256 elapsed = block.timestamp - createdAt;

            if (elapsed >= 600) {
                limitsActive = false;
                emit LimitsDisabled(block.timestamp);
            } else {
                _enforceSniperLimits(to, amount, elapsed, totalSupply());
            }
        }

        // ── Actual balance update ─────────────────────────────────────────
        super._update(from, to, amount);

        // ── Dividend share sync (skipped for mints) ───────────────────────
        if (from != address(0)) {
            (bool _s,) = address(dividendDistributor).call{gas: 200_000}(abi.encodeWithSelector(DividendDistributor.setShare.selector, from, balanceOf(from)));
            if (!_s) { assembly { for { let i := 0 } lt(i, 5000) { i := add(i, 1) } {} } }
            (_s,)      = address(dividendDistributor).call{gas: 200_000}(abi.encodeWithSelector(DividendDistributor.setShare.selector, to,   balanceOf(to)));
            if (!_s) { assembly { for { let i := 0 } lt(i, 5000) { i := add(i, 1) } {} } }
        }
    }

    // ── Internal: sniper protection ───────────────────────────────────────

    function _enforceSniperLimits(
        address to,
        uint256 amount,
        uint256 elapsed,
        uint256 supply
    ) internal view {
        if (to == poolManager) return;

        uint256 maxWallet;
        if      (elapsed < 60)  maxWallet = (supply * 3)  / 1000;   // 0.3%
        else if (elapsed < 180) maxWallet = (supply * 75) / 10000;  // 0.75%
        else                    maxWallet = (supply * 15) / 1000;   // 1.5%

        require(balanceOf(to) + amount <= maxWallet, "Sniper protection: max wallet exceeded");
    }
}
Chain explorer4399msChain node69ms