ZephyrRewardToken

0xd05452a9ec85af657a416deaee16ba9849decb89

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

Contract information

Address
0xd05452a9ec85af657a416deaee16ba9849decb89
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xad9ba81aa0…b74ae128dd

Token

Name
JAT
Symbol
JAT
Decimals
18
Holders
3

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 explorer3640msChain node129ms