Backfill#
Start with the manager / clone / singleton's deployment block (read via useManagerDeploymentBlockNumberfor vesting; analogous helpers exist on the other products). Page the getLogs range, RPC providers cap at 2k blocks for free tiers. Use the ABI export for clean decoding.
import { parseEventLogs } from "viem";
import { confidentialVestingManagerAbi } from "@tokenops/sdk/fhe-vesting";
// One-time backfill: query the historical range.
const logs = await publicClient.getLogs({
address: managerAddress,
fromBlock,
toBlock: "latest",
});
// Decode every log against the ABI; non-matching are dropped.
const events = parseEventLogs({
abi: confidentialVestingManagerAbi,
logs,
});
for (const e of events) {
switch (e.eventName) {
case "VestingCreated":
// e.args = { vestingId, recipient, encryptedAmountHandle, ... }
await persistVesting(e.args);
break;
case "VestingClaimed":
await persistClaim(e.args);
break;
// ...
}
}Live watch#
For new blocks, watchEvent handles re-orgs and gaps. Pair it with the backfill range so the indexer never has a missed-block window.
// Live subscription: watch new blocks for the same set of events.
publicClient.watchEvent({
address: managerAddress,
events: confidentialVestingManagerAbi.filter((x) => x.type === "event"),
onLogs(logs) {
const events = parseEventLogs({
abi: confidentialVestingManagerAbi,
logs,
});
for (const e of events) handle(e);
},
});Schema sketch#
A minimal persistence schema:
vestings(id, recipient, manager, encryptedAllocationHandle, start, end, cliff, interval, status)
claims(vestingId, txHash, encryptedAmountHandle, claimedAt)
disclosures(vestingId, party, disclosureType, txHash)
roles(manager, role, account, grantedAt, revokedAt)
Encrypted handles stay as bytes32 strings in the DB; consumers query for handle ownership, then decrypt at render time via useDecryptedHandle.