SqueekMiningLink to this section
TL;DRLink to this section
- 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
0x3beE588fC20dE788Bac17d33b41Da7346cb27EC4on Sepolia.
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.
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 |
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.
FunctionsLink to this section
TypesLink to this section
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 actionsLink to this section
These are signed by the NFT's current owner. Approved operators cannot call them. Build owner actions covers preparing and simulating them.
claimLink to this section
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.
mergeLink to this section
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).
PermissionlessLink to this section
checkpointLink to this section
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.
ViewsLink to this section
positionLink to this section
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.
claimableLink to this section
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.
checkpointStatusLink to this section
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.
stateLink to this section
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.
configurationLink to this section
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.
collectionPolicyLink to this section
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.
weightForLevelLink to this section
function weightForLevel(MiningCollection collection, uint8 level) external view returns (uint64);
Mining power at level. Reverts with InvalidBounds for level 0 or above the maximum.
cooldownBlocksLink to this section
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.
weightActivationLink to this section
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.
accountingLink to this section
function accounting() external view returns (MiningAccounting result);
Rounding leftovers and SQK custody. See Scaled credit and custody.
ConstantsLink to this section
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
IdentityLink to this section
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 checks them.
Admin and setupLink to this section
onMintLink to this section
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.
initializeLink to this section
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).
completeBootstrapLink to this section
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.
EventsLink to this section
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.
ErrorsLink to this section
| 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 patternsLink to this section
- Read ownership live.
claimableandpositionignore ownership; read SqueekNFTownerOfimmediately 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,levelis the maximum,pendingRound != 0, ornextMergeBlockis aboveSqueekCorestate().blockHeight. Elapsed time does not count toward cooldowns. - Check before checkpointing. Call
checkpointStatus()first; submit only whencorePausedis false androundSettledis true. Repeat whileroundSettledstays true after a successful call. - Expect lag. Settled work is not claimable until a checkpoint processes it; compare
processedThroughRoundwithSqueekCoresettledThroughRound. - 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.amountor the mintTransfer, not both withCheckpointMinted. - Treat zero as valid. A claim of
0and a checkpoint that mints0both succeed and advance state.
Reward calculationLink to this section
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).
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 custodyLink to this section
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 exampleLink to this section
Read a Miner's position, its claimable SQK and checkpoint progress. Uses setup.ts and verify.ts from the Quickstart.
// 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 },
});
ABILink to this section
SqueekMining.json. Call it on the proxy address, not the implementation.