QuiverToken

0x600478629dd470fbf2a4145a24899458aab34663

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
5,021 bytes
ABI entries
45
16 read · 10 write
License
none

Contract information

Address
0x600478629dd470fbf2a4145a24899458aab34663
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x2b01904bf4…63ddffe587

Token

Name
stockprintr.fun
Symbol
PRINTR
Decimals
18
Holders
100

Read contract (16)

allowance(address, address)uint256
balanceOf(address)uint256
claimable(address)uint256
creator()address
decimals()uint8
eligibleSupply()uint256
excluded(address)bool
hook()address
metadataURI()string
name()string
pendingRewards(address)uint256
rewardToken()address
symbol()string
taxBps()uint16
totalRewardsDistributed()uint256
totalSupply()uint256

Events (6)

ApprovalExcludedSetHookSetRewardsClaimedRewardsDistributedTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI_",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "supply_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "creator_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "supplyRecipient_",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "taxBps_",
        "type": "uint16"
      },
      {
        "internalType": "address",
        "name": "rewardToken_",
        "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"
  },
  {
    "inputs": [],
    "name": "HookAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyFactory",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyHook",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "WrongRewardCurrency",
    "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": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "excluded",
        "type": "bool"
      }
    ],
    "name": "ExcludedSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "hook",
        "type": "address"
      }
    ],
    "name": "HookSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "RewardsClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "RewardsDistributed",
    "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": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "burn",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claim",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      }
    ],
    "name": "claimFor",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "holders",
        "type": "address[]"
      }
    ],
    "name": "claimForMany",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "claimable",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "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": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "distributeRewards",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "distributeRewardsNative",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eligibleSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "excluded",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hook",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "hook_",
        "type": "address"
      },
      {
        "internalType": "address[]",
        "name": "excludedAddrs",
        "type": "address[]"
      }
    ],
    "name": "initHook",
    "outputs": [],
    "stateMutability": "nonpayable",
    "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": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      }
    ],
    "name": "pendingRewards",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardToken",
    "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": "taxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "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": "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"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title QuiverToken
