Time Travel
A StoreKit store’s full history is already on chain. A store’s boxes
only change through its own ABI calls — set, set_bytes, concat and
remove, which only the creator can make — and through its deletion. Every
confirmed call succeeded, so replaying the app’s calls in chain order rebuilds
the exact boxes at every round. Any indexer can serve that history; no
extra contract state, events or services are needed.
Try it
Pick a network and a store’s app id (a store you deploy on the Quick Start is filled in), then step through its history.
Replay a store’s history
import { fetchStoreHistory, replay, stateFromBoxes, diffBoxes,} from "@awesome-algorand/store-kit/history";
// Every create / set / concat / remove / meta update / delete, in chain orderconst history = await fetchStoreHistory(algorand.client.indexer, appId);
// The store at the end of a round...const then = stateFromBoxes(replay(history, { round: 1234n }));
// ...or after its first n operations, and what the n-th one changedconst before = replay(history, { count: n - 1 });const after = replay(history, { count: n });const changes = diffBoxes(before, after); // [{ path, kind: "added" | "changed" | "removed", ... }]fetchStoreHistory(indexer, appId, { minRound, maxRound, onPage, signal })pages through the indexer and also decodes inner calls, so stores written by other contracts replay too.replay(history, { round | count })returns the raw boxes by path;stateFromBoxesassembles them into the object,decodeBoxesgives decoded values by path. Pass{ schema }for stores packed with a JSON Schema.replayMeta(history, until)gives the store’s metadata at that point: whether it exists, its creation/deletion round,contract_name,contract_typeandschema_app_id.changeRounds(history)lists the rounds at which the store changed.
Read from a time travel node
A go-algorand node with EnableTimeTravel answers box reads for any round
and lists each box’s versions. A stock node only serves recent rounds.
import { fetchBoxesAtRound, fetchBoxVersions, isTimeTravelNode, stateFromBoxes,} from "@awesome-algorand/store-kit/history";
const node = { baseUrl: "https://explorer.shore-tech.net/tt/fnet-v1" };
if (await isTimeTravelNode(node)) { // GET /v2/applications/{id}/boxes?round=R&include=values const state = stateFromBoxes(await fetchBoxesAtRound(node, appId, 1234n));
// GET /v2/applications/{id}/box/versions?name=… const versions = await fetchBoxVersions(node, appId, "count"); // [{ round, timestamp, deleted, value }, ...]}Both sources give the same boxes, so an app can replay from the indexer and check any round against a time travel node.
Find stores
A store declares itself with a protocol global state key set to
storekit/1 when it is created. Stores deployed before that are recognized by
the StoreKit ABI selectors (set, get, remove) in their approval program.
import { inspectStoreApp } from "@awesome-algorand/store-kit/history";
const { application } = await indexer.lookupApplications(appId).do();const info = inspectStoreApp(application.params);// { isStore: true, via: "protocol" | "abi", protocol: "storekit/1",// methods: ["set", "get", "remove", ...], contractName, contractType, schemaAppId }