> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/confidential-vault/guides/track-batches.md).

# Track Batch State

Read batch status, compute readiness times, index lifecycle events, and self-finalize when needed.

Between a join and a claim, a user's funds move through the batch lifecycle. This guide covers the view surface, the event log, and the permissionless writes that let your app drive a batch forward itself.

## The view surface

Every batcher exposes the same read functions:

{% code title="batcher-views.ts" %}

```ts
import { parseAbi } from "viem";

export const BATCHER_VIEW_ABI = parseAbi([
  "function currentBatchId() view returns (uint256)",
  "function batchState(uint256) view returns (uint8)",
  "function batchCreatedAt(uint256) view returns (uint256)",
  "function batchDispatchedAt(uint256) view returns (uint256)",
  "function batchMinBatchAge(uint256) view returns (uint256)",
  "function batchCallbackDeadline(uint256) view returns (uint256)",
  "function deposits(uint256 batchId, address account) view returns (bytes32)",
  "function exchangeRate(uint256 batchId) view returns (uint64)",
  "function totalDeposits(uint256) view returns (bytes32)",
  "function unwrapRequestId(uint256) view returns (bytes32)",
  "function paused() view returns (bool)",
  "function fromToken() view returns (address)",
  "function toToken() view returns (address)",
  "function vault() view returns (address)",
]);

export const BATCH_STATE = { Pending: 0, Dispatched: 1, Finalized: 2, Canceled: 3 } as const;
```

{% endcode %}

Facts to build on:

* Batch ids start at 1. Exactly one batch is `Pending` at a time — always `currentBatchId()`.
* `batchState` reverts with `BatchNonexistent` for ids that were never opened; guard for it.
* `exchangeRate(batchId)` is `0` until finalization — a non-zero rate *is* the finalized flag. The rate has 6 decimals: output units per input unit, times 10⁶.
* `deposits(batchId, account)` returns the zero handle if the account never joined that batch.

## Compute readiness and deadlines from pinned values

Policy is pinned into each batch at creation. A batch's timing must be computed from the **per-batch getters**, never from the live `minBatchAge()` — a policy change never moves a batch that is already open:

```ts
const [state, createdAt, minAge] = await publicClient.multicall({
  allowFailure: false,
  contracts: [
    { address: batcher, abi: BATCHER_VIEW_ABI, functionName: "batchState", args: [batchId] },
    { address: batcher, abi: BATCHER_VIEW_ABI, functionName: "batchCreatedAt", args: [batchId] },
    { address: batcher, abi: BATCHER_VIEW_ABI, functionName: "batchMinBatchAge", args: [batchId] },
  ],
});

const dispatchableAt = createdAt + minAge; // unix seconds
```

The same rule applies after dispatch: the batch auto-cancels at `batchDispatchedAt(batchId) + batchCallbackDeadline(batchId)` if settlement has not succeeded by then.

A complete status line for a user's position:

| Condition                             | Show                                                          |
| ------------------------------------- | ------------------------------------------------------------- |
| `Pending`, before `dispatchableAt`    | "In batch — settles after ⟨time⟩" with a cancel (quit) action |
| `Pending`, after `dispatchableAt`     | "Awaiting dispatch" — or dispatch it yourself, see below      |
| `Dispatched`                          | "Settling" with the deadline as the worst case                |
| `Finalized`, position not yet claimed | "Ready to claim" with a claim action                          |
| `Canceled`                            | "Batch canceled — funds recoverable" with a quit action       |
| `paused() == true`                    | Disable the deposit action; quit and claim keep working       |

## Index the lifecycle events

Six events describe everything. Amounts on user-scoped events are encrypted handles (`bytes32`), decryptable only by the account:

```ts
export const BATCHER_EVENTS_ABI = parseAbi([
  "event Joined(uint256 indexed batchId, address indexed account, bytes32 amount)",
  "event Quit(uint256 indexed batchId, address indexed account, bytes32 amount)",
  "event BatchDispatched(uint256 indexed batchId)",
  "event BatchFinalized(uint256 indexed batchId, uint64 exchangeRate)",
  "event BatchCanceled(uint256 indexed batchId)",
  "event Claimed(uint256 indexed batchId, address indexed account, bytes32 amount)",
]);
```

`Claimed(batchId, account)` is the unambiguous "funds have arrived" signal — it is the last lifecycle step, whether the user claimed or a third party claimed for them:

```ts
const logs = await publicClient.getLogs({
  address: batcher,
  event: BATCHER_EVENTS_ABI[5], // Claimed
  args: { batchId, account: user },
  fromBlock: joinBlock,
});
```

Keep `eth_getLogs` windows bounded (a few thousand blocks) and use the manifest's `deployBlock` as the earliest bound for full-history indexing.

## Drive the batch yourself

Every lifecycle write is permissionless. An integration that wants zero dependence on the protocol's operator can advance batches on its own:

**Dispatch.** When the pending batch passes its minimum age and the batcher is not paused:

```ts
const DISPATCH_ABI = parseAbi(["function dispatchBatch()"]);

await walletClient.writeContract({
  address: batcher,
  abi: DISPATCH_ABI,
  functionName: "dispatchBatch",
});
```

**Finalize.** A dispatched batch needs its aggregate cleartext and KMS proof. Both come from a public decryption of the batch's unwrap handle — no signature needed, the dispatch marked it publicly decryptable:

```ts
const requestId = await read(batcher, "unwrapRequestId", [batchId]);
const fromToken = await read(batcher, "fromToken");
const handle = await read(fromToken, "unwrapAmount", [requestId]);

const { clearValues, decryptionProof } = await sdk.decryption.decryptPublicValues([handle]);

await walletClient.writeContract({
  address: batcher,
  abi: parseAbi(["function dispatchBatchCallback(uint256 batchId, uint64 cleartext, bytes proof)"]),
  functionName: "dispatchBatchCallback",
  args: [batchId, clearValues[handle], decryptionProof],
});
```

If the vault call reverts, the batch stays `Dispatched` — retry the callback with identical arguments until it finalizes or the deadline cancels it. Past the deadline the same call cancels rather than settles, so "finalize" stays a single user action either way. Callback gas is flat regardless of participant count.

**Claim for your users.** `claim(batchId, account)` can be batched, but each claim costs about 2.7M units of the FHEVM's 20M per-transaction compute budget (HCU) — **at most 7 claims per transaction**. An eighth reverts the whole transaction.

## Re-derive state on each poll

Batch state transitions can happen between any two of your reads — an operator dispatch, another integrator's claim, a user quitting from a different device. Re-derive everything from the chain on each poll (`batchState`, `deposits`, `exchangeRate`) and treat your local cache as display-only. Multicall keeps this cheap.

## Next steps

* [Batch Lifecycle](/protocol/confidential-vault/concepts/batch-lifecycle.md) — what each state means and why.
* [Deposit into a Vault](/protocol/confidential-vault/guides/deposit.md) — producing the `Joined` events you index here.
* [Contract Addresses](/protocol/confidential-vault/reference/addresses.md) — deploy blocks for indexer start points.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zama.org/protocol/confidential-vault/guides/track-batches.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
