๐Ÿฆถ Squatchi v2 โ€” Historical Smart Contract Source

Verified Solidity source & compiler settings ยท Base mainnet (chain ID 8453)

โ† Back to home

This is the historical source of the SQUATCHI v2 token and its Victory Rewards contract, released under the MIT License. The .sol files are byte-for-byte identical to the verified sources. Recompiling them with the settings below reproduces the deployed bytecode exactly, including the metadata hash.

โœ… BaseScan verified token code โ†— โœ… BaseScan verified Victory Rewards code โ†— ๐Ÿ“ฆ Download squatchi-contracts.zip

Current official SQUATCHI on Base: 0xA71eD0B46009ed99807bc5363Df5868F88a0845c.

The source files and tokenomics below belong to historical SQUATCHI v2 (0xb2867D1D6590C89591C2811c51e5144077dc1Ab3), not the current official token. They do not establish ERC-20i features, supply, tax, ownership or liquidity properties for the current token.

Contracts

ContractAddress (Base)
Historical SQUATCHI v2 token0xb2867D1D6590C89591C2811c51e5144077dc1Ab3
SquatchiVictoryRewards0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6

Compiler settings (both contracts)

Compilersolc 0.8.38+commit.1835d247
Optimizerenabled, runs: 200
viaIRtrue
evmVersionparis
Metadata bytecode hashipfs
Libraries / remappingsnone

To verify it yourself: npm install && node scripts/verify.js (Node 18+). The script recompiles both contracts and compares them byte for byte against the on-chain code.

Files

contracts/token/Squatchi.solraw ยท download
sha256 6973281f34126158be3936a05865c5b7a12c7bdef8d03bcb1ef871ff9b24bef5
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title Squatchi ($SQUATCHI) โ€” Base ERC-20i Dynamic Inscription Token & Pacific Northwest Arcade Asset
 * @notice Fixed 15,000,000 $SQUATCHI supply, 0% buy/sell/transfer tax, no minting, no blacklist,
 *         no direct-sale arbitrage functions, and standard OpenZeppelin Ownable renouncement
 *         (`owner() == address(0)`).
 *         - 86.67% (13,000,000 $SQUATCHI) allocated to Base Uniswap V2 Liquidity Pool (100% LP burned
 *           to 0x000000000000000000000000000000000000dEaD) & Play-to-Earn Victory Pool.
 *         - 10.00% (1,500,000 $SQUATCHI) Genesis Ecosystem & Gameplay Allocation.
 *         - 3.33%  (500,000 $SQUATCHI) Genesis Partner Allocation.
 */

