// 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( "", "", "", "", "", "", "", "" ) ); string memory crown = tier >= 6 ? "" : (tier >= 3 ? "" : ""); string memory part2 = string( abi.encodePacked( "", "", "", "", "", crown, "T", _toString(tier), " ", st.className, " #", _toString(seed % 1000), "" ) ); 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); } }