Bootstrap, manifests, versions, and reorgs¶
Bootstrap configuration must supply addresses, chain ID, deployment blocks, proxy implementation history, retired-route boundaries, and finality policy.
Required deployment manifest¶
The canonical manifest must be signed or reviewed operational data, not inferred from tests or local deployment scripts. Its schema must enumerate every contract type, and its ABI/schema registry must be the decoder-version allowlist.
A production manifest must fill its network/source/finality identity; deployment addresses, transactions, code hashes, ownership, authority, and complete proxy implementation history; Spot and Perp market configuration; Engine quote configuration; component relationships; route history; and verification evidence. For every proxy include the implementation used at deployment and every later implementation interval, each with an approved ABI version/schema pair. Every direct deployment carries its own approved pair. For every market store route intervals rather than overwriting the old book.
Every implementation, relationship, and route interval has this boundary shape:
{
"fromBlock": 123,
"toBlock": null,
"activationPosition": {"blockNumber": 123, "transactionIndex": 4, "logIndex": 7},
"retirementPosition": null,
"activationTransaction": "0x...",
"retirementTransaction": null
}
Compare non-null positions lexicographically as (blockNumber,transactionIndex,logIndex);
fromBlock/toBlock are query-friendly mirrors only. Exact positions are required when a
deployment, relationship, upgrade, or route change shares a block with other activity.
Closed intervals must also carry the transaction hash that established their terminal position.
For implementation history the activation hash is named upgradeTransaction; its value must
equal the preceding interval's retirementTransaction at a contiguous upgrade boundary. Each
implementation interval additionally carries abiVersion and abiSchema; direct deployments use
directAbiVersion and directAbiSchema instead. The first proxy interval must use the deployment
block and transaction index, and its upgradeTransaction must equal deploymentTransaction.
Bootstrap algorithm¶
- Validate manifest chain ID and genesis hash against the RPC.
- Verify deployed bytecode exists at every component address at/after its declared deployment block.
- For each proxy, verify the ERC-1967 implementation slot at a finalized checkpoint and reconcile
all
Upgradedlogs from deployment to that block. - Verify ownership state (
owned,renounced, or no ownership surface), every ownership-handover candidate discovered by the scan, authority pointers, authority roles, Router dependencies, AccountCore Router/ updater/keeper settings, Engine quote asset/scale, and orderbook immutable/config fields with block-pinned getters. - Scan from the earliest declared component deployment block. Do not start from the first market discovery event: initializer and registry logs can precede it.
- Buffer unknown emitters within a transaction, then classify them when discovery/manifest data becomes available.
- Apply the event reducers from genesis through a finalized checkpoint.
- Run the checkpoint reconciliation in replay-recipes.md.
- Publish the index only after every mismatch is resolved or explicitly marked as an unavailable deployment fact.
Mid-history bootstrap¶
A mid-history start cannot produce full L3 or exact cross-position/OI history from current getters alone unless all active entities are enumerated externally. There is no global on-chain iterator over every maker page/account slot.
If historical RPC access is limited, obtain a trusted checkpoint artifact containing:
- its chain ID, block number/hash, transaction boundary, source/ABI schema, and generator version;
- every known contract and implementation interval;
- each contract's structured owner row and every known pending-handover candidate with its stored expiry (the getter is candidate-keyed and has no global iterator);
- all accounts, signer/referral/builder policy, and nonzero balance rows;
- every active Spot/Perp slot and current next trade/order identities needed for continuity;
- all passive bands/positions and fee-growth checkpoints;
- every Perp market, margin namespace, mode, cross slot, position, exposure, reserve, OI, and lock;
- a cryptographic hash of the canonical serialized snapshot.
Read missing Engine state with getMarket, getCrossPosition, getIsolatedPosition, margin,
reserve, mode, threshold, and liquidation getters at the checkpoint block. Read Spot band/position
state with getPassiveBand/getPassivePosition. A maker/account universe still has to come from a
prior event scan or an independently audited snapshot.
ABI and semantic version selection¶
Use an implementation interval table whose last two fields are an exact key in the ABI/schema registry:
(chainId, proxy, activationPosition, retirementPosition, implementation, codeHash, abiVersion, abiSchema)
For UUPS proxies:
- initial proxy construction emits
Upgraded; the named implementation decodes later initializer logs in that transaction; - on a later
Upgraded, the implementation slot has already changed at that log; decode the common upgrade event independently, and use the new schema for subsequent logs; - migration logs emitted by
upgradeToAndCallfollowUpgradedand therefore use the new ABI; - Router
SpotOrderBookUpgraded,PerpOrderBookUpgraded, andPerpEngineUpgradedare later audit confirmations, not decoder boundaries.
An upgrade may retain the same topic with changed packed payload semantics. Therefore (emitter,
topic0) is not enough. Resolve the active pair through the registry and validate its decoder,
packed input operations, output record width, byte/word order, bit ranges, reserved policy, and
known flag masks. Unknown pairs are fatal bootstrap errors; do not fall back to the latest decoder.
Non-proxy ProtocolAuthority, SpotPeriphery, KeeperAuthority, and ExamplePostFillHook
instances are versioned by address/code hash and directAbiVersion/directAbiSchema. A new
deployment is a new version interval; do not assume it is semantically interchangeable solely
because events match.
For an EIP-7702 trading wallet, the log emitter and state address are the delegated EOA, while executable logic comes from its active delegation implementation. Version the pair (wallet, delegation implementation) at each activation boundary. Never read lastSeenOrderNonce, usedTriggerNonce, or getTrigger from the shared implementation.
Route versioning¶
Spot¶
Current Spot Router has no route replacement/revocation function. Every registered Spot orderbook remains independently verified in AccountCore. A future migration must be represented by an explicit manifest interval and any new on-chain event; never infer retirement because another book uses the same assets.
Perp¶
The AccountCore PerpOrderBookUpdated log is the authorization boundary. Maintain:
(marketId, orderBook, activeFromLogPosition, activeUntilLogPosition)
Initial deployment emits AccountCore PerpOrderBookUpdated(0,new) but no Router route event.
Replacement/revocation emits AccountCore PerpOrderBookUpdated(old,new) followed by Router
PerpOrderBookRouteUpdated(old,new). The old book is expected to be drained operationally; logs do
not prove that invariant, so the deployment/operations record must attest it.
Record AccountCore market enablement as settlementEnabled, independently of Engine status and
orderbook state. An enabled market must have exactly one open route interval. A disabled market may
retain one open route or have none after revocation; historical closed intervals are never deleted.
Do not merge order/trade identity counters across old and new books. They remain distinct market data emitters even though they share a global market ID.
Eventless control reconciliation¶
At deployment and every finalized checkpoint, read:
- AccountCore
protocolPaused,feeCollector, Router addresses, price/funding updaters, keeper membership, base/default fees, token configs, Engine/market routes; - every ProtocolAccess child's
authoritypointer and every Ownable contract's structured ownership state. Recordrenouncedonly whenowner()returns the zero address; useunknownonly in non-production work, andnot-applicableonly for a contract type with no ownership surface; ownershipHandoverExpiresAt(candidate)for every candidate address learned from request/cancel history or the trusted checkpoint. Requests are concurrent under(chainId,contract,pendingOwner), and this getter is not enumerable: a mid-history RPC snapshot cannot discover an omitted candidate. A nonzero expiry remains stored after time passes; derive liveness ascheckpointTimestamp <= expiresAtrather than expecting automatic cleanup;- ProtocolAuthority
governance,ops, andemergency; - Router implementation defaults and dependency addresses;
- Engine
accountCoreAddress,quoteAsset,quoteScale, andmaxCrossRestingOrders; - orderbook owner, authority, state, parameters, hook gas/minimum, and product dependencies;
- EIP-7702 wallet delegation implementation, AccountCore, immutable keeper registry, max nonce skew, and wallet-local nonce/trigger getters; and keeper-registry owner and membership;
- example hook immutable orderbook/maker binding and live settings.
For AccountCore toggleProtocolState(bool) and setFeeCollector(address), retain successful
transaction input and update at that transaction boundary, then verify the getter. A block-end read
cannot distinguish multiple changes in the same block; if calldata/trace coverage is absent, mark
the intra-block history unknown.
Constructor-only fields require deployment transaction input or a canonical manifest. An event scan alone cannot recover them reliably.
For ownership history, OwnershipTransferred alone is insufficient to decide whether a handover
slot was consumed. Decode successful transaction calldata or a trace: only
completeOwnershipHandover(pendingOwner) clears that named candidate, while direct transfer,
renounce, and initialization clear none. A block-pinned getter can reconcile each known candidate,
but an ordinary eth_call observes block-end state and cannot recover an intermediate transition
when later transactions in the same block touch the candidate.
Reorg and finality policy¶
- Persist raw blocks/logs keyed by block hash, not only block number.
- Keep a reversible mutation journal for at least the configured reorg horizon.
- On reorg, roll back packed records in reverse record order, then logs, transactions, and blocks.
- Do not reuse orphaned
(orderBook,tradeId)records on the new branch; block hash remains part of raw identity until finalized. - Re-run block-pinned getter fallbacks for replacement-branch events. Never carry a getter result from an orphaned block.
- Deployment/upgrade/route manifest changes are reviewed configuration changes and must themselves be versioned. Do not edit prior intervals in place.