QuiverToken

0x871807f628910d1bef61ce75940dc1367ec75836

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
6,158 bytes
ABI entries
34
16 read · 6 write
License
none

Contract information

Address
0x871807f628910d1bef61ce75940dc1367ec75836
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x81d297b6ff…2b92fcd99b

Token

Name
Quiver
Symbol
Quiver
Decimals
18
Holders
2

Read contract (16)

allowance(address, address)uint256
balanceOf(address)uint256
contractURI()string
creator()address
decimals()uint8
exclusive()bool
factory()address
gate()address
gated()bool
guardExempt(address)bool
imageURI()string
launcher()address
name()string
pool()address
symbol()string
totalSupply()uint256

Events (4)

ApprovalExclusivityDisabledGatingEndedTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "totalSupply_",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "imageURI_",
        "type": "string"
      },
      {
        "components": [
          {
            "internalType": "bool",
            "name": "gated",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "exclusive",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "gate",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "factory",
            "type": "address"
          },
          {
            "internalType": "address[]",
            "name": "exempt",
            "type": "address[]"
          }
        ],
        "internalType": "struct QuiverToken.GuardConfig",
        "name": "guard_",
        "type": "tuple"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BuyNotApproved",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "GuardConfigInvalid",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyCreator",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyLauncher",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PoolAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RivalPoolBlocked",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferToZeroAddress",
    "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": [],
    "name": "ExclusivityDisabled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "GatingEnded",
    "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": "value",
        "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": "contractURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "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": [],
    "name": "disableExclusivity",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "endGating",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "exclusive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gate",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gated",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "guardExempt",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "imageURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launcher",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pool_",
        "type": "address"
      }
    ],
    "name": "setPool",
    "outputs": [],
    "stateMutability": "nonpayable",
    "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: BUSL-1.1
// Copyright (c) 2026 Quiver (quiver-app.io)
// Licensed under the Business Source License 1.1, see LICENSE.
// Production use and forks are not permitted without a written grant
// from Quiver.
pragma solidity 0.8.26;

interface IQuiverGateMinimal {
    function isApproved(address buyer) external view returns (bool);
}

interface IUniswapV3FactoryGetPool {
    function getPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external view returns (address);
}