/// @notice Fixed-supply launchpad token with an on-chain, gas-safe holder
///         dividend tracker. A share of every trade's tax (routed in by the
///         hook) is distributed to holders in proportion to how much they
///         hold, using a MasterChef-style accumulator so distribution is O(1)
///         regardless of holder count. Locked liquidity (held by the V4
///         PoolManager) and other system addresses are excluded so rewards
///         only flow to real holders.
///
///         Rewards are paid in `rewardToken` — any ERC-20 the creator picks at
///         launch (e.g. a tokenized stock), or native currency when set to the
///         zero address. Holders accrue continuously and pull with claim().
contract QuiverToken is ERC20 {
    using SafeERC20 for IERC20;

    uint256 private constant ACC_PRECISION = 1e24;

    /// @notice Wallet credited as the token's creator (immutable attribution).
    address public immutable creator;
    /// @notice The hook allowed to credit dividends; set once by the factory.
    address public hook;
    /// @notice Immutable per-token trade tax in basis points (0..1000 = 0-10%).
    uint16 public immutable taxBps;
    /// @notice Currency dividends are paid in. address(0) == native.
    address public immutable rewardToken;

    /// @dev Accumulated reward per eligible share, scaled by ACC_PRECISION.
    uint256 private accRewardPerShare;
    /// @dev Supply eligible for dividends (excludes system/excluded holders).
    uint256 public eligibleSupply;
    /// @dev Reward already accounted to a holder: balance * acc / PRECISION.
    mapping(address => uint256) private rewardDebt;
    /// @dev Settled-but-unclaimed rewards per holder.
    mapping(address => uint256) public claimable;
    /// @dev Addresses that do not participate in dividends (pool, system).
    mapping(address => bool) public excluded;

    /// @notice Lifetime rewards distributed to holders, in reward units.
    uint256 public totalRewardsDistributed;

    string private _metadataURI;

    event HookSet(address indexed hook);
    event ExcludedSet(address indexed account, bool excluded);
    event RewardsDistributed(uint256 amount);
    event RewardsClaimed(address indexed holder, uint256 amount);

    error OnlyFactory();
    error OnlyHook();
    error HookAlreadySet();
    error WrongRewardCurrency();

    address private immutable _factory;

    constructor(
        string memory name_,
        string memory symbol_,
        string memory metadataURI_,
        uint256 supply_,
        address creator_,
        address supplyRecipient_,
        uint16 taxBps_,
        address rewardToken_
    ) ERC20(name_, symbol_) {
        require(taxBps_ <= 1000, "tax>10%");
        _factory = msg.sender;
        creator = creator_;
        taxBps = taxBps_;
        rewardToken = rewardToken_;
        _metadataURI = metadataURI_;

        // Exclude system endpoints (zero, self, and the supply recipient) from
        // dividends up front, so rewards only ever flow to real holders.
        excluded[address(0)] = true;
        excluded[address(this)] = true;
        excluded[supplyRecipient_] = true;

        _mint(supplyRecipient_, supply_);
    }

    /// @notice Off-chain metadata JSON (description, logo, website, socials).
    function metadataURI() external view returns (string memory) {
        return _metadataURI;
    }

    /// @notice Burn tokens held by the caller (used by the hook for buyback&burn).
    function burn(uint256 amount) external {
        _burn(msg.sender, amount);
    }

    // ---------------------------------------------------------------------
    // Factory wiring (one-time)
    // ---------------------------------------------------------------------

    /// @notice Wire the hook and exclude the pool/system addresses. The factory
    ///         calls this exactly once, right after it knows the pool endpoints.
    function initHook(address hook_, address[] calldata excludedAddrs) external {
        if (msg.sender != _factory) revert OnlyFactory();
        if (hook != address(0)) revert HookAlreadySet();
        hook = hook_;
        emit HookSet(hook_);
        _setExcluded(hook_, true);
        for (uint256 i; i < excludedAddrs.length; ++i) {
            _setExcluded(excludedAddrs[i], true);
        }
    }

    // ---------------------------------------------------------------------
    // Dividend distribution
    // ---------------------------------------------------------------------

    /// @notice Credit an ERC-20 reward distribution to all eligible holders.
    ///         The hook must have transferred `amount` of `rewardToken` to this
    ///         contract before calling. No-op-safe when there is no eligible
    ///         supply (caller keeps the funds).
    function distributeRewards(uint256 amount) external {
        if (msg.sender != hook) revert OnlyHook();
        if (rewardToken == address(0)) revert WrongRewardCurrency();
        _distribute(amount);
    }

    /// @notice Credit a native reward distribution to all eligible holders.
    function distributeRewardsNative() external payable {
        if (msg.sender != hook) revert OnlyHook();
        if (rewardToken != address(0)) revert WrongRewardCurrency();
        _distribute(msg.value);
    }

    function _distribute(uint256 amount) private {
        uint256 supply = eligibleSupply;
        if (amount == 0 || supply == 0) return;
        accRewardPerShare += (amount * ACC_PRECISION) / supply;
        totalRewardsDistributed += amount;
        emit RewardsDistributed(amount);
    }

    /// @notice Pending, not-yet-settled rewards for a holder.
    function pendingRewards(address holder) public view returns (uint256) {
        if (excluded[holder]) return claimable[holder];
        uint256 accrued = (balanceOf(holder) * accRewardPerShare) / ACC_PRECISION;
        uint256 debt = rewardDebt[holder];
        uint256 extra = accrued > debt ? accrued - debt : 0;
        return claimable[holder] + extra;
    }

    /// @notice Claim all settled + pending rewards to the caller.
    function claim() external returns (uint256 amount) {
        return _claimTo(msg.sender);
    }

    /// @notice Push a holder's accrued rewards to THEIR wallet. Callable by
    ///         anyone (the protocol keeper calls it after every distribution so
    ///         rewards land in wallets with no user action), but the funds can
    ///         only ever go to the holder — non-custodial by construction.
    function claimFor(address holder) external returns (uint256 amount) {
        return _claimTo(holder);
    }

    /// @notice Batch delivery for the keeper: push rewards to many holders in
    ///         one transaction. A single failing receiver (native rewards only)
    ///         is skipped rather than blocking the whole batch.
    function claimForMany(address[] calldata holders) external {
        for (uint256 i; i < holders.length; ++i) {
            try this.claimFor(holders[i]) {} catch {}
        }
    }

    function _claimTo(address holder) private returns (uint256 amount) {
        _settle(holder);
        amount = claimable[holder];
        if (amount == 0) return 0;
        claimable[holder] = 0;
        emit RewardsClaimed(holder, amount);
        if (rewardToken == address(0)) {
            (bool ok, ) = payable(holder).call{value: amount}("");
            require(ok, "native xfer");
        } else {
            IERC20(rewardToken).safeTransfer(holder, amount);
        }
    }

    // ---------------------------------------------------------------------
    // Accounting hooks
    // ---------------------------------------------------------------------

    /// @dev Move a holder's freshly-accrued rewards into `claimable` and reset
    ///      their debt to the current balance basis.
    function _settle(address account) private {
        if (account == address(0) || excluded[account]) return;
        uint256 accrued = (balanceOf(account) * accRewardPerShare) / ACC_PRECISION;
        uint256 debt = rewardDebt[account];
        if (accrued > debt) claimable[account] += accrued - debt;
        rewardDebt[account] = accrued;
    }

    function _resetDebt(address account) private {
        rewardDebt[account] = (balanceOf(account) * accRewardPerShare) / ACC_PRECISION;
    }

    function _setExcluded(address account, bool value) private {
        if (excluded[account] == value) return;
        // Settle then flip participation, adjusting eligibleSupply by balance.
        uint256 bal = balanceOf(account);
        if (value) {
            _settle(account);
            if (bal != 0) eligibleSupply -= bal;
        } else {
            if (bal != 0) eligibleSupply += bal;
            _resetDebt(account);
        }
        excluded[account] = value;
        emit ExcludedSet(account, value);
    }

    /// @dev Core transfer/mint/burn accounting. Settles both sides, moves the
    ///      balance, keeps `eligibleSupply` in sync with participation, and
    ///      rebases each side's reward debt to its new balance.
    function _update(address from, address to, uint256 value) internal override {
        bool fromEligible = from != address(0) && !excluded[from];
        bool toEligible = to != address(0) && !excluded[to];

        if (fromEligible) _settle(from);
        if (toEligible) _settle(to);

        super._update(from, to, value);

        // Keep the eligible-supply denominator correct across the flow.
        if (fromEligible && !toEligible) {
            eligibleSupply -= value; // eligible -> excluded (or burn)
        } else if (!fromEligible && toEligible) {
            eligibleSupply += value; // mint or excluded -> eligible
        }

        if (fromEligible) _resetDebt(from);
        if (toEligible) _resetDebt(to);
    }

    /// @notice Accept native only as reward funding.
    receive() external payable {}
}
Chain explorer5162msChain node77ms