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

# SqueekMining

> SqueekMining version 6 function by function — positions, checkpoints, claims and merges, its events and errors, integration patterns, and the published ABI.

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

## TL;DR

- SqueekMining issues a fixed 5,600 SQK for every completed Squeek block and divides it across active mining power through one reward index.
- Owners claim and merge; anyone can call `checkpoint()` to process settled rounds; the Squeeks collection registers mints.
- New power earns from the round after a mint or merge, and merge cooldowns count completed Squeek blocks.
- Call it through the proxy at `0x3beE588fC20dE788Bac17d33b41Da7346cb27EC4` on Sepolia.

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

SqueekMining reads `SqueekCore`'s certified history, mints SQK through `SqueekToken` for each completed Squeek block, and holds it until Miner owners claim. It tracks one position per NFT: level, active and pending mining power, merge cooldown and scaled unclaimed credit. There is no halving, multiplier, staking or user boost.

| Network | Address | Kind |
|---|---|---|
| Sepolia | `0x3beE588fC20dE788Bac17d33b41Da7346cb27EC4` | Transparent proxy; implementation version 6, upgradeable through the [governance timelock](https://docs.squeekchain.com/security/governance.md) |

Positions are keyed by a collection selector and a token ID. The Squeeks collection is selector `0`; every `collection` parameter below takes that value. Identities and launch values are under [Deployments](https://docs.squeekchain.com/developers.md#deployments).

## Functions

### Types

```solidity
struct MiningPosition {
    uint64 activeWeight;   // mining power earning now
    uint64 pendingWeight;  // full replacement power from pendingRound; 0 if none
    uint40 pendingRound;   // SqueekCore round the pending power starts earning; 0 if none
    uint48 nextMergeBlock; // completed Squeek block height at which a merge is allowed
    uint8 level;           // 0 means no live position
    uint256 lastIndex;     // reward index the credit was last caught up to
    uint256 accruedScaled; // unclaimed credit in SQK base units × 2^128
}

struct MiningState {
    uint256 minted;                      // SQK base units ever funded
    uint256 paid;                        // SQK base units ever claimed
    uint256 totalWeight;                 // active mining power across all positions
    uint256 rewardIndex;                 // cumulative scaled reward per unit of power
    uint64 closedThroughBlock;           // Squeek block height funding has passed
    uint64 lastActivationRound;          // round of the last applied power change
    uint256 weightActivationCursor;      // next queued activation to apply
    uint256 weightActivationCount;       // activations ever queued
    bool bootstrapped;
    uint64 processedThroughRound;        // last SqueekCore round funded
    uint64 closedCumulativeWork;         // SqueekCore endpoint at that round
    uint96 closedCumulativeBlockUnits;
    uint32 closedWorkPerThousandBlocks;
}

struct MiningConfiguration {
    address core;
    bytes32 streamId;
    address governance;
    address token;
    address genesisCollection;           // the Squeeks collection
    uint64 genesisTimestamp;
    uint16 genesisMaxSupply;
    uint64 maxWorkPerRound;
    uint64 minimumTimelockDelay;
    uint128 supplyCap;
    uint8 genesisMaximumLevel;
    uint32 initialWorkPerThousandBlocks;
}

struct MiningLevel { uint64 weight; uint64 cooldownBlocks; }

struct MiningCollectionPolicy {
    address nft;
    uint16 maxSupply;
    uint8 maximumLevel;
    MiningLevel[] levels;                // index 0 is level 1
}

struct MiningAccounting {
    uint256 closedResidual;              // scaled rounding leftovers from past weight epochs
    uint256 openResidual;                // scaled leftovers in the current epoch
    uint256 miningBalance;               // SQK base units SqueekMining holds
    uint256 trackedCustody;              // minted − paid, in SQK base units
}

struct MiningWeightActivation { uint64 round; uint192 addedWeight; }

struct MiningLaunchConfig {
    address core;
    bytes32 streamId;
    address governance;
    uint64 minimumTimelockDelay;
    uint64[] levelWeights;
    uint64[] cooldownBlocks;
}
```

Fields of `uint48` and narrower decode to `number` in viem; wider fields decode to `bigint`.

### Owner actions

These are signed by the NFT's current owner. Approved operators cannot call them. [Build owner actions](https://docs.squeekchain.com/developers/owner-actions.md) covers preparing and simulating them.

#### `claim`

```solidity
function claim(MiningCollection collection, uint256[] tokenIds) external returns (uint256 paid);
```

Pays the caller the unclaimed SQK of 1–32 NFTs they own, in one transfer. Credit is summed in scaled units, the whole base units are paid, and the fraction stays on the first (lowest) ID.

| Name | Type | Unit | Meaning |
|---|---|---|---|
| `collection` | `uint8` | — | `0` for Squeeks |
| `tokenIds` | `uint256[]` | token IDs | 1–32 live IDs in strictly ascending order |

| Returns | Type | Unit |
|---|---|---|
| `paid` | `uint256` | SQK base units (`10^-18` SQK); can be `0` |

Reverts with `Unauthorized` if the caller does not own every ID, `InvalidClaim` for an empty, oversized, duplicate or unsorted list, and `InvalidPosition` for a burned or unminted ID. Claims do not read `SqueekCore` and work while it is paused.

#### `merge`

```solidity
function merge(MiningCollection collection, uint256 survivorId, uint256 donorId) external;
```

Burns `donorId` and moves `survivorId` up one level. The survivor keeps both positions' unclaimed credit and their combined active power until the next round, when its new level's power starts earning. **The donor cannot be restored.**

| Name | Type | Unit | Meaning |
|---|---|---|---|
| `collection` | `uint8` | — | `0` for Squeeks |
| `survivorId` | `uint256` | token ID | The NFT that keeps its ID and image |
| `donorId` | `uint256` | token ID | The NFT that is burned |

Both must be owned by the caller, distinct, at the same level below the maximum, with no pending power, and with `nextMergeBlock` at or below `SqueekCore`'s completed height. Reverts with `Unauthorized`, `InvalidPosition`, `InvalidMerge`, `PendingActivation(tokenId, round)` or `CooldownActive(tokenId, nextMergeBlock)`.

### Permissionless

#### `checkpoint`

```solidity
function checkpoint() external;
```

Processes settled `SqueekCore` rounds: funds each stretch of constant total power with 5,600 SQK per completed block, applies up to 32 queued power changes in round order, then mints the total once. Anyone can call it and pays the gas; there is no caller reward and no caller-chosen input.

Reverts with `CorePaused` while `SqueekCore` is paused and `RoundNotSettled(round)` when no round beyond `processedThroughRound` is certified. Any dependency or accounting failure (`CoreUnavailable`, `CoreInconsistent`, `CoreRegression`, `CoreFutureRound`, `MintMismatch`, `TokenSupplyMismatch`, `CustodyShortfall`) reverts the whole call.

### Views

#### `position`

```solidity
function position(MiningCollection collection, uint256 tokenId) external view returns (MiningPosition);
```

One NFT's position, caught up to the current reward index without calling `SqueekCore`. A burned or unminted ID returns level `0` rather than reverting.

#### `claimable`

```solidity
function claimable(MiningCollection collection, uint256[] tokenIds) external view returns (uint256 amount);
```

What `claim` would pay for these IDs now, in SQK base units. It applies claim's list rules (`InvalidClaim`, `InvalidPosition`) but **does not check ownership**, and it changes with every checkpoint, claim and merge.

#### `checkpointStatus`

```solidity
function checkpointStatus() external view returns (bool corePaused, uint64 nextRound, bool roundSettled);
```

Whether `SqueekCore` is paused, the next round to process, and whether `SqueekCore` has certified it. `roundSettled` true means a checkpoint can advance, not that it will mint: stretches with no completed blocks or no active power fund zero.

#### `state`

```solidity
function state() external view returns (MiningState);
```

Totals, active power, reward index, the activation queue cursor, and the last processed round's `SqueekCore` endpoint. Field units are in [Types](https://docs.squeekchain.com/developers/mining.md#types).

#### `configuration`

```solidity
function configuration() external view returns (MiningConfiguration);
```

Fixed bindings and launch values: `SqueekCore`, stream, governance, `SqueekToken`, the Squeeks collection, genesis time, supply limit, work cap, minimum timelock delay, supply cap, maximum level and initial difficulty.

#### `collectionPolicy`

```solidity
function collectionPolicy(MiningCollection collection) external view returns (MiningCollectionPolicy policy);
```

The collection's NFT address, supply limit, maximum level and level table. Reverts with `CollectionNotBound` for a selector with no collection.

#### `weightForLevel`

```solidity
function weightForLevel(MiningCollection collection, uint8 level) external view returns (uint64);
```

Mining power at `level`. Reverts with `InvalidBounds` for level `0` or above the maximum.

#### `cooldownBlocks`

```solidity
function cooldownBlocks(MiningCollection collection, uint8 level) external view returns (uint64);
```

Completed Squeek blocks a Miner must wait after reaching `level` before merging again; `0` at the maximum level, which cannot merge.

#### `weightActivation`

```solidity
function weightActivation(uint256 index) external view returns (MiningWeightActivation);
```

Queued power change number `index` (zero-based), including ones already applied: its round and the total power it adds. Reverts with `InvalidBounds` at or beyond `state().weightActivationCount`.

#### `accounting`

```solidity
function accounting() external view returns (MiningAccounting result);
```

Rounding leftovers and SQK custody. See [Scaled credit and custody](https://docs.squeekchain.com/developers/mining.md#scaled-credit-and-custody).

#### Constants

```solidity
function REWARD_PER_BLOCK() external view returns (uint256);           // 5,600 × 10^18 SQK base units
function SUPPLY_CAP() external view returns (uint256);                 // 2,100,000,000 × 10^18
function MAX_CLAIMS() external view returns (uint256);                 // 32 IDs per claim
function MAX_CHECKPOINT_ACTIVATIONS() external view returns (uint256); // 32 power changes per checkpoint
function MAX_LEVELS() external view returns (uint256);                 // 10
function SCALE() external view returns (uint256);                      // 2^128, the scaled-credit unit
```

#### Identity

```solidity
function core() external view returns (address);
function bootstrapAuthority() external view returns (address);
function proxyAdmin() external view returns (address admin);
function implementationCodeAddress() external view returns (address);
function implementationCodeVersion() external pure returns (uint64);
function implementationStorageLayoutDigest() external pure returns (bytes32);
```

The bound `SqueekCore` proxy; the deployer still allowed to complete bootstrap (zero after launch); the ProxyAdmin; and the running implementation's address, version (`6`) and storage-layout digest. [Verify the deployment](https://docs.squeekchain.com/developers/quickstart.md#verify-the-deployment) checks them.

### Admin and setup

#### `onMint`

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

Called by the Squeeks collection when it mints an NFT. Registers a Level 1 position whose power starts in the next `SqueekCore` round and sets its merge cooldown. Any other caller reverts with `Unauthorized`.

#### `initialize`

```solidity
function initialize(MiningLaunchConfig config) external;
```

Sets launch policy while the proxy is being deployed; it cannot be called afterwards (`InvalidInitialization`, `NotInitializing`). Requires a fresh, paused `SqueekCore` with zero work (`CoreNotFresh`).

#### `completeBootstrap`

```solidity
function completeBootstrap(address token_, address genesisNft) external;
```

Called once by the bootstrap authority during deployment to bind `SqueekToken` and the Squeeks collection after checking the token cap and minter, the collection's wiring, the proxy admin and governance, and that `SqueekCore` is still at zero. Reverts with `BootstrapAlreadyComplete` afterwards.

## Events

```solidity
event MiningInitialized(address indexed core, bytes32 indexed streamId, address governance);
event MiningBootstrapped(address indexed token, address indexed collection);
event CollectionPolicyUpdated(MiningCollection indexed collection, address nft, uint8 maximumLevel, uint64[] levelWeights, uint64[] cooldownBlocks);
event PositionRegistered(MiningCollection indexed collection, uint256 indexed tokenId, uint64 firstRound, uint128 weight, uint64 nextMergeBlock);
event PositionsMerged(MiningCollection indexed collection, uint256 indexed survivor, uint256 indexed donor, uint8 level, uint64 effectiveRound, uint64 nextMergeBlock, uint128 activeWeight, uint128 pendingWeight, uint256 accruedScaled);
event WeightActivated(uint64 indexed round, uint256 totalWeight, uint256 rewardIndex);
event Checkpointed(uint64 indexed afterRound, uint64 indexed throughRound, uint64 completedBlocks, uint64 cumulativeWork, uint96 cumulativeBlockUnits, uint256 amount, uint256 minted, uint64 lastActivationRound, uint256 totalWeight, uint256 rewardIndex);
event RewardsClaimed(address indexed owner, MiningCollection indexed collection, uint256 amount, uint256 remainderScaled, uint256[] tokenIds);
event Initialized(uint64 version);
```

| Event | Emitted when |
|---|---|
| `MiningInitialized`, `Initialized` | The proxy is constructed |
| `MiningBootstrapped`, `CollectionPolicyUpdated` | Bootstrap binds the token and collection; the policy event carries the full level table |
| `PositionRegistered` | A Squeek is minted; `firstRound` is when its power starts earning |
| `PositionsMerged` | A merge; carries the survivor's complete post-merge weights and credit |
| `Checkpointed` | Once per funded stretch `(afterRound, throughRound]`, with the `SqueekCore` endpoint, completed blocks and amount |
| `WeightActivated` | A queued power change is applied during a checkpoint |
| `RewardsClaimed` | A claim, including a zero payout; the SQK `Transfer` exists only when `amount > 0` |

Event weights use `uint128` and rounds and cooldowns `uint64`, wider than the stored fields. A checkpoint's events all precede `SqueekToken`'s single mint `Transfer` and `CheckpointMinted`.

## Errors

| Error | Cause | Fix |
|---|---|---|
| `Unauthorized` | Caller is not the NFT owner, the collection, governance or the bootstrap authority as required | Submit from the owning address |
| `InvalidClaim` | Empty, oversized, duplicate or unsorted ID list | Sort, deduplicate, batch by 32 |
| `InvalidPosition` | ID has no live position | Remove burned or unminted IDs |
| `InvalidMerge` | Same ID twice, levels differ, or maximum level | Choose a valid pair |
| `PendingActivation(tokenId, round)` | Power from a mint or merge is not applied yet | Wait for a checkpoint to process `round` |
| `CooldownActive(tokenId, nextMergeBlock)` | Completed Squeek block height is below `nextMergeBlock` | Wait for more completed blocks |
| `RoundNotSettled(round)` | No newly certified round to checkpoint | Wait for the next settlement |
| `CorePaused` | `SqueekCore` is paused | Retry checkpoints after unpause; claims still work |
| `CoreUnavailable`, `CoreInconsistent`, `CoreRegression`, `CoreFutureRound` | A `SqueekCore` read failed or disagreed with earlier reads | Stop and investigate; never substitute zero progress |
| `CoreIdentityMismatch`, `CoreNotFresh` | `SqueekCore` differs from launch, or is not fresh at setup | Deployment problem; not caused by callers |
| `MintMismatch`, `TokenSupplyMismatch`, `CustodyShortfall` | Token mint, supply or SQK balance disagrees with accounting | The call rolled back; investigate before retrying |
| `CollectionNotBound(collection)` | No collection is bound to that selector | Use `0` |
| `InvalidBounds` | Level or index out of range | Query a valid level or activation index |
| `BootstrapIncomplete`, `BootstrapAlreadyComplete` | Bootstrap has not run, or already ran | Setup only |
| `InvalidToken`, `InvalidCollection`, `InvalidGraphAdmin`, `BindingCallFailed` | A bound contract is unusable or miswired | Setup only |
| `InvalidConfiguration`, `InvalidHistory`, `InvalidProxyAdmin`, `InvalidProxyContext`, `InvalidProxyRuntime` | Invalid launch values or proxy context | Setup only |
| `InvalidInitialization`, `NotInitializing` | Initialization outside proxy construction | Setup only |
| `ReentrantCall` | Re-entry during a call | Do not call back into SqueekMining |
| `SafeCastOverflowedUintDowncast(bits, value)`, `SafeERC20FailedOperation(token)` | Inherited overflow or token-transfer failure | The call rolled back; investigate |

## Integrator patterns

- **Read ownership live.** `claimable` and `position` ignore ownership; read SqueekNFT `ownerOf` immediately before preparing a claim or merge.
- **Batch claims.** Sort IDs ascending, deduplicate, and split into batches of at most 32. Claiming several Miners together lets fractions of a base unit combine.
- **Preflight merges from `position`.** Reject before simulating when levels differ, `level` is the maximum, `pendingRound != 0`, or `nextMergeBlock` is above `SqueekCore` `state().blockHeight`. Elapsed time does not count toward cooldowns.
- **Check before checkpointing.** Call `checkpointStatus()` first; submit only when `corePaused` is false and `roundSettled` is true. Repeat while `roundSettled` stays true after a successful call.
- **Expect lag.** Settled work is not claimable until a checkpoint processes it; compare `processedThroughRound` with `SqueekCore` `settledThroughRound`.
- **New power waits a round.** A mint or merge earns at its new power from the round after the transaction, applied when a checkpoint reaches that round.
- **Count supply once.** Use `Checkpointed.amount` or the mint `Transfer`, not both with `CheckpointMinted`.
- **Treat zero as valid.** A claim of `0` and a checkpoint that mints `0` both succeed and advance state.

## Reward calculation

Each funded stretch ends at the earlier of `SqueekCore`'s certified frontier and the round before the next queued power change. SqueekMining reads that exact historical endpoint with `roundPage(throughRound, throughRound, 0)`.

```text
Q = 1_000_000_000
intervalBlocks = floor(endBlockUnits / Q) - floor(startBlockUnits / Q)
nominalReward  = intervalBlocks * 5_600 ether
remaining      = supplyCap - minted
reward         = totalWeight == 0 ? 0 : min(nominalReward, remaining)
```

The two floors are taken before subtracting, so a block completed by carried-over progress belongs to the stretch that completes it. A stretch with no active power mints nothing, and its blocks can never be claimed. At the cap, the last stretch is clipped and later ones mint nothing while the cursor keeps advancing.

## Scaled credit and custody

Reward indexes and position credit use `SCALE = 2^128` per SQK base unit, unrelated to `SqueekCore`'s block-unit scale of `10^9`. A position accrues `activeWeight × (index − lastIndex)`. A claim pays `floor(credit / SCALE)` base units and keeps `credit % SCALE` on the lowest selected ID.

While total power `T` is unchanged, the index is `I0 + floor(E × SCALE / T)` for the epoch's cumulative funding `E`, and `openResidual` is `(E × SCALE) % T`. A power change moves the open residual into `closedResidual` and starts a new epoch. Residuals stay in custody and are never redistributed or swept.

| Check | Meaning |
|---|---|
| `SqueekToken.totalSupply() == state().minted` | All SQK was issued by SqueekMining |
| `accounting().miningBalance >= accounting().trackedCustody` | SqueekMining holds at least what it owes |
| `(minted − paid) × SCALE` | Equals all positions' scaled credit plus both residuals |

Direct SQK transfers to SqueekMining raise its balance but fund no claims and restore no mint capacity.

## Usage example

Read a Miner's position, its claimable SQK and checkpoint progress. Uses `setup.ts` and `verify.ts` from the [Quickstart](https://docs.squeekchain.com/developers/quickstart.md).

```ts
// read-mining.ts
import { parseAbi } from "viem";
import { blockNumber, client, required } from "./setup";
import { verifyGraph } from "./verify";

const { mining } = await verifyGraph();
const SQUEEKS = 0;
const abi = parseAbi([
  "function state() view returns ((uint256 minted, uint256 paid, uint256 totalWeight, uint256 rewardIndex, uint64 closedThroughBlock, uint64 lastActivationRound, uint256 weightActivationCursor, uint256 weightActivationCount, bool bootstrapped, uint64 processedThroughRound, uint64 closedCumulativeWork, uint96 closedCumulativeBlockUnits, uint32 closedWorkPerThousandBlocks))",
  "function position(uint8 collection, uint256 tokenId) view returns ((uint64 activeWeight, uint64 pendingWeight, uint40 pendingRound, uint48 nextMergeBlock, uint8 level, uint256 lastIndex, uint256 accruedScaled))",
  "function claimable(uint8 collection, uint256[] tokenIds) view returns (uint256)",
  "function checkpointStatus() view returns (bool corePaused, uint64 nextRound, bool roundSettled)",
]);
const tokenId = BigInt(required("TOKEN_ID"));
const read = { address: mining, abi, blockNumber } as const;
const [state, position, claimable, [corePaused, nextRound, roundSettled]] = await Promise.all([
  client.readContract({ ...read, functionName: "state" }),
  client.readContract({ ...read, functionName: "position", args: [SQUEEKS, tokenId] }),
  client.readContract({ ...read, functionName: "claimable", args: [SQUEEKS, [tokenId]] }),
  client.readContract({ ...read, functionName: "checkpointStatus" }),
]);
if (position.level === 0) throw new Error("No live position for this token ID");
console.log({
  processedThroughRound: state.processedThroughRound,
  level: position.level,
  activeWeight: position.activeWeight,
  pending: position.pendingRound === 0 ? null : { round: position.pendingRound, weight: position.pendingWeight },
  nextMergeBlock: position.nextMergeBlock,
  claimableBaseUnits: claimable,
  checkpoint: { corePaused, nextRound, roundSettled },
});
```

## ABI

[`SqueekMining.json`](https://docs.squeekchain.com/assets/abi/SqueekMining.json). Call it on the proxy address, not the implementation.

## Related pages

- [Build owner actions](https://docs.squeekchain.com/developers/owner-actions.md)
- [Index events](https://docs.squeekchain.com/developers/index-events.md)
- [SqueekCore](https://docs.squeekchain.com/developers/core.md)
- [How SQK is issued](https://docs.squeekchain.com/rewards/creation.md)
- [Mining power and levels](https://docs.squeekchain.com/miners/levels.md)