/// @title QuiverToken
/// @notice Fixed-supply ERC-20, deployed once per Quiver launch. No owner,
/// no minting, no burning. The whole supply goes to the deployer (the
/// QuiverLauncher) in the constructor.
///
/// Metadata lives on-chain: name/symbol/imageURI are set in the constructor
/// and contractURI() exposes them as ERC-7572 contract-level metadata (a
/// data-URI JSON), so explorers and tooling can pick up the token image
/// without any off-chain registry.
///
/// Launch guard - two OPT-IN, ONE-WAY toggles, both fixed at launch and only
/// ever switchable OFF (never back on) by the creator:
///
///   - gated:     buys (transfers out of the main launch pool) require the
///                recipient to be approved by the QuiverGate for the current
///                transaction (an API-signed per-wallet voucher). Sells and
///                wallet-to-wallet transfers are NEVER blocked - gating can
///                never trap a holder. endGating() opens public trading
///                forever (the "end bonding" moment).
///   - exclusive: transfers to any OTHER Uniswap v3 pool of this token
///                revert (a pool that cannot receive tokens can never hold
///                liquidity), so the main launch pool captures all volume
///                and its fee tier can't be undercut by a rival pool. This
///                is POOL exclusivity, not position exclusivity: anyone can
///                still LP inside the main pool and share its fees (v3
///                pools are permissionless). disableExclusivity() lifts
///                this forever.
///
/// A public launch deploys with both flags false and behaves exactly like a
/// plain ERC-20: the guard short-circuits on a single storage read. All
/// guard config lives in STORAGE (not immutables) so every launch - public
/// or guarded - shares one canonical runtime bytecode, which is what the
/// Quiver API exact-matches before listing a pool.
contract QuiverToken {
    string public name;
    string public symbol;
    uint8 public constant decimals = 18;
    uint256 public immutable totalSupply;
    /// @notice Token image URI (any scheme: https, ipfs, ...). Fixed at
    /// deployment; consumers decide which sources they trust.
    string public imageURI;

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

    // ------------------------------------------------------------------
    // Launch guard state. `pool`/`gated`/`exclusive` pack into ONE slot,
    // so the unguarded hot path costs a single extra sload.
    // ------------------------------------------------------------------

    /// @notice The main launch pool (token/WETH at the launch's fee tier),
    /// set once by the launcher right after pool creation. address(0) on
    /// public launches (the guard never needs it there).
    address public pool;
    /// @notice Buys-only gate toggle. One-way: endGating() only.
    bool public gated;
    /// @notice Rival-v3-pool block toggle. One-way: disableExclusivity() only.
    bool public exclusive;

    /// @notice The deployer (QuiverLauncher); only authority is setPool once.
    address public launcher;
    /// @notice The launch creator; can only ever LOOSEN the guard.
    address public creator;
    /// @notice The QuiverGate consulted for buy approval while gated.
    address public gate;
    /// @notice Canonical Uniswap v3 factory, used to identify rival pools.
    address public factory;

    /// @notice Addresses exempt from the buy gate as recipients. Set at
    /// launch to infrastructure that legitimately receives tokens FROM the
    /// pool while gated: the PermaLock (fee collection routes token-side
    /// fees pool -> PermaLock), the QuoterV2 (buy quotes simulate a
    /// pool -> quoter transfer via eth_call; a real transfer there only
    /// strands the tokens, so this is not a bypass), and - when the launch
    /// carried an atomic first buy - the QuiverLauncher (the first-buy swap
    /// lands there before being forwarded to the creator; any later
    /// transfer there only strands tokens too).
    mapping(address => bool) public guardExempt;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
    /// @notice The one-way "end bonding" moment: public trading is open forever.
    event GatingEnded();
    /// @notice One-way: other v3 pools of this token may exist from now on.
    event ExclusivityDisabled();

    error OnlyLauncher();
    error OnlyCreator();
    error PoolAlreadySet();
    error GuardConfigInvalid();
    error BuyNotApproved();
    error RivalPoolBlocked();
    error TransferToZeroAddress();

    /// @notice Guard configuration, fixed at deployment (an all-zero config
    /// is a public launch). Grouped into a struct to keep the constructor's
    /// stack shallow.
    struct GuardConfig {
        bool gated;
        bool exclusive;
        /// The QuiverGate; required iff gated.
        address gate;
        /// The launch creator (endGating/disableExclusivity authority);
        /// required iff any toggle is on.
        address creator;
        /// The canonical Uniswap v3 factory; required iff exclusive.
        address factory;
        /// Recipients exempt from the buy gate (launch infrastructure).
        address[] exempt;
    }

    constructor(
        string memory name_,
        string memory symbol_,
        uint256 totalSupply_,
        string memory imageURI_,
        GuardConfig memory guard_
    ) {
        name = name_;
        symbol = symbol_;
        totalSupply = totalSupply_;
        imageURI = imageURI_;

        // Guard wiring, written only when a toggle is on (public launches
        // pay no storage for config they don't use).
        if (guard_.gated && guard_.gate == address(0))
            revert GuardConfigInvalid();
        if (guard_.exclusive && guard_.factory == address(0))
            revert GuardConfigInvalid();
        if ((guard_.gated || guard_.exclusive) && guard_.creator == address(0))
            revert GuardConfigInvalid();
        if (guard_.gated || guard_.exclusive) {
            launcher = msg.sender;
            creator = guard_.creator;
            gated = guard_.gated;
            exclusive = guard_.exclusive;
            if (guard_.gated) {
                gate = guard_.gate;
                for (uint256 i = 0; i < guard_.exempt.length; i++) {
                    guardExempt[guard_.exempt[i]] = true;
                }
            }
            if (guard_.exclusive) factory = guard_.factory;
        }

        balanceOf[msg.sender] = totalSupply_;
        emit Transfer(address(0), msg.sender, totalSupply_);
    }

    // ========================================================================
    // Launch guard
    // ========================================================================

    /// @notice Launcher-only, once: records the main launch pool so the
    /// guard can tell buys/sells apart. Called between pool creation and
    /// the LP mint inside the atomic launch transaction.
    function setPool(address pool_) external {
        if (msg.sender != launcher) revert OnlyLauncher();
        if (pool != address(0)) revert PoolAlreadySet();
        if (pool_ == address(0)) revert GuardConfigInvalid();
        pool = pool_;
    }

    /// @notice Creator-only, one-way: opens public trading forever. There
    /// is no way to re-enable gating.
    function endGating() external {
        if (msg.sender != creator) revert OnlyCreator();
        if (!gated) revert GuardConfigInvalid();
        gated = false;
        emit GatingEnded();
    }

    /// @notice Creator-only, one-way: allows other v3 pools of this token
    /// forever. There is no way to re-enable exclusivity.
    function disableExclusivity() external {
        if (msg.sender != creator) revert OnlyCreator();
        if (!exclusive) revert GuardConfigInvalid();
        exclusive = false;
        emit ExclusivityDisabled();
    }

    /// @dev The transfer guard. Rules, in order:
    ///   1. both toggles off (every public launch, and every guarded launch
    ///      after the creator ends both) -> plain ERC-20, one sload;
    ///   2. transfers TO the main pool (sells, the launch LP mint) always
    ///      pass - no configuration can ever block an exit;
    ///   3. while gated, transfers FROM the pool (buys) require the
    ///      recipient to be gate-approved for this transaction, unless the
    ///      recipient is launch-exempt infrastructure (PermaLock, quoter);
    ///   4. while exclusive, transfers to a contract that proves to be a
    ///      canonical v3 pool of this token (self-reported token0/token1/
    ///      fee confirmed via factory.getPool == recipient) revert.
    function _checkGuard(address from, address to) private view {
        // pool + gated + exclusive share one slot: one sload for all three.
        address pool_ = pool;
        bool gated_ = gated;
        bool exclusive_ = exclusive;
        if (!gated_ && !exclusive_) return;

        // UNCONDITIONAL EXIT GUARANTEE: a Uniswap v3 sell transfers tokens
        // from the holder/router TO the pool. Return before every gate,
        // exemption, exclusivity, or external-contract check, so neither the
        // creator nor any configured dependency can ever block that sell.
        if (to == pool_) return;

        // The gate is deliberately the opposite direction only: pool -> buyer.
        // It can reject a buy, but is unreachable for sells and wallet transfers.
        if (gated_ && from == pool_ && !guardExempt[to]) {
            if (!IQuiverGateMinimal(gate).isApproved(to))
                revert BuyNotApproved();
        }

        if (exclusive_ && to.code.length > 0 && _isRivalV3Pool(to))
            revert RivalPoolBlocked();
    }

    /// @dev True when `to` is a canonical Uniswap v3 pool with this token
    /// on one side. The recipient's self-reported token0/token1/fee are
    /// only trusted after factory.getPool(token0, token1, fee) confirms the
    /// recipient IS that pool: a real pool reports its true immutables (so
    /// no false negatives), and a liar gets a different address back from
    /// the factory (so no false positives).
    function _isRivalV3Pool(address to) private view returns (bool) {
        (bool ok0, bytes memory data0) = to.staticcall(
            abi.encodeWithSignature("token0()")
        );
        if (!ok0 || data0.length != 32) return false;
        (bool ok1, bytes memory data1) = to.staticcall(
            abi.encodeWithSignature("token1()")
        );
        if (!ok1 || data1.length != 32) return false;
        address token0 = _asAddress(data0);
        address token1 = _asAddress(data1);
        if (token0 != address(this) && token1 != address(this)) return false;
        (bool okFee, bytes memory dataFee) = to.staticcall(
            abi.encodeWithSignature("fee()")
        );
        if (!okFee || dataFee.length != 32) return false;
        uint24 fee = uint24(uint256(bytes32(dataFee)));
        return
            IUniswapV3FactoryGetPool(factory).getPool(token0, token1, fee) ==
            to;
    }

    /// @dev Lower-160-bits decode that can't revert on dirty return data.
    function _asAddress(bytes memory data) private pure returns (address) {
        return address(uint160(uint256(bytes32(data))));
    }

    // ========================================================================
    // ERC-20
    // ========================================================================

    /// @notice ERC-7572 contract-level metadata as a self-contained data URI,
    /// so there's no off-chain JSON to host. name/symbol/imageURI are user
    /// input and get JSON-escaped before embedding.
    function contractURI() external view returns (string memory) {
        return
            string(
                abi.encodePacked(
                    'data:application/json;utf8,{"name":"',
                    _jsonEscape(name),
                    '","symbol":"',
                    _jsonEscape(symbol),
                    '","image":"',
                    _jsonEscape(imageURI),
                    '"}'
                )
            );
    }

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

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

    function transferFrom(
        address from,
        address to,
        uint256 value
    ) external returns (bool) {
        uint256 allowed = allowance[from][msg.sender];
        if (allowed != type(uint256).max) {
            allowance[from][msg.sender] = allowed - value;
        }
        _transfer(from, to, value);
        return true;
    }

    function _transfer(address from, address to, uint256 value) internal {
        // Reject the zero address (OpenZeppelin ERC-20 behavior): the supply
        // is fixed and there is no burn, so tokens must never be strandable
        // at address(0) either.
        if (to == address(0)) revert TransferToZeroAddress();
        _checkGuard(from, to);
        balanceOf[from] -= value;
        balanceOf[to] += value;
        emit Transfer(from, to, value);
    }

    /// @dev Escapes `"` and `\` and strips control characters. That's enough
    /// to keep the contractURI JSON well-formed for arbitrary user strings.
    function _jsonEscape(
        string memory value
    ) private pure returns (string memory) {
        bytes memory input = bytes(value);
        bytes memory out = new bytes(input.length * 2);
        uint256 j;
        for (uint256 i = 0; i < input.length; i++) {
            bytes1 c = input[i];
            if (c == '"' || c == "\\") {
                out[j++] = "\\";
                out[j++] = c;
            } else if (uint8(c) >= 0x20) {
                out[j++] = c;
            }
        }
        assembly {
            mstore(out, j)
        }
        return string(out);
    }
}
Chain explorer6660msChain node88ms