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.
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
| Contract | Address (Base) |
|---|---|
| Historical SQUATCHI v2 token | 0xb2867D1D6590C89591C2811c51e5144077dc1Ab3 |
| SquatchiVictoryRewards | 0x7f2101F20a265A1Be64AC8A3fffE0f0cBf991ED6 |
Compiler settings (both contracts)
| Compiler | solc 0.8.38+commit.1835d247 |
| Optimizer | enabled, runs: 200 |
| viaIR | true |
| evmVersion | paris |
| Metadata bytecode hash | ipfs |
| Libraries / remappings | none |
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.solยท raw โcontracts/victory-rewards/SquatchiVictoryRewards.solยท raw โscripts/verify.jsยท raw โcompiler-settings/token/settings.jsonยท raw โcompiler-settings/victory-rewards/settings.jsonยท raw โpackage.jsonยท raw โREADME.mdยท raw โLICENSEยท raw โcompiler-settings/token/standard-json-input.jsonยท raw โ (20,468 bytes, single-line JSON)compiler-settings/token/metadata.jsonยท raw โ (8,196 bytes, single-line JSON)compiler-settings/victory-rewards/standard-json-input.jsonยท raw โ (3,221 bytes, single-line JSON)compiler-settings/victory-rewards/metadata.jsonยท raw โ (3,414 bytes, single-line JSON)
// 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);
}
}
// 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);
}
}
// 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);
})();
{
"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"
}
{
"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"
}
{
"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"
}
}
# 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.
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.