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

# Index events

> Index SqueekCore settlements and SqueekMining rewards from logs, reconstruct block progress, and stay correct through reorgs.

Web version: https://docs.squeekchain.com/developers/index-events/

## TL;DR

- SqueekCore logs record settled work and block progress; SqueekMining and SqueekToken logs record funded rewards, activations and claims.
- Decode with the ABI of the implementation active at that address and block.
- `BatchSettled` advances the certified frontier even when no round recorded work.
- Store block hashes and log order so a reorg can roll back derived state, not just raw logs.

## Before you start

Complete [Verify the deployment](https://docs.squeekchain.com/developers/quickstart.md#verify-the-deployment) and start at `INDEX_FROM_BLOCK` from [Deployments](https://docs.squeekchain.com/developers.md#deployments). Decode with the published ABIs linked there. The graph launches fresh: there is no earlier ledger to continue from.

Applications log at the proxy addresses. A matching topic does not prove the emitter, so filter by verified address and keep each proxy's implementation history, because an upgrade can change the ABI.

## Steps

### Fetch and order logs

1. Fetch logs for the four verified proxies in bounded Ethereum block ranges.
2. Store chain ID, block number and hash, transaction hash and index, and log index. Use `(chainId, blockHash, transactionHash, logIndex)` as the occurrence identity.
3. Process in block, transaction index and log index order.
4. After an upgrade event, verify the new implementation before choosing its ABI. Stop the projection if the code or layout is unknown.

### Know which events each action emits

| Action | Events, in order |
|---|---|
| Settlement | Zero or more SqueekCore `RoundRecorded`, then one `BatchSettled` |
| Mint | For each Squeek, SqueekNFT `Transfer` from zero then SqueekMining `PositionRegistered`; then one SqueekNFT batch event (`PublicMinted`, `AllowlistMinted`, `ProtocolReserveMinted` or `PublicRemainderMinted`) |
| Checkpoint | SqueekMining `WeightActivated` and `Checkpointed` per stretch, then one SqueekToken mint `Transfer` and `CheckpointMinted` |
| Claim | SqueekMining `RewardsClaimed`; a SqueekToken `Transfer` only when the payout is nonzero |
| Merge | SqueekNFT burn `Transfer` for the donor, then SqueekMining `PositionsMerged` |
| NFT transfer | SqueekNFT `Transfer` only; SqueekMining gets no callback |

Index ownership from SqueekNFT `Transfer` events, not SqueekMining events. A reverted transaction emits nothing; a reorg can remove events from a receipt you already saw.

### Index SqueekCore

1. Start physical work and block units at zero and difficulty at `CoreInitialized.initialWorkPerThousandBlocks`.
2. For each `RoundRecorded`, price its work with the **previous** position's difficulty, then check the emitted totals and the emitted next difficulty.
3. Advance the certified frontier on every `BatchSettled`, including batches with zero active rounds. Reconcile its final totals and `activeRoundCount` with the transaction's `RoundRecorded` events, which all precede it.
4. Derive completed blocks from the change in `cumulativeBlockUnits / 1_000_000_000`. There is no per-block event, and one settlement can complete several blocks.

```ts
// decode-round.ts — `log` is a raw log already filtered to the verified SqueekCore proxy
import { decodeEventLog, parseAbi, type Log } from "viem";

const abi = parseAbi([
  "event RoundRecorded(uint64 indexed roundId, uint64 work, uint64 cumulativeWork, uint96 cumulativeBlockUnits, uint32 workPerThousandBlocks, bytes32 dataHash)",
]);
export function decodeRound(log: Log) {
  const { args } = decodeEventLog({ abi, data: log.data, topics: log.topics, strict: true });
  return {
    round: args.roundId,
    physicalWork: args.cumulativeWork,
    completedBlocks: args.cumulativeBlockUnits / 1_000_000_000n,
    blockProgress: args.cumulativeBlockUnits % 1_000_000_000n,
    nextDifficulty: BigInt(args.workPerThousandBlocks),
  };
}
```

The emitted difficulty prices the next active round, not this one. Follow the [price-then-steer rule](https://docs.squeekchain.com/developers/core.md#price-first-then-steer) with exact integer arithmetic.

### Index SqueekMining and SqueekToken

1. Record `PositionRegistered` for mints and `PositionsMerged` for merges; together with SqueekNFT `Transfer` they give the live position set.
2. For each checkpoint, record every `Checkpointed` stretch and `WeightActivated`, then the single SqueekToken mint `Transfer` and `CheckpointMinted`.
3. Count new supply once, from the mint `Transfer` or from `Checkpointed.amount`; `CheckpointMinted` describes the same SQK.
4. Record `RewardsClaimed` for payouts. Its SQK `Transfer` exists only when `amount > 0`.
5. Reconcile `Checkpointed.minted` with SqueekToken `totalSupply()` at the same block.

### Replay and reconcile

Replay both contracts from the deployment block to one confirmed block, then compare the result with each contract's own state at that block. A mismatch means a missed log, a wrong decoder, or a reorg; never adjust the projection to match.

1. Fetch `RoundRecorded` and `BatchSettled` from SqueekCore and `Checkpointed` from SqueekMining in bounded ranges, oldest first.
2. Require each `BatchSettled.previousSettledThroughRound` to equal the frontier you hold, so no settlement is skipped.
3. Sum `activeRoundCount` across batches; it must equal the number of `RoundRecorded` events.
4. Sum `Checkpointed.amount`; it must equal SqueekMining `state().minted` and SqueekToken `totalSupply()`.

```ts
// replay.ts
import { parseAbi, parseAbiItem } from "viem";
import { blockNumber, client, required } from "./setup";
import { verifyGraph } from "./verify";

const { core, mining, token } = await verifyGraph();
const start = BigInt(required("INDEX_FROM_BLOCK"));
const roundRecorded = parseAbiItem("event RoundRecorded(uint64 indexed roundId, uint64 work, uint64 cumulativeWork, uint96 cumulativeBlockUnits, uint32 workPerThousandBlocks, bytes32 dataHash)");
const batchSettled = parseAbiItem("event BatchSettled(bytes32 indexed digest, address indexed reporter, uint64 indexed settledThroughRound, uint256 nonce, uint64 previousSettledThroughRound, uint16 activeRoundCount, uint64 cumulativeWork, uint96 cumulativeBlockUnits)");
const checkpointed = parseAbiItem("event Checkpointed(uint64 indexed afterRound, uint64 indexed throughRound, uint64 completedBlocks, uint64 cumulativeWork, uint96 cumulativeBlockUnits, uint256 amount, uint256 minted, uint64 lastActivationRound, uint256 totalWeight, uint256 rewardIndex)");

let frontier = 0n, blockUnits = 0n, activeRounds = 0n, recorded = 0n, minted = 0n;
for (let from = start; from <= blockNumber; from += 1_000n) {
  const to = from + 999n < blockNumber ? from + 999n : blockNumber;
  const range = { fromBlock: from, toBlock: to, strict: true } as const;
  const [rounds, batches, checkpoints] = await Promise.all([
    client.getLogs({ address: core, event: roundRecorded, ...range }),
    client.getLogs({ address: core, event: batchSettled, ...range }),
    client.getLogs({ address: mining, event: checkpointed, ...range }),
  ]);
  recorded += BigInt(rounds.length);
  for (const { args } of batches) {
    if (args.previousSettledThroughRound !== frontier) throw new Error(`Missing settlement before round ${args.settledThroughRound}`);
    frontier = args.settledThroughRound;
    blockUnits = args.cumulativeBlockUnits;
    activeRounds += BigInt(args.activeRoundCount);
  }
  for (const { args } of checkpoints) minted += args.amount;
}

const views = parseAbi([
  "function state() view returns ((uint64 settledThroughRound, uint64 cumulativeWork, uint96 cumulativeBlockUnits, uint64 blockHeight, uint96 blockProgress, uint32 workPerThousandBlocks, uint256 authorizationNonce, bool paused))",
  "function totalSupply() view returns (uint256)",
]);
const miningState = 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))",
]);
const [coreState, mined, supply] = await Promise.all([
  client.readContract({ address: core, abi: views, functionName: "state", blockNumber }),
  client.readContract({ address: mining, abi: miningState, functionName: "state", blockNumber }),
  client.readContract({ address: token, abi: views, functionName: "totalSupply", blockNumber }),
]);
const checks = {
  frontier: frontier === coreState.settledThroughRound,
  blockUnits: blockUnits === coreState.cumulativeBlockUnits,
  activeRounds: activeRounds === recorded,
  minted: minted === mined.minted && minted === supply,
};
if (Object.values(checks).includes(false)) throw new Error(`Projection disagrees with contract state: ${JSON.stringify(checks)}`);
console.log({ blockNumber, frontier, completedBlocks: blockUnits / 1_000_000_000n, recorded, mintedBaseUnits: minted });
```

A production indexer stores each log with its block hash and applies the same checks continuously instead of replaying from the start.

### Handle reorgs

Keep unconfirmed head data separate from confirmed projections. When a stored block hash no longer matches the chain, roll derived state back to the common ancestor and replay from there. Deduplicating raw log IDs alone leaves derived totals wrong.

## Expected result

| Event | Projection effect |
|---|---|
| `CoreInitialized` | Stream, actors, genesis, work cap and initial difficulty; totals start at zero |
| `RoundRecorded` | One active round's work and hash, with the resulting physical total, earned total and next difficulty |
| `BatchSettled` | New certified frontier, consumed nonce, active count, and final totals |
| `PositionRegistered`, `PositionsMerged` | New and merged positions with their pending power and cooldowns |
| `Checkpointed`, `WeightActivated` | Funded stretches with completed blocks and amounts; power changes applied |
| SqueekToken mint `Transfer`, `CheckpointMinted` | One aggregate mint per checkpoint |
| `RewardsClaimed` | Payout to the owner and the remainder left on the lowest ID |
| Pause, Reporter and upgrade events | Availability, signer and implementation history |

Exact field lists are on each contract page: [SqueekCore](https://docs.squeekchain.com/developers/core.md#events), [SqueekMining](https://docs.squeekchain.com/developers/mining.md#events), [SqueekToken](https://docs.squeekchain.com/developers/token.md#events) and [SqueekNFT](https://docs.squeekchain.com/developers/nft.md#events).

## Common failures

| Failure | Cause | Fix |
|---|---|---|
| Wrong topics or tuple widths | Decoding with an earlier ABI | Select the ABI per implementation and block |
| Earned totals drift | Pricing a round with its own emitted difficulty | Use the previous position's difficulty |
| Block count is off | Dividing physical work by current difficulty | Use block units |
| Frontier lags | Ignoring zero-record `BatchSettled` | Advance on every `BatchSettled` |
| Supply doubled | Counting `CheckpointMinted` and its `Transfer` separately | Count one of them |
| Gaps read as zero work | No `RoundRecorded` outside a certified interval | Treat uncertified rounds as unknown |
| Totals wrong after a reorg | Deduplicating logs without rolling back | Roll back to the common ancestor and replay |

A `dataHash` commits to the round's supporting records; it is not those records or proof of physical causation.

## Verify the result

At one canonical block, compare your replayed frontier, physical total, earned total and difficulty with `SqueekCore.state()`, and selected endpoints with `roundPage(r, r, 0)`. Compare your minted and paid totals with SqueekMining `state()` and SqueekToken `totalSupply()`.

`BatchSettled.nonce` is the nonce that settlement consumed, not the next one. Read `state()` at the same block before preparing a new authorization.

## Related pages

- [Developers](https://docs.squeekchain.com/developers.md)
- [Quickstart](https://docs.squeekchain.com/developers/quickstart.md)
- [SqueekCore](https://docs.squeekchain.com/developers/core.md)
- [SqueekMining](https://docs.squeekchain.com/developers/mining.md)
- [Build owner actions](https://docs.squeekchain.com/developers/owner-actions.md)
- [Rounds and Squeek blocks](https://docs.squeekchain.com/system/rounds-and-blocks.md)
