HooxTokenV3

0x8ba7765fcccc69fab20538b5bcb6a1c1392e1234

Verification
Unverified
v0.8.26+commit.8a97fa7a
Type
Contract
5,271 bytes
ABI entries
32
17 read · 6 write
License
none

Contract information

Address
0x8ba7765fcccc69fab20538b5bcb6a1c1392e1234
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x62c67c01d5…e0c13087d2

Token

Name
test
Symbol
TEST
Decimals
18
Holders
5

Read contract (17)

allowance(address, address)uint256
balanceOf(address)uint256
decimals()uint8
eligibleSupply()uint256
magnifiedRewardPerShare()uint256
name()string
rewardAsset()address
rewardConfigurator()address
rewardDistributor()address
rewardExcluded(address)bool
rewardsConfigured()bool
symbol()string
tokenURI()string
totalRewardsDistributed()uint256
totalSupply()uint256
withdrawableRewardOf(address)uint256
withdrawnReward(address)uint256

Events (6)

ApprovalPermanentMetadataInitializedRewardClaimedRewardConfigurationFinalizedRewardDistributedTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "tokenURI_",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "totalSupply_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "supplyReceiver",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "rewardConfigurator_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "rewardAsset_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFromFailed",
    "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": "string",
        "name": "tokenURI",
        "type": "string"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "metadataDigest",
        "type": "bytes32"
      }
    ],
    "name": "PermanentMetadataInitialized",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "RewardClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eligibleSupply",
        "type": "uint256"
      }
    ],
    "name": "RewardConfigurationFinalized",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "RewardDistributed",
    "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": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claimReward",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "distributeReward",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "received",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eligibleSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "internalType": "address[]",
        "name": "excluded",
        "type": "address[]"
      }
    ],
    "name": "finalizeRewardConfiguration",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "magnifiedRewardPerShare",
    "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": "rewardAsset",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardConfigurator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardDistributor",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "rewardExcluded",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardsConfigured",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalRewardsDistributed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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": "amount",
        "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": "amount",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "withdrawableRewardOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "withdrawnReward",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {IERC20Minimal} from "../interfaces/IVlady.sol";
import {SafeTransfer, ReentrancyGuard} from "../libraries/SafeTransfer.sol";

/// @title HooxTokenV3
/// @notice Fixed-supply, tax-free HOOX launch token with an optional immutable
/// reward asset. Reward accounting is configured once by the factory and then frozen.
contract HooxTokenV3 is ReentrancyGuard {
    uint256 private constant MAGNITUDE = 2 ** 128;

    string public name;
    string public symbol;
    /// @notice ERC-1046 style immutable metadata document URI. Usually ipfs://<CID>.
    string public tokenURI;
    uint8 public constant decimals = 18;
    uint256 public immutable totalSupply;
    address public immutable rewardAsset;
    address public immutable rewardConfigurator;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    bool public rewardsConfigured;
    address public rewardDistributor;
    uint256 public eligibleSupply;
    uint256 public magnifiedRewardPerShare;
    uint256 public totalRewardsDistributed;
    mapping(address => bool) public rewardExcluded;
    mapping(address => int256) private _rewardCorrection;
    mapping(address => uint256) public withdrawnReward;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event PermanentMetadataInitialized(string tokenURI, bytes32 indexed metadataDigest);
    event RewardConfigurationFinalized(address indexed distributor, uint256 eligibleSupply);
    event RewardDistributed(address indexed distributor, uint256 amount);
    event RewardClaimed(address indexed account, uint256 amount);

    constructor(
        string memory name_,
        string memory symbol_,
        string memory tokenURI_,
        uint256 totalSupply_,
        address supplyReceiver,
        address rewardConfigurator_,
        address rewardAsset_
    ) {
        require(bytes(name_).length != 0 && bytes(name_).length <= 32, "name");
        require(bytes(symbol_).length != 0 && bytes(symbol_).length <= 10, "symbol");
        bytes memory uri = bytes(tokenURI_);
        require(uri.length > 7 && uri.length <= 200, "metadata URI");
        require(
            uri[0] == 0x69 && uri[1] == 0x70 && uri[2] == 0x66 && uri[3] == 0x73
                && uri[4] == 0x3a && uri[5] == 0x2f && uri[6] == 0x2f,
            "metadata scheme"
        );
        require(totalSupply_ != 0 && supplyReceiver != address(0) && rewardConfigurator_ != address(0), "constructor");
        name = name_;
        symbol = symbol_;
        tokenURI = tokenURI_;
        emit PermanentMetadataInitialized(tokenURI_, keccak256(uri));
        totalSupply = totalSupply_;
        rewardAsset = rewardAsset_;
        rewardConfigurator = rewardConfigurator_;
        balanceOf[supplyReceiver] = totalSupply_;
        emit Transfer(address(0), supplyReceiver, totalSupply_);
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        uint256 allowed = allowance[from][msg.sender];
        if (allowed != type(uint256).max) {
            require(allowed >= amount, "allowance");
            unchecked { allowance[from][msg.sender] = allowed - amount; }
            emit Approval(from, msg.sender, allowance[from][msg.sender]);
        }
        _transfer(from, to, amount);
        return true;
    }

    function finalizeRewardConfiguration(address distributor, address[] calldata excluded) external {
        require(msg.sender == rewardConfigurator, "configurator only");
        require(!rewardsConfigured && rewardAsset != address(0) && distributor != address(0), "reward config");
        rewardsConfigured = true;
        rewardDistributor = distributor;
        uint256 excludedBalance;
        for (uint256 i; i < excluded.length; ++i) {
            address account = excluded[i];
            require(account != address(0), "zero excluded");
            if (!rewardExcluded[account]) {
                rewardExcluded[account] = true;
                excludedBalance += balanceOf[account];
            }
        }
        eligibleSupply = totalSupply - excludedBalance;
        emit RewardConfigurationFinalized(distributor, eligibleSupply);
    }

    function distributeReward(uint256 amount) external nonReentrant returns (uint256 received) {
        require(rewardsConfigured && msg.sender == rewardDistributor, "distributor only");
        require(amount != 0 && eligibleSupply != 0, "no eligible supply");
        uint256 beforeBalance = IERC20Minimal(rewardAsset).balanceOf(address(this));
        SafeTransfer.safeTransferFrom(rewardAsset, msg.sender, address(this), amount);
        received = IERC20Minimal(rewardAsset).balanceOf(address(this)) - beforeBalance;
        require(received != 0, "zero reward");
        magnifiedRewardPerShare += received * MAGNITUDE / eligibleSupply;
        totalRewardsDistributed += received;
        emit RewardDistributed(msg.sender, received);
    }

    function withdrawableRewardOf(address account) public view returns (uint256) {
        if (!rewardsConfigured || rewardExcluded[account]) return 0;
        uint256 magnified = magnifiedRewardPerShare * balanceOf[account];
        require(magnified <= uint256(type(int256).max), "reward overflow");
        int256 corrected = int256(magnified) + _rewardCorrection[account];
        if (corrected <= 0) return 0;
        uint256 accumulated = uint256(corrected) / MAGNITUDE;
        uint256 withdrawn = withdrawnReward[account];
        return accumulated > withdrawn ? accumulated - withdrawn : 0;
    }

    function claimReward() external nonReentrant returns (uint256 amount) {
        amount = withdrawableRewardOf(msg.sender);
        require(amount != 0, "nothing");
        withdrawnReward[msg.sender] += amount;
        SafeTransfer.safeTransfer(rewardAsset, msg.sender, amount);
        emit RewardClaimed(msg.sender, amount);
    }

    function _transfer(address from, address to, uint256 amount) private {
        require(to != address(0), "zero address");
        uint256 fromBalance = balanceOf[from];
        require(fromBalance >= amount, "balance");
        if (rewardsConfigured && amount != 0) _moveRewardWeight(from, to, amount);
        unchecked {
            balanceOf[from] = fromBalance - amount;
            balanceOf[to] += amount;
        }
        emit Transfer(from, to, amount);
    }

    function _moveRewardWeight(address from, address to, uint256 amount) private {
        bool fromExcluded = rewardExcluded[from];
        bool toExcluded = rewardExcluded[to];
        uint256 correctionRaw = magnifiedRewardPerShare * amount;
        require(correctionRaw <= uint256(type(int256).max), "correction overflow");
        int256 correction = int256(correctionRaw);
        if (!fromExcluded) _rewardCorrection[from] += correction;
        if (!toExcluded) _rewardCorrection[to] -= correction;
        if (fromExcluded && !toExcluded) eligibleSupply += amount;
        else if (!fromExcluded && toExcluded) eligibleSupply -= amount;
    }
}
Chain explorer3788msChain node82ms