QuiverToken

0xc7a8af2e5e5e2cf6c5257218cc93ad2debe17579

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

Contract information

Address
0xc7a8af2e5e5e2cf6c5257218cc93ad2debe17579
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xd337555f61…989b9d5de0

Token

Name
Quiver guild
Symbol
QUIVER
Decimals
18
Holders
930

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 explorer3697msChain node75ms