abstract contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event RenounceOwnership();

    constructor(address initialOwner) {
        _owner = initialOwner;
        emit OwnershipTransferred(address(0), initialOwner);
    }

    modifier onlyOwner() {
        require(_owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function owner() public view virtual returns (address) {
        return _owner;
    }

    function ownershipRenounced() public view returns (bool) {
        return _owner == address(0);
    }

    function renounceOwnership() public virtual onlyOwner {
        address oldOwner = _owner;
        _owner = address(0);
        emit OwnershipTransferred(oldOwner, address(0));
        emit RenounceOwnership();
    }
}

contract Squatchi is Ownable {
    // โ”€โ”€โ”€ ERC-20i Seed Structure (compatible with Base ERC-20i indexers & Squatchi Arcade) โ”€โ”€โ”€
    struct SeedData {
        uint256 seed;
        uint256 extra;
    }

    struct SquatchStats {
        uint8 tier;
        string className;
        uint256 sizeBps;      // 100 = 1.00x, 350 = 3.50x
        uint256 speed;        // Game movement speed rating
        uint256 magnetRadius; // Item pickup radius in game units
        uint256 multiplier;   // Score & foraging yield multiplier
        string furColor;
        string gearItem;
        string auraType;
        bool isStable;
    }

    // โ”€โ”€โ”€ Token Constants โ”€โ”€โ”€
    string public constant name = "Squatchi";
    string public constant symbol = "SQUATCHI";
    uint8 public constant decimals = 18;

    uint256 public constant MAX_SUPPLY = 15_000_000 * 1e18; // 15,000,000 SQUATCHI total (fixed, immutable)
    address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    // โ”€โ”€โ”€ Standard ERC-20 State (0% Buy/Sell/Transfer Tax) โ”€โ”€โ”€
    uint256 public totalSupply;
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    // โ”€โ”€โ”€ ERC-20i Inscription State โ”€โ”€โ”€
    mapping(address => SeedData) private _seeds;
    mapping(address => uint256) public holderNonce;
    mapping(address => bool) public stableInscription;
    mapping(address => bool) public isPoolOrExempt;

    address[] private _holders;
    mapping(address => uint256) private _holderIndex; // 1-based index

    // โ”€โ”€โ”€ Standard ERC-20 Events โ”€โ”€โ”€
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event InscriptionStabilized(address indexed holder, bool stable, uint256 lockedSeed);

    constructor(address genesisPartner) Ownable(msg.sender) {
        require(genesisPartner != address(0), "Squatchi: invalid partner");

        isPoolOrExempt[address(this)] = true;
        isPoolOrExempt[DEAD_ADDRESS] = true;

        // 1. Mint 500,000 SQUATCHI (3.33%) to Genesis Partner
        _mint(genesisPartner, 500_000 * 1e18);

        // 2. Mint 14,500,000 SQUATCHI to deployer (1,500,000 [10%] Genesis Ecosystem + 13,000,000 [86.67%] DEX LP & P2E Pool)
        _mint(msg.sender, 14_500_000 * 1e18);
    }

    // โ”€โ”€โ”€ Standard ERC-20 Functions (0% Tax, No Restrictions) โ”€โ”€โ”€

    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    function effectiveBalanceOf(address holder) public view returns (uint256) {
        return _balances[holder];
    }

    function lockedFounderBalance() public pure returns (uint256) {
        return 0;
    }

    function allowance(address holder, address spender) public view returns (uint256) {
        return _allowances[holder][spender];
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

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

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        uint256 currentAllowance = _allowances[from][msg.sender];
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "Squatchi: insufficient allowance");
            unchecked {
                _approve(from, msg.sender, currentAllowance - amount);
            }
        }
        _transfer(from, to, amount);
        return true;
    }

    // โ”€โ”€โ”€ ERC-20i Dynamic Seed & Inscription Mechanics โ”€โ”€โ”€

    function stabilizeInscription(bool stable) external {
        require(_balances[msg.sender] > 0, "Squatchi: must hold tokens to stabilize");
        stableInscription[msg.sender] = stable;
        emit InscriptionStabilized(msg.sender, stable, _seeds[msg.sender].seed);
    }

    function getTier(address holder) public view returns (uint8) {
        return _tierForBalance(_balances[holder]);
    }

    function _tierForBalance(uint256 bal) internal pure returns (uint8) {
        uint256 whole = bal / 1e18;
        if (whole >= 500_000) return 6; // Mythic Sasquatch King (500k+ SQUATCHI)
        if (whole >= 250_000) return 5; // Cascadian Elder
        if (whole >= 50_000)  return 4; // Mountain Alpha
        if (whole >= 10_000)  return 3; // Timber Strider
        if (whole >= 1_000)   return 2; // Moss Forager
        if (bal > 0)          return 1; // Baby Wood Ape
        return 0;
    }

    function getSeed(address holder) public view returns (SeedData memory) {
        return _seeds[holder];
    }

    function getHolderCount() external view returns (uint256) {
        return _holders.length;
    }

    function getHolderByIndex(uint256 index) external view returns (address) {
        require(index < _holders.length, "Squatchi: index out of bounds");
        return _holders[index];
    }

    function getHoldersList(uint256 startIndex, uint256 count) external view returns (address[] memory list) {
        uint256 len = _holders.length;
        if (startIndex >= len) return new address[](0);
        uint256 end = startIndex + count;
        if (end > len) end = len;
        uint256 outLen = end - startIndex;
        list = new address[](outLen);
        for (uint256 i = 0; i < outLen; i++) {
            list[i] = _holders[startIndex + i];
        }
    }

    function getSquatchStats(address holder) public view returns (SquatchStats memory stats) {
        SeedData memory sd = _seeds[holder];
        uint8 tier = _tierForBalance(_balances[holder]);
        return _computeStats(sd.seed, tier, stableInscription[holder]);
    }

    function _computeStats(uint256 seed, uint8 tier, bool isStable) internal pure returns (SquatchStats memory s) {
        s.tier = tier;
        s.isStable = isStable;

        if (tier == 0) {
            s.className = "Uninscribed";
            s.furColor = "None";
            s.gearItem = "None";
            s.auraType = "None";
            return s;
        }

        if (tier == 1) {
            s.className = "Baby Wood Ape";
            s.sizeBps = 100;
            s.speed = 100 + (seed % 15);
            s.magnetRadius = 60 + (seed % 12);
            s.multiplier = 1;
        } else if (tier == 2) {
            s.className = "Moss Forager";
            s.sizeBps = 135;
            s.speed = 125 + (seed % 20);
            s.magnetRadius = 95 + (seed % 18);
            s.multiplier = 2;
        } else if (tier == 3) {
            s.className = "Timber Strider";
            s.sizeBps = 175;
            s.speed = 150 + (seed % 25);
            s.magnetRadius = 135 + (seed % 22);
            s.multiplier = 3;
        } else if (tier == 4) {
            s.className = "Mountain Alpha";
            s.sizeBps = 225;
            s.speed = 180 + (seed % 30);
            s.magnetRadius = 180 + (seed % 28);
            s.multiplier = 4;
        } else if (tier == 5) {
            s.className = "Cascadian Elder";
            s.sizeBps = 280;
            s.speed = 215 + (seed % 35);
            s.magnetRadius = 235 + (seed % 35);
            s.multiplier = 4;
        } else {
            s.className = "Mythic Sasquatch King";
            s.sizeBps = 350;
            s.speed = 260 + (seed % 40);
            s.magnetRadius = 310 + (seed % 50);
            s.multiplier = 5;
        }

        string[8] memory furs = [
            "Pine Bark Brown",
            "Cascadian Moss",
            "Redwood Umber",
            "Glacier Frost",
            "Obsidian Silverback",
            "Bioluminescent Mycelium",
            "Ancient Amber",
            "Cosmic Auroral Gold"
        ];
        string[8] memory gears = [
            "Woven Twig Pouch",
            "Forager Berry Basket",
            "Glowing Spore Lantern",
            "Carved Cedar Totem",
            "Golden Pinecone Satchel",
            "Elder Mycelium Staff",
            "Emerald Relic Horn",
            "Crown of the Old Growth"
        ];
        string[6] memory auras = [
            "Pine Mist",
            "Emerald Spore Ring",
            "Frost Wind Swirl",
            "Thunder Stomp Shockwave",
            "Bioluminescent Halo",
            "Sovereign Auroral Crown"
        ];

        if (tier == 6) {
            s.furColor = furs[7];
            s.gearItem = gears[7];
            s.auraType = auras[5];
        } else {
            s.furColor = furs[(seed >> 8) % (tier + 2)];
            s.gearItem = gears[(seed >> 16) % (tier + 2)];
            s.auraType = auras[tier - 1];
        }
    }

    function getMeta(SeedData memory seed_data) public pure returns (string memory) {
        uint8 tier = uint8(seed_data.extra & 0xFF);
        if (tier == 0 && seed_data.seed > 0) {
            tier = 1;
        }
        bool isStable = ((seed_data.extra >> 8) & 1) == 1;
        uint256 wholeTokens = seed_data.extra >> 16;
        return _buildMetadataJSON(seed_data.seed, tier, isStable, wholeTokens);
    }

    function getInscription(address holder) external view returns (string memory) {
        SeedData memory sd = _seeds[holder];
        uint8 tier = _tierForBalance(_balances[holder]);
        return _buildMetadataJSON(sd.seed, tier, stableInscription[holder], _balances[holder] / 1e18);
    }

    function _buildMetadataJSON(
        uint256 seed,
        uint8 tier,
        bool isStable,
        uint256 wholeTokens
    ) internal pure returns (string memory) {
        SquatchStats memory st = _computeStats(seed, tier, isStable);
        string memory svg = _buildSVG(seed, tier, st);

        string memory header = string(
            abi.encodePacked(
                '{"name":"Squatchi #',
                _toString(seed % 100000),
                " - ",
                st.className,
                '","description":"Squatchi ($SQUATCHI) dynamic ERC-20i on-chain Sasquatch inscription on Base.","balance_tokens":',
                _toString(wholeTokens),
                ',"tier":',
                _toString(tier),
                ',"image":"data:image/svg+xml;utf8,',
                svg
            )
        );

        string memory attrs = string(
            abi.encodePacked(
                '","attributes":[{"trait_type":"Tier","value":"Tier ',
                _toString(tier),
                '"},{"trait_type":"Class","value":"',
                st.className,
                '"},{"trait_type":"Size Multiplier","value":"',
                _toString(st.sizeBps / 100),
                ".",
                _toString((st.sizeBps % 100) / 10),
                'x"},{"trait_type":"Speed","value":',
                _toString(st.speed),
                '},{"trait_type":"Magnet Radius","value":',
                _toString(st.magnetRadius),
                '},{"trait_type":"Yield Multiplier","value":"',
                _toString(st.multiplier),
                'x"},{"trait_type":"Fur Coat","value":"',
                st.furColor,
                '"},{"trait_type":"Woods Gear","value":"',
                st.gearItem,
                '"},{"trait_type":"Forest Aura","value":"',
                st.auraType,
                '"}]}'
            )
        );

        return string(abi.encodePacked(header, attrs));
    }

    function _buildSVG(
        uint256 seed,
        uint8 tier,
        SquatchStats memory st
    ) internal pure returns (string memory) {
        string[7] memory bodyColors = ["#334155","#784c2a","#4a6b3b","#8c3f2b","#64748b","#14b8a6","#eab308"];
        string[7] memory auraColors = ["#1e293b","#38bdf8","#4ade80","#60a5fa","#f59e0b","#2dd4bf","#facc15"];
        string memory furHex = bodyColors[tier > 6 ? 6 : tier];
        string memory auraHex = auraColors[tier > 6 ? 6 : tier];
        uint256 scalePct = 60 + (uint256(tier) * 10);

        string memory part1 = string(
            abi.encodePacked(
                "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='480' height='480' shape-rendering='crispEdges'>",
                "<rect width='24' height='24' fill='#0b1d13'/>",
                "<circle cx='12' cy='12' r='", _toString(4 + tier), "' fill='none' stroke='", auraHex, "' stroke-width='1' opacity='0.55'/>",
                "<g transform='translate(12,13) scale(", _toString(scalePct / 100), ".", _toString((scalePct % 100) / 10), ") translate(-12,-13)'>",
                "<rect x='8' y='4' width='8' height='6' fill='", furHex, "'/>",
                "<rect x='6' y='9' width='12' height='9' fill='", furHex, "'/>",
                "<rect x='5' y='10' width='2' height='7' fill='", furHex, "'/>",
                "<rect x='17' y='10' width='2' height='7' fill='", furHex, "'/>"
            )
        );
        string memory crown = tier >= 6
            ? "<polygon points='8,4 10,1 12,3 14,1 16,4' fill='#facc15'/>"
            : (tier >= 3 ? "<rect x='9' y='3' width='6' height='1' fill='#4ade80'/>" : "");
        string memory part2 = string(
            abi.encodePacked(
                "<rect x='8' y='18' width='3' height='4' fill='", furHex, "'/>",
                "<rect x='13' y='18' width='3' height='4' fill='", furHex, "'/>",
                "<rect x='9' y='6' width='6' height='2' fill='#1e293b'/>",
                "<rect x='10' y='6' width='1' height='1' fill='", auraHex, "'/>",
                "<rect x='13' y='6' width='1' height='1' fill='", auraHex, "'/>",
                crown,
                "</g><text x='1' y='23' font-size='2' fill='#a7f3d0' font-family='monospace'>T",
                _toString(tier), " ", st.className, " #", _toString(seed % 1000), "</text></svg>"
            )
        );
        return string(abi.encodePacked(part1, part2));
    }

    // โ”€โ”€โ”€ Internal Transfer & Seed Evolution Logic (Standard Transfer Event Only) โ”€โ”€โ”€

    function _mint(address to, uint256 amount) internal {
        require(to != address(0), "Squatchi: mint to zero");
        totalSupply += amount;
        _balances[to] += amount;

        _updateHolderSet(to);
        if (!isPoolOrExempt[to]) {
            _evolveSeed(address(0), to, amount);
        }

        emit Transfer(address(0), to, amount);
    }

    function _transfer(address from, address to, uint256 amount) internal {
        require(from != address(0) && to != address(0), "Squatchi: transfer from/to zero");
        uint256 fromBal = _balances[from];
        require(fromBal >= amount, "Squatchi: transfer exceeds balance");

        unchecked {
            _balances[from] = fromBal - amount;
        }
        _balances[to] += amount;

        _updateHolderSet(from);
        _updateHolderSet(to);

        if (!isPoolOrExempt[from]) {
            _evolveSeed(to, from, amount);
        }
        if (!isPoolOrExempt[to]) {
            _evolveSeed(from, to, amount);
        }

        emit Transfer(from, to, amount);
    }

    function _evolveSeed(address counterparty, address holder, uint256 amount) internal {
        uint256 bal = _balances[holder];
        if (bal == 0) {
            _seeds[holder] = SeedData(0, 0);
            stableInscription[holder] = false;
            return;
        }

        uint8 tier = _tierForBalance(bal);
        uint256 nonce = ++holderNonce[holder];

        uint256 newSeedVal = _seeds[holder].seed;
        if (!stableInscription[holder] || newSeedVal == 0) {
            newSeedVal = uint256(
                keccak256(
                    abi.encodePacked(
                        block.timestamp,
                        block.prevrandao,
                        holder,
                        counterparty,
                        amount,
                        bal,
                        nonce
                    )
                )
            );
            if (newSeedVal == 0) newSeedVal = 1;
        }

        uint256 extraVal = uint256(tier)
            | (stableInscription[holder] ? (uint256(1) << 8) : 0)
            | ((bal / 1e18) << 16);

        _seeds[holder] = SeedData({ seed: newSeedVal, extra: extraVal });
    }

    function _updateHolderSet(address account) internal {
        if (isPoolOrExempt[account]) return;
        uint256 idx = _holderIndex[account];
        uint256 bal = _balances[account];

        if (bal > 0 && idx == 0) {
            _holders.push(account);
            _holderIndex[account] = _holders.length;
        } else if (bal == 0 && idx > 0) {
            uint256 lastIdx = _holders.length;
            if (idx != lastIdx) {
                address lastHolder = _holders[lastIdx - 1];
                _holders[idx - 1] = lastHolder;
                _holderIndex[lastHolder] = idx;
            }
            _holders.pop();
            delete _holderIndex[account];
        }
    }

    function _approve(address holder, address spender, uint256 amount) internal {
        require(holder != address(0) && spender != address(0), "Squatchi: approve zero");
        _allowances[holder][spender] = amount;
        emit Approval(holder, spender, amount);
    }

    function _toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}
contracts/victory-rewards/SquatchiVictoryRewards.solraw ยท download
sha256 65918467e16a9c477e72580801483cb48d671ebfc843a5c5e4e9d0d7f451cb1e
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address a) external view returns (uint256);
}

/// @title Squatchi 1-Month Play-to-Earn Victory Pool
/// @notice Pays 1 $SQUATCHI once per wallet to champions who beat all 10 levels, for 30 days.
contract SquatchiVictoryRewards {
    IERC20 public immutable SQUATCHI;
    uint256 public constant REWARD_AMOUNT = 1e18;
    uint256 public constant ALL_LEVELS_MASK = 0x3FF;
    uint256 public immutable campaignEndTime;
    bytes32 private immutable depositorHash;

    mapping(address => bool) public hasClaimed;
    uint256 public totalWinners;
    bool private locked;

    event VictoryRewarded(address indexed champion, uint256 amount, uint256 timestamp);
    event RemainingRecovered(address indexed to, uint256 amount);

    constructor(address tokenAddress) {
        require(tokenAddress != address(0), "invalid token");
        SQUATCHI = IERC20(tokenAddress);
        campaignEndTime = block.timestamp + 30 days;
        depositorHash = keccak256(abi.encodePacked(msg.sender));
    }

    function expectedProof(address champion) public view returns (bytes32) {
        return keccak256(abi.encodePacked("SQUATCHI_CHAMPION_10_LEVELS", champion, ALL_LEVELS_MASK, address(this)));
    }

    function claimVictoryReward(uint256 starsMask, bytes32 completionProof) external {
        require(!locked, "reentrant");
        require(block.timestamp <= campaignEndTime, "campaign ended");
        require(msg.sender == tx.origin, "EOA only");
        require(!hasClaimed[msg.sender], "already claimed");
        require(starsMask == ALL_LEVELS_MASK, "beat all 10 levels");
        require(completionProof == expectedProof(msg.sender), "bad proof");
        locked = true;
        hasClaimed[msg.sender] = true;
        totalWinners += 1;
        require(SQUATCHI.transfer(msg.sender, REWARD_AMOUNT), "transfer failed");
        locked = false;
        emit VictoryRewarded(msg.sender, REWARD_AMOUNT, block.timestamp);
    }

    function poolBalance() external view returns (uint256) { return SQUATCHI.balanceOf(address(this)); }

    function withdrawTokens(address to, uint256 amount) external {
        require(keccak256(abi.encodePacked(msg.sender)) == depositorHash, "not depositor");
        require(SQUATCHI.transfer(to, amount), "transfer failed");
        emit RemainingRecovered(to, amount);
    }

    function recoverRemainingAfterExpiry(address to) external {
        require(block.timestamp > campaignEndTime, "campaign active");
        require(keccak256(abi.encodePacked(msg.sender)) == depositorHash, "not depositor");
        uint256 bal = SQUATCHI.balanceOf(address(this));
        require(SQUATCHI.transfer(to, bal), "transfer failed");
        emit RemainingRecovered(to, bal);
    }
}
scripts/verify.jsraw ยท download
sha256 f580351613966da162375f7078f3025aab04edc965756c45e09215e635ba6280
// Recompile the verified sources with the exact settings and compare against on-chain bytecode.
// Usage: npm install && node scripts/verify.js [RPC_URL]
// Default RPC: https://mainnet.base.org (public). Requires Node 18+.
const fs = require("fs");
const path = require("path");
const solc = require("solc");

const RPC = process.argv[2] || "https://mainnet.base.org";
const ROOT = path.join(__dirname, "..");
const TARGETS = [
  { dir: "token", file: "Squatchi.sol", name: "Squatchi", address: "0xb2867D1D6590C89591C2811c51e5144077dc1Ab3" },
  { dir: "victory-rewards", file: "SquatchiVictoryRewards.sol", name: "SquatchiVictoryRewards", address: "0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6" },
];

async function rpc(method, params) {
  const r = await fetch(RPC, { method: "POST", headers: { "content-type": "application/json" },
    body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }) });
  const j = await r.json();
  if (j.error) throw new Error(JSON.stringify(j.error));
  return j.result;
}

(async () => {
  if (!solc.version().startsWith("0.8.38+commit.1835d247")) {
    console.error("Expected solc 0.8.38+commit.1835d247, got " + solc.version());
    process.exit(1);
  }
  let ok = true;
  for (const t of TARGETS) {
    const input = JSON.parse(fs.readFileSync(path.join(ROOT, "compiler-settings", t.dir, "standard-json-input.json"), "utf8"));
    // Use the source from contracts/ (must be identical to the verified one).
    input.sources = { [t.file]: { content: fs.readFileSync(path.join(ROOT, "contracts", t.dir, t.file), "utf8") } };
    input.settings.outputSelection = { "*": { "*": ["evm.bytecode.object", "evm.deployedBytecode.object", "evm.deployedBytecode.immutableReferences", "metadata"] } };
    const out = JSON.parse(solc.compile(JSON.stringify(input)));
    const errs = (out.errors || []).filter(e => e.severity === "error");
    if (errs.length) { console.error(errs); process.exit(1); }
    const c = out.contracts[t.file][t.name];
    let compiled = c.evm.deployedBytecode.object.toLowerCase();
    let onchain = (await rpc("eth_getCode", [t.address, "latest"])).slice(2).toLowerCase();
    // Immutable variables are filled in at deploy time; blank those byte ranges on both sides.
    for (const refs of Object.values(c.evm.deployedBytecode.immutableReferences || {})) {
      for (const { start, length } of refs) {
        const z = "0".repeat(length * 2);
        compiled = compiled.slice(0, start * 2) + z + compiled.slice((start + length) * 2);
        onchain = onchain.slice(0, start * 2) + z + onchain.slice((start + length) * 2);
      }
    }
    const match = compiled === onchain;
    ok = ok && match;
    const metaMatch = c.metadata === fs.readFileSync(path.join(ROOT, "compiler-settings", t.dir, "metadata.json"), "utf8");
    console.log(`${t.name} @ ${t.address}`);
    console.log(`  runtime bytecode (incl. metadata hash): ${match ? "MATCH" : "MISMATCH"} (${onchain.length / 2} bytes)`);
    console.log(`  metadata.json identical to compiler output: ${metaMatch ? "yes" : "no"}`);
  }
  process.exit(ok ? 0 : 1);
})();
compiler-settings/token/settings.jsonraw ยท download
sha256 508ba6f0e281705ca108f3170f1d05a4355d5a7aafca82ea55263fe3c29a2111
{
  "contract": "Squatchi.sol:Squatchi",
  "address": "0xb2867D1D6590C89591C2811c51e5144077dc1Ab3",
  "chainId": 8453,
  "compiler": "solc",
  "compilerVersion": "0.8.38+commit.1835d247",
  "optimizer": {
    "runs": 200,
    "enabled": true
  },
  "viaIR": true,
  "evmVersion": "paris",
  "metadataBytecodeHash": "ipfs",
  "sourcifyMatch": "exact_match"
}
compiler-settings/victory-rewards/settings.jsonraw ยท download
sha256 235bed446734fe0f17844319c5a0a37731523c9c71d57eea717a943d781b6e61
{
  "contract": "SquatchiVictoryRewards.sol:SquatchiVictoryRewards",
  "address": "0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6",
  "chainId": 8453,
  "compiler": "solc",
  "compilerVersion": "0.8.38+commit.1835d247",
  "optimizer": {
    "runs": 200,
    "enabled": true
  },
  "viaIR": true,
  "evmVersion": "paris",
  "metadataBytecodeHash": "ipfs",
  "sourcifyMatch": "exact_match"
}
package.jsonraw ยท download
sha256 3e25563d54f87bf868da6ad440b3c24c04ee01c210a5c963279c05d8c87a9e43
{
  "name": "squatchi-contracts",
  "version": "1.0.0",
  "private": true,
  "description": "Verified Solidity source and compiler settings for $SQUATCHI on Base",
  "license": "MIT",
  "scripts": {
    "verify": "node scripts/verify.js"
  },
  "devDependencies": {
    "solc": "0.8.38"
  }
}
README.mdraw ยท download
sha256 0db2822370893e627053935b2d0b13b23cdda628375dc75b66f406e64cd7d287
# squatchi-contracts

Verified Solidity source and compiler settings for **$SQUATCHI (v2)** on Base mainnet (chain ID 8453), plus the Play-to-Earn Victory Rewards contract.

The files in `contracts/` are byte-for-byte identical to the sources verified on Sourcify (**exact match**: creation and runtime bytecode, including the metadata hash).

## Contracts

| Contract | Address | BaseScan | Sourcify |
|---|---|---|---|
| Squatchi ($SQUATCHI) token | `0xb2867D1D6590C89591C2811c51e5144077dc1Ab3` | [BaseScan](https://basescan.org/address/0xb2867D1D6590C89591C2811c51e5144077dc1Ab3#code) | [Sourcify](https://repo.sourcify.dev/8453/0xb2867D1D6590C89591C2811c51e5144077dc1Ab3) |
| SquatchiVictoryRewards | `0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6` | [BaseScan](https://basescan.org/address/0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6#code) | [Sourcify](https://repo.sourcify.dev/8453/0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6) |

Uniswap V2 WETH/SQUATCHI pair: [`0x8b1aDaDB4b15B075C362afCFa7db76b9C32caa9A`](https://basescan.org/address/0x8b1aDaDB4b15B075C362afCFa7db76b9C32caa9A)

## Repository layout

```
contracts/
  token/Squatchi.sol                          # $SQUATCHI token (verified source)
  victory-rewards/SquatchiVictoryRewards.sol  # P2E Victory Rewards (verified source)
compiler-settings/
  token/            settings.json, metadata.json, standard-json-input.json
  victory-rewards/  settings.json, metadata.json, standard-json-input.json
scripts/verify.js   # recompiles and compares against on-chain bytecode
```

`metadata.json` is the Solidity compiler metadata, byte-identical to the file whose IPFS hash is embedded at the end of the deployed bytecode. `standard-json-input.json` is the exact solc input that Sourcify used.

## Compiler settings (both contracts)

| Setting | Value |
|---|---|
| Compiler | solc `0.8.38+commit.1835d247` |
| Optimizer | enabled, `runs: 200` |
| viaIR | `true` |
| evmVersion | `paris` |
| Metadata bytecode hash | `ipfs` |
| Libraries / remappings | none |

## About the token

`Squatchi.sol` is a **custom ERC-20** in a single self-contained file. It has **no external imports**: it does not use the OpenZeppelin library. It includes a small hand-written `Ownable` contract (owner, `onlyOwner`, `renounceOwnership`). The NatSpec header mentions "standard OpenZeppelin Ownable renouncement"; that refers to the familiar renounce pattern, but the code itself is custom.

Besides standard ERC-20 functions (`transfer`, `approve`, `transferFrom`, `balanceOf`, `allowance`, `totalSupply`), the token has an "ERC-20i dynamic inscription" layer:
- Each holder gets a pseudo-random seed (derived from `block.timestamp`, `block.prevrandao`, and the transfer data) that changes on transfer. A holder can freeze it with `stabilizeInscription(true)`.
- Tier, game stats, and an on-chain SVG / JSON metadata are derived from the balance and the seed (view functions only).
- An on-chain holder list (`getHolderCount`, `getHolderByIndex`, `getHoldersList`).

Because of this bookkeeping, transfers cost more gas than a plain ERC-20 (about 290k gas for a buy). The extra logic never blocks or taxes a transfer.

### Facts (checkable on-chain)

- **Fixed supply: 15,000,000 SQUATCHI** (18 decimals). The entire supply is minted at deployment, as defined in the constructor. `_mint` is `internal` and is never called after deployment. **There is no mint function.**
- **0% tax** on buy, sell, and transfer. There are no fees, max-transaction or max-wallet limits, or cooldowns.
- **No blacklist, no pause, no proxy.** The contract has no `delegatecall`, no `selfdestruct`, and no upgrade mechanism. `isPoolOrExempt` is written only in the constructor, and it only skips seed/holder-list bookkeeping.
- **Ownership renounced:** `owner()` returns `0x0000000000000000000000000000000000000000`. The only `onlyOwner` function was `renounceOwnership`.
- **LP 100% burned:** all Uniswap V2 LP tokens for the pair are held by `0x000000000000000000000000000000000000dEaD`. The only exception is the standard 1000-wei `MINIMUM_LIQUIDITY` that Uniswap V2 permanently locks at `0x0`.
- `lockedFounderBalance()` is a `pure` view function that returns `0`.

## About the Victory Rewards contract

`SquatchiVictoryRewards.sol` is a simple reward pool for the Squatchi arcade game. It also has no imports, and it uses a minimal `IERC20` interface.
- It pays **1 SQUATCHI**, once per wallet (EOA only), through `claimVictoryReward`. The caller must submit the all-levels mask (`0x3FF`) and a completion proof equal to `expectedProof(msg.sender)`. That proof is a deterministic hash, so the contract does **not** verify gameplay on-chain. It is a simple per-wallet claim.
- The claim window is 30 days from deployment (`campaignEndTime`).
- The depositor (the deployer, stored as a hash) can withdraw tokens with `withdrawTokens` at any time, and can recover the remainder with `recoverRemainingAfterExpiry` after the campaign ends. The contract holds only SQUATCHI that was sent to it, and it has no access to any other wallet.

## How to verify

Anyone can recompile the sources and confirm they produce the deployed bytecode.

### Option A: script (Node 18+)

```bash
npm install          # installs solc-js 0.8.38
node scripts/verify.js                      # uses the public RPC https://mainnet.base.org
node scripts/verify.js https://your-base-rpc  # or your own RPC
```

The script compiles each contract from `contracts/` with `compiler-settings/*/standard-json-input.json`. It fetches the runtime code with `eth_getCode` and compares the two byte for byte, including the CBOR metadata hash. Immutable slots are masked on both sides, because they are filled in at deploy time. It also checks that `metadata.json` equals the compiler's metadata output. The expected output is `MATCH` for both contracts.

### Option B: solc CLI

```bash
solc-0.8.38 --standard-json compiler-settings/token/standard-json-input.json > out.json
# compare out.json .contracts["Squatchi.sol"].Squatchi.evm.deployedBytecode.object
# with: cast code 0xb2867D1D6590C89591C2811c51e5144077dc1Ab3 --rpc-url https://mainnet.base.org
```

Add `"outputSelection": {"*": {"*": ["evm.deployedBytecode", "metadata"]}}` to `settings` in the input first.

### Option C: Sourcify / BaseScan

Both contracts show **exact match** on Sourcify, and BaseScan shows them as verified. You can also upload `contracts/<x>/<file>.sol` together with `compiler-settings/<x>/metadata.json` to [verify.sourcify.dev](https://verify.sourcify.dev).

## Links

- Website: https://squatchi-base.surge.sh/
- X: https://x.com/SquatchiBase
- Telegram: https://t.me/SquatchiBase
- Contact: [email protected]

## Disclaimer

$SQUATCHI is a **meme token** made for fun and community, with an arcade game attached. It has no intrinsic value, carries no promise of returns, and has no formal roadmap obligation. Nothing in this repository is financial advice (**NFA**). Crypto assets are highly volatile and you can lose everything you put in. Do your own research. The contracts have **not** been professionally audited. They are provided "as is" under the MIT License.
LICENSEraw ยท download
sha256 a916759ff11ac8644fc1bb0ba4c151e544ac8fbf6e2fd514651aa11303fa1fa2
MIT License

Copyright (c) 2026 Squatchi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Disclaimer

$SQUATCHI is a meme token made for fun and community, with an arcade game attached. It has no intrinsic value, carries no promise of returns, and has no formal roadmap obligation. Nothing in this repository is financial advice (NFA). Crypto assets are highly volatile and you can lose everything you put in. Do your own research. The contracts have not been professionally audited. They are provided "as is" under the MIT License.

โ† Back to home