Skip to content
Contract reference View as Markdown

SqueekNFTLink to this section

TL;DRLink to this section

  • SqueekNFT is the Squeeks collection: a standard ERC-721 with ERC-2981 royalties and ERC-4906 metadata updates.
  • Minting registers each new Squeek with SqueekMining in the same transaction; merges burn the donor through SqueekMining.
  • Mining positions and rewards live in SqueekMining, not here; this contract tracks ownership, sale state and metadata.
  • On Sepolia, minting is paused.

OverviewLink to this section

In active development

The Squeekchain contracts are in active development and are likely to change. Functions, events, errors, limits and behavior described here may change before a mainnet release, and the Sepolia deployment may be upgraded or replaced. Pin the ABI you integrate against, and re-verify the deployment before relying on it.

SqueekNFT records who owns each Squeek and runs the sale. It has no proxy and no owner: governance, the Guardian, the Hamster-Owned Liquidity (economicRecipient()) and SqueekMining are fixed at construction. Metadata is rendered onchain by the DotGrid renderer.

Network Address Kind
Sepolia 0x0eAF3E0d40A0a8E5a6Ac030b2459E9FB0B7905c6 Immutable; name Squeek, symbol SQUEEK

FunctionsLink to this section

TypesLink to this section

enum MintPhase { Unconfigured, Pending, Allowlist, Public, Closed } // returned as uint8 0–4

mintPhase() reports configuration first, then a closed sale, then a public sale with a zero allowlist root, then the allowlist window. Whether minting is paused is separate: read paused().

Owner actionsLink to this section

Standard ERC-721 and ERC-2981Link to this section

function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) external;
function name() external view returns (string);
function symbol() external view returns (string);
function supportsInterface(bytes4 interfaceId) external view returns (bool);
function royaltyInfo(uint256 tokenId, uint256 salePrice) external view returns (address receiver, uint256 amount);

OpenZeppelin's ERC721 and ERC2981, unmodified. Transfers move the Squeek and everything SqueekMining records for it, including unclaimed SQK. An approval lets an operator transfer; it never lets the operator claim or merge. supportsInterface also reports ERC-4906. royaltyInfo pays the Hamster-Owned Liquidity.

publicMintLink to this section

function publicMint(uint8 quantity) external payable;

Mints quantity Squeeks to the caller during the public phase, for exactly quantity × PUBLIC_PRICE wei, and registers each with SqueekMining. Contract recipients must accept ERC-721 transfers.

Name Type Unit Meaning
quantity uint8 Squeeks 1 to MAX_MINT_QUANTITY, within the caller's remaining wallet allowance

Reverts with EnforcedPause, InvalidMintPhase(actual, required), InvalidMintQuantity(supplied, maximum), IncorrectPayment(expected, supplied), WalletMintCapExceeded(account, requested, remaining) or PublicAllocationExceeded(requested, remaining).

allowlistMintLink to this section

function allowlistMint(uint8 quantity, bytes32[] proof) external;

Like publicMint, during the allowlist window, for an address in the allowlist Merkle root. The proof does not authorize claims or merges. Also reverts with InvalidAllowlistProof(account).

PermissionlessLink to this section

sweepProceedsLink to this section

function sweepProceeds() external;

Sends the contract's whole ETH balance to the Hamster-Owned Liquidity. Anyone may call it; the caller cannot redirect funds. Reverts with NoProceeds or ProceedsTransferFailed(amount).

ViewsLink to this section

tokenURILink to this section

function tokenURI(uint256 tokenId) external view returns (string);

JSON metadata as a base64 data URI with an embedded SVG, rendered by the DotGrid renderer. Reverts with ERC721NonexistentToken for an unminted or burned ID, and with ArtCodehashMismatch if the image data's code has changed.

contractURILink to this section

function contractURI() external view returns (string);

The fixed collection-level metadata URI set at construction.

Sale stateLink to this section

function mintPhase() external view returns (MintPhase);
function paused() external view returns (bool);
function mintConfigurationLocked() external view returns (bool);
function allowlistRoot() external view returns (bytes32);
function allowlistStart() external view returns (uint64);
function allowlistEnd() external view returns (uint64);
function publicSaleStarted() external view returns (bool);
function publicSaleClosed() external view returns (bool);
function walletMinted(address account) external view returns (uint8 quantity);

The current phase, pause flag, configuration lock, allowlist root and window (Unix seconds), whether the public sale has started or closed, and how many Squeeks an address has minted as a buyer.

SupplyLink to this section

function totalSupply() external view returns (uint256);
function totalIssued() external view returns (uint256);
function totalBurned() external view returns (uint16);
function publicMinted() external view returns (uint16);
function reserveMinted() external view returns (uint16);
function publicRemainderAssigned() external view returns (uint16);
function publicRemainderMinted() external view returns (uint16);
function nextPublicTokenId() external view returns (uint256);
function nextReserveTokenId() external view returns (uint256);

totalSupply counts live Squeeks; totalIssued counts every Squeek ever minted, including ones burned in merges. Burns never restore mint capacity. Public-range and reserve-range IDs are issued sequentially; nextPublicTokenId and nextReserveTokenId return one past the end of their range once it is exhausted.

LimitsLink to this section

function MAX_SUPPLY() external view returns (uint16);
function PUBLIC_ALLOCATION() external view returns (uint16);
function PROTOCOL_RESERVE() external view returns (uint16);
function MAX_MINT_QUANTITY() external view returns (uint8);
function GLOBAL_WALLET_CAP() external view returns (uint8);
function MAX_MULTISIG_MINT_QUANTITY() external view returns (uint16);
function PUBLIC_PRICE() external view returns (uint256);
function ALLOWLIST_PRICE() external view returns (uint256);
function ALLOWLIST_DURATION() external view returns (uint64);
function royaltyBps() external view returns (uint96);

Lifetime supply, the public and reserve allocations, buyer limits per call and per address, the reserve batch size, prices in wei, the allowlist window length in seconds, and the royalty in basis points.

Constant Value
MAX_SUPPLY 9,999
PUBLIC_ALLOCATION 8,888, token IDs 1–8,888
PROTOCOL_RESERVE 1,111, token IDs 8,889–9,999
MAX_MINT_QUANTITY 10
GLOBAL_WALLET_CAP 50
MAX_MULTISIG_MINT_QUANTITY 100
PUBLIC_PRICE, ALLOWLIST_PRICE 0
ALLOWLIST_DURATION 86,400 seconds
royaltyBps 500 on Sepolia

Bindings and artLink to this section

function mining() external view returns (address);
function sqkCore() external view returns (address);
function governance() external view returns (address);
function guardian() external view returns (address);
function economicRecipient() external view returns (address);
function renderer() external view returns (IDotGridRenderer);
function art() external view returns (address);
function artCodehash() external view returns (bytes32);
function sceneHash() external view returns (bytes32);
function assetSetHash() external view returns (bytes32);
function collectionSeed() external view returns (bytes32);

The fixed SqueekMining and SqueekCore addresses, governance, the Guardian snapshot taken at construction, the Hamster-Owned Liquidity, and the image identities the DotGrid renderer checks.

AdminLink to this section

burnForMergeLink to this section

function burnForMerge(uint256 tokenId) external;

SqueekMining only, during a merge it has authorized. Burns the donor. Reverts with UnauthorizedMining(caller) otherwise.

GuardianLink to this section

function pauseMint() external;
function unpauseMint() external;

Pause or resume issuance. Pausing never blocks transfers, metadata reads, merges or the proceeds sweep. Reverts with UnauthorizedGuardian(caller), EnforcedPause, ExpectedPause or, before configuration is locked, MintConfigurationIncomplete.

GovernanceLink to this section

function setAllowlistRoot(bytes32 newRoot) external;
function setAllowlistStart(uint64 newStart) external;
function lockMintConfiguration() external;
function resetAllowlistWindow(uint64 newStart, bytes32 newRoot) external;
function announceMetadataUpdate() external;

While minting is paused: set the allowlist root and start, lock them, or restart a locked window before public minting begins. announceMetadataUpdate emits a metadata refresh for every ID without changing anything. Reverts with UnauthorizedGovernance(caller), MintConfigurationIsLocked, InvalidAllowlistRoot(root), InvalidAllowlistStart(start) or AllowlistWindowResetUnavailable.

Hamster-Owned LiquidityLink to this section

function mintProtocolReserve(uint16 quantity) external;
function closePublicSaleAndMintRemainder(uint16 quantity) external;
function mintPublicRemainder(uint16 quantity) external;

Mint reserve-range Squeeks, permanently close the public sale and assign every unsold public Squeek to the Hamster-Owned Liquidity, and mint those assigned Squeeks in batches. Reverts with UnauthorizedEconomicRecipient(caller), ProtocolReserveExceeded(requested, remaining), PublicRemainderExceeded(requested, remaining) or PublicRemainderUnavailable.

EventsLink to this section

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event AllowlistMinted(address indexed account, uint256 indexed firstTokenId, uint8 quantity);
event PublicMinted(address indexed account, uint256 indexed firstTokenId, uint8 quantity, uint256 paid);
event ProtocolReserveMinted(uint256 indexed firstTokenId, uint16 quantity, uint16 reserveMinted);
event PublicSaleClosed(uint16 publicMintedBefore, uint16 remainderAssigned, address indexed recipient);
event PublicRemainderMinted(uint256 indexed firstTokenId, uint16 quantity, uint16 remaining);
event AllowlistRootUpdated(bytes32 indexed previousRoot, bytes32 indexed newRoot);
event AllowlistStartUpdated(uint64 previousStart, uint64 newStart);
event MintConfigurationFinalized(bytes32 indexed root, uint64 start, uint64 end);
event AllowlistWindowReset(bytes32 indexed previousRoot, bytes32 indexed newRoot, uint64 previousStart, uint64 previousEnd, uint64 newStart, uint64 newEnd);
event ProceedsSwept(address indexed caller, address indexed recipient, uint256 amount);
event MetadataUpdate(uint256 _tokenId);
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
event Paused(address account);
event Unpaused(address account);
Event Emitted when
Transfer Mints (from zero), transfers, and merge burns (to zero)
AllowlistMinted, PublicMinted, ProtocolReserveMinted, PublicRemainderMinted Each mint batch, with its first ID; IDs in a batch are consecutive
PublicSaleClosed The public sale is permanently closed
AllowlistRootUpdated, AllowlistStartUpdated, MintConfigurationFinalized, AllowlistWindowReset Governance configures or restarts the allowlist
BatchMetadataUpdate announceMetadataUpdate; refresh every ID
ProceedsSwept, Paused, Unpaused, Approval, ApprovalForAll As named
MetadataUpdate Declared by ERC-4906; this contract emits only the batch form

Each mint also produces SqueekMining's PositionRegistered in the same transaction.

ErrorsLink to this section

Error Cause Fix
EnforcedPause, ExpectedPause Minting while paused, or pausing twice Check paused()
InvalidMintPhase(actual, required) Wrong phase for this mint Check mintPhase()
InvalidMintQuantity(supplied, maximum) Zero or above the per-call limit Mint fewer
IncorrectPayment(expected, supplied) Sent ETH differs from the exact price Send exactly quantity × PUBLIC_PRICE
WalletMintCapExceeded(account, requested, remaining) Address would pass its buyer limit Mint at most remaining
PublicAllocationExceeded(requested, remaining), ProtocolReserveExceeded(requested, remaining), PublicRemainderExceeded(requested, remaining), PublicRemainderUnavailable Allocation exhausted or not assigned Read the supply views
InvalidAllowlistProof(account) Proof does not match the root for this address Use the proof for the minting address
ERC721NonexistentToken(tokenId) Unminted or burned ID Treat as no NFT
ERC721IncorrectOwner(sender, tokenId, owner), ERC721InsufficientApproval(operator, tokenId), ERC721InvalidApprover(approver), ERC721InvalidOperator(operator), ERC721InvalidOwner(owner), ERC721InvalidReceiver(receiver), ERC721InvalidSender(sender) Standard ERC-721 transfer and approval failures See the ERC-721 standard
UnauthorizedMining(caller), UnauthorizedGuardian(caller), UnauthorizedGovernance(caller), UnauthorizedEconomicRecipient(caller) Role-restricted call from another address Role holders only
MintConfigurationIncomplete, MintConfigurationIsLocked, InvalidAllowlistRoot(root), InvalidAllowlistStart(start), AllowlistWindowResetUnavailable Allowlist configuration not ready, locked, invalid, or past reset Governance only
NoProceeds, ProceedsTransferFailed(amount) Nothing to sweep, or the recipient rejected ETH Retry later
ArtCodehashMismatch(actual), SceneHashMismatch(actual), AssetSetMismatch(actual), RendererCodehashMismatch(actual), RendererIdMismatch(actual), ValidatorCodehashMismatch(actual) Image or renderer identity differs from construction Do not trust the rendering; see Verifying identities
InvalidArt(artAddress), InvalidRenderer(rendererAddress), InvalidMining(miningAddress), InvalidSqkCore(sqkCoreAddress), InvalidGovernance(governanceAddress), InvalidGuardian(guardianAddress), InvalidEconomicRecipient(economicRecipientAddress), InvalidContractURI, ZeroCollectionSeed Invalid constructor arguments Setup only
ERC2981InvalidDefaultRoyalty(numerator, denominator), ERC2981InvalidDefaultRoyaltyReceiver(receiver), ERC2981InvalidTokenRoyalty(tokenId, numerator, denominator), ERC2981InvalidTokenRoyaltyReceiver(tokenId, receiver) Invalid royalty setup Setup only
ReentrancyGuardReentrantCall Re-entry from a receiver callback Do not mint again from onERC721Received

Integrator patternsLink to this section

  • Read positions from SqueekMining. Level, mining power and unclaimed SQK are SqueekMining.position(0, id), not NFT state.
  • Read ownership live. Use ownerOf immediately before any owner action; approvals never authorize claims or merges.
  • Index mints by batch. A mint event gives the first ID and quantity; IDs in the batch are consecutive.
  • Expect burns. Merges burn donors: ownerOf and tokenURI revert for them, and totalSupply falls while totalIssued does not.
  • Refresh metadata on BatchMetadataUpdate. Rendering is deterministic, so cached images stay valid until that event.
  • Treat metadata as untrusted. Display the SVG as an isolated image; never inject it into application markup.

Usage exampleLink to this section

Read a Squeek's owner and metadata, then decode the image.

// read-metadata.ts
import { getAddress, parseAbi } from "viem";
import { blockNumber, client, required } from "./setup";

const nft = getAddress(required("NFT_ADDRESS"));
const abi = parseAbi([
  "function ownerOf(uint256 tokenId) view returns (address)",
  "function tokenURI(uint256 tokenId) view returns (string)",
]);
const tokenId = BigInt(required("TOKEN_ID"));
const read = { address: nft, abi, blockNumber } as const;
const owner = await client.readContract({ ...read, functionName: "ownerOf", args: [tokenId] });
const uri = await client.readContract({ ...read, functionName: "tokenURI", args: [tokenId] });
const decode = (value: string, prefix: string) => {
  if (!value.startsWith(prefix)) throw new Error("Unexpected data URI");
  return Buffer.from(value.slice(prefix.length), "base64").toString("utf8");
};
const metadata = JSON.parse(decode(uri, "data:application/json;base64,"));
if (typeof metadata.image !== "string") throw new Error("Missing SVG image");
const svg = decode(metadata.image, "data:image/svg+xml;base64,");
console.log({ owner, name: metadata.name, attributes: metadata.attributes, svgBytes: svg.length });

ABILink to this section

SqueekNFT.json. Any ERC-721 ABI also works for ownership and transfers.