> Documentation index: https://docs.squeekchain.com/llms.txt. Use it to find every page.

# SqueekNFT

> SqueekNFT function by function — the Squeeks ERC-721 collection, minting phases and limits, merge burns, metadata, royalties, events and errors, and the published ABI.

Web version: https://docs.squeekchain.com/developers/nft/

## TL;DR

- 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.

## Overview

!!! warning "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](https://docs.squeekchain.com/developers/dotgrid.md).

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

## Functions

### Types

```solidity
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 actions

#### Standard ERC-721 and ERC-2981

```solidity
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](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c64a1edb67b6e3f4a15cca8909c9482ad33a02b0/contracts/token/ERC721/ERC721.sol) and [ERC2981](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c64a1edb67b6e3f4a15cca8909c9482ad33a02b0/contracts/token/common/ERC2981.sol), 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.

#### `publicMint`

```solidity
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)`.

#### `allowlistMint`

```solidity
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)`.

### Permissionless

#### `sweepProceeds`

```solidity
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)`.

### Views

#### `tokenURI`

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

JSON metadata as a base64 data URI with an embedded SVG, rendered by the [DotGrid renderer](https://docs.squeekchain.com/developers/dotgrid.md). Reverts with `ERC721NonexistentToken` for an unminted or burned ID, and with `ArtCodehashMismatch` if the image data's code has changed.

#### `contractURI`

```solidity
function contractURI() external view returns (string);
```

The fixed collection-level metadata URI set at construction.

#### Sale state

```solidity
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.

#### Supply

```solidity
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.

#### Limits

```solidity
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.

#### Bindings and art

```solidity
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](https://docs.squeekchain.com/developers/dotgrid.md#verifying-identities) checks.

### Admin

#### `burnForMerge`

```solidity
function burnForMerge(uint256 tokenId) external;
```

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

#### Guardian

```solidity
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`.

#### Governance

```solidity
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 Liquidity

```solidity
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`.

## Events

```solidity
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.

## Errors

| 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](https://docs.squeekchain.com/developers/dotgrid.md#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 patterns

- **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 example

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

```ts
// 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 });
```

## ABI

[`SqueekNFT.json`](https://docs.squeekchain.com/assets/abi/SqueekNFT.json). Any ERC-721 ABI also works for ownership and transfers.

## Related pages

- [DotGrid renderer](https://docs.squeekchain.com/developers/dotgrid.md)
- [SqueekMining](https://docs.squeekchain.com/developers/mining.md)
- [Getting a Miner](https://docs.squeekchain.com/miners/minting.md)
- [Ownership and transfers](https://docs.squeekchain.com/miners/ownership.md)
