Background
AgentKeys (litentry/agentKeys) v2 stage 1 has deployed its four anchor contracts to Heima mainnet (chain_id 212013). These are written in Solidity 0.8.20, target EVM london (matching Heima's Frontier EVM level), and are the on-chain source of truth for the AgentKeys protocol's identity + scope + audit state.
The existing subscan-essentials explorer is Substrate-side only — it indexes pallet extrinsics + events but does NOT decode EVM contract calls, EVM events, or EVM contract storage. Today, the only way to inspect these contracts is via direct RPC (eth_getCode, cast call, cast logs). For operators + auditors, that's friction; for the project's "everything auditable on-chain" guarantee (see arch.md §6 + §15.3 in litentry/agentKeys), we need a first-class explorer view of the AgentKeys contracts.
This issue tracks adding EVM contract data + search support to subscan-essentials, scoped to a basic-but-extensible MVP. Future stages add ABI decoding for arbitrary contracts (not just AgentKeys'); this stage hard-codes our four.
Live mainnet addresses to support
All on Heima mainnet (chain_id 212013, RPC https://rpc.heima-parachain.heima.network):
| Contract |
Address |
Bytecode size |
AgentKeysScope |
0x14C23B5D1cE20c094af643a20e6b0972dAD12aa8 |
3146 bytes |
SidecarRegistry |
0x76D574a107727bE87fc1422661A030FEFda70786 |
3301 bytes |
K3EpochCounter |
0x8396dEc50ff755d6DE7728DABB00Be2eFBCdf4dF |
687 bytes |
CredentialAudit |
0x1801ded1a4FBD8c9224Ab18B9EcbB293B8674c06 |
1421 bytes |
ABIs + source live in crates/agentkeys-chain/ in the agentKeys repo. Canonical address registry: docs/spec/deployed-contracts.md.
MVP scope (what this issue ships)
1. EVM contract index ingestion
The backend indexer learns to ingest contract data from the Frontier RPC namespace:
- Bytecode — per address, via
eth_getCode(addr, 'latest'). Indexed at deploy block (one-shot; updates only if selfdestruct triggers, which AgentKeys contracts don't use).
- Transaction list — every
to == contract_addr transaction. Indexed continuously via eth_getBlockByNumber walking. Each entry stores: block, txHash, from, value, calldata, status, gasUsed.
- Event log — every
Log whose address == contract_addr. Index by topic[0] (event signature hash) for cheap filtering. Each entry stores: block, txHash, logIndex, topics[], data.
- Storage layout snapshot (optional, v1.1) —
eth_getStorageAt(addr, slot) for declared slots from the ABI's storage layout output.
2. Per-contract view (UI)
For each of the four AgentKeys contracts, the subscan-essentials-ui-react frontend adds a route:
/contract/<addr> → overview: name, bytecode hash, deploy date, balance, tx count
/contract/<addr>/txs → paginated tx list (decoded function name + args via ABI)
/contract/<addr>/events → paginated event list (decoded event name + indexed topics + data)
/contract/<addr>/call → read-only view function caller (cast call equivalent)
ABI decoding is hard-coded for the four AgentKeys contracts in MVP (ship the ABIs in the indexer's config). v1.1+ adds operator-supplied ABI uploads.
3. Search
Search bar accepts:
- Contract address (0x...40 hex) — direct route to
/contract/<addr>
- Tx hash (0x...64 hex) — existing substrate tx route + new EVM tx route (auto-detect by hash length)
- Block number — existing route
- EVM event filter — new: `Sid:keccak256(EventSig)` syntax routes to events page filtered by topic[0]. E.g., `SidecarRegistry.DeviceRegistered` → `/contract/0x76D5.../events?topic0=0x`. Hard-coded keyword → topic mapping for AgentKeys events in MVP.
- Per-operator filter — `actor_omni:<64hex>` filters events across ALL four contracts where any indexed topic matches that actor_omni. Backend joins across the contract event indexes.
4. Per-actor cross-contract view (THIS is what makes the explorer useful for AgentKeys)
A new top-level route /agentkeys/actor/<operator_omni_hex> shows ONE operator's activity across ALL four contracts:
- Devices bound to them (from
SidecarRegistry.DeviceRegistered filtered on indexed operatorOmni)
- Their scope grants (from
AgentKeysScope.ScopeUpdated)
- Their audit log entries (from
CredentialAudit.AuditAppended)
- Current K3 epoch (from
K3EpochCounter.K3Rotated, latest)
This is the auditor's view: "show me everything this operator has done on-chain."
Acceptance criteria / test standard
The MVP is complete when ALL of these pass:
A. Backend ingestion (Go service)
A.1. make build + make test pass with the new indexer modules added.
A.2. For each of the four AgentKeys contracts, running the indexer against Heima mainnet produces:
- A non-empty
contract row in the DB
- A
bytecode_size matching the on-chain bytecode size (3146, 3301, 687, 1421 bytes respectively)
- All historical transactions to that address recorded with status + gas + decoded function names
- All historical events recorded with topic0 → event name decoded
A.3. Re-running the indexer is idempotent (no duplicate rows).
A.4. The indexer keeps up with chain head — when a new tx hits one of the contracts, it appears in the DB within 1 block of inclusion.
B. Frontend (React)
B.1. `/contract/0x14C23B5D1cE20c094af643a20e6b0972dAD12aa8` (AgentKeysScope) loads and shows:
- Name: `AgentKeysScope`
- Bytecode size: 3146 bytes
- Deploy tx: linkable to the original deploy tx page
- Function call counter (number of writes since deploy)
- Tab navigation: Overview / Transactions / Events / Read Functions
B.2. `/contract/0x76D574a107727bE87fc1422661A030FEFda70786/events` (SidecarRegistry) lists at least the one DeviceRegistered event from block 9620483, with topic[0] decoded as DeviceRegistered(bytes32,bytes32,bytes32,uint8,uint8,bytes32) and indexed args (deviceKeyHash, operatorOmni, actorOmni) shown.
B.3. `/contract/0x76D574a107727bE87fc1422661A030FEFda70786/call` exposes a form for the read-only functions (isActive(bytes32), getDevice(bytes32), operatorMasterWallet(bytes32), etc.) — filling the args + clicking "Call" returns the decoded result without a tx.
B.4. `/agentkeys/actor/0x941cb1c3260518bbf40eac7d02663517fc7cff304d9b03e80d2cc54126c6bef2` (the live registered operator from block 9620483) shows:
- 1 device registered
- 0 scope grants (we haven't issued any yet)
- 0 audit entries
- Current K3 epoch = 1
C. Search
C.1. Pasting 0x14C23B5D1cE20c094af643a20e6b0972dAD12aa8 into the search bar routes to the contract overview page.
C.2. Pasting 0x8f1d7cca5710c2859b4f8b942c36df41d3c6b8b02a862d1f506285a6176c988b (the device-register tx) routes to the EVM tx detail page, with the decoded function call SidecarRegistry.registerMasterDevice(...) and all its args shown.
C.3. Typing the keyword DeviceRegistered → autocomplete suggests the SidecarRegistry events route filtered to topic[0] of that event sig.
D. Performance (acceptance smoke)
D.1. Indexer cold-start (from genesis) on Heima mainnet completes the four contracts' history in under 30 minutes on a 4-core / 8GB host.
D.2. Frontend pages (overview, txs, events) load under 500ms p50 on the indexed data.
E. Backward-compatibility
E.1. All existing subscan-essentials Substrate-side routes (`/extrinsic/`, `/account/`, etc.) continue to work unchanged.
E.2. No schema migration breaks for existing operators upgrading from the previous indexer version. New EVM tables are additive.
Future revision plan (out of scope for this issue)
These are tracked as follow-up issues, NOT blocking MVP:
- Operator-supplied ABI uploads — let any operator point at any contract address + upload its ABI; the explorer decodes it. Today's MVP hard-codes the four AgentKeys contracts.
- Bytecode verification — match deployed bytecode against compiled source (
forge inspect) and flag verified contracts with a checkmark.
- Per-actor activity dashboards — analytics across operators (top-N by audit volume, scope-change rate, etc.).
- Cross-contract trace — given a tx hash that calls A which calls B, render the full call tree (would need debug_traceTransaction RPC support on Heima collators).
- WebSocket subscriptions — real-time event push so dashboards update without refresh.
Reference
🤖 Generated with Claude Code
Background
AgentKeys (
litentry/agentKeys) v2 stage 1 has deployed its four anchor contracts to Heima mainnet (chain_id 212013). These are written in Solidity 0.8.20, target EVMlondon(matching Heima's Frontier EVM level), and are the on-chain source of truth for the AgentKeys protocol's identity + scope + audit state.The existing subscan-essentials explorer is Substrate-side only — it indexes pallet extrinsics + events but does NOT decode EVM contract calls, EVM events, or EVM contract storage. Today, the only way to inspect these contracts is via direct RPC (
eth_getCode,cast call,cast logs). For operators + auditors, that's friction; for the project's "everything auditable on-chain" guarantee (seearch.md§6 + §15.3 inlitentry/agentKeys), we need a first-class explorer view of the AgentKeys contracts.This issue tracks adding EVM contract data + search support to subscan-essentials, scoped to a basic-but-extensible MVP. Future stages add ABI decoding for arbitrary contracts (not just AgentKeys'); this stage hard-codes our four.
Live mainnet addresses to support
All on Heima mainnet (chain_id 212013, RPC https://rpc.heima-parachain.heima.network):
AgentKeysScope0x14C23B5D1cE20c094af643a20e6b0972dAD12aa8SidecarRegistry0x76D574a107727bE87fc1422661A030FEFda70786K3EpochCounter0x8396dEc50ff755d6DE7728DABB00Be2eFBCdf4dFCredentialAudit0x1801ded1a4FBD8c9224Ab18B9EcbB293B8674c06ABIs + source live in
crates/agentkeys-chain/in the agentKeys repo. Canonical address registry:docs/spec/deployed-contracts.md.MVP scope (what this issue ships)
1. EVM contract index ingestion
The backend indexer learns to ingest contract data from the Frontier RPC namespace:
eth_getCode(addr, 'latest'). Indexed at deploy block (one-shot; updates only ifselfdestructtriggers, which AgentKeys contracts don't use).to == contract_addrtransaction. Indexed continuously viaeth_getBlockByNumberwalking. Each entry stores: block, txHash, from, value, calldata, status, gasUsed.Logwhoseaddress == contract_addr. Index by topic[0] (event signature hash) for cheap filtering. Each entry stores: block, txHash, logIndex, topics[], data.eth_getStorageAt(addr, slot)for declared slots from the ABI's storage layout output.2. Per-contract view (UI)
For each of the four AgentKeys contracts, the subscan-essentials-ui-react frontend adds a route:
ABI decoding is hard-coded for the four AgentKeys contracts in MVP (ship the ABIs in the indexer's config). v1.1+ adds operator-supplied ABI uploads.
3. Search
Search bar accepts:
/contract/<addr>4. Per-actor cross-contract view (THIS is what makes the explorer useful for AgentKeys)
A new top-level route
/agentkeys/actor/<operator_omni_hex>shows ONE operator's activity across ALL four contracts:SidecarRegistry.DeviceRegisteredfiltered onindexed operatorOmni)AgentKeysScope.ScopeUpdated)CredentialAudit.AuditAppended)K3EpochCounter.K3Rotated, latest)This is the auditor's view: "show me everything this operator has done on-chain."
Acceptance criteria / test standard
The MVP is complete when ALL of these pass:
A. Backend ingestion (Go service)
A.1.
make build+make testpass with the new indexer modules added.A.2. For each of the four AgentKeys contracts, running the indexer against Heima mainnet produces:
contractrow in the DBbytecode_sizematching the on-chain bytecode size (3146, 3301, 687, 1421 bytes respectively)A.3. Re-running the indexer is idempotent (no duplicate rows).
A.4. The indexer keeps up with chain head — when a new tx hits one of the contracts, it appears in the DB within 1 block of inclusion.
B. Frontend (React)
B.1. `/contract/0x14C23B5D1cE20c094af643a20e6b0972dAD12aa8` (
AgentKeysScope) loads and shows:B.2. `/contract/0x76D574a107727bE87fc1422661A030FEFda70786/events` (
SidecarRegistry) lists at least the oneDeviceRegisteredevent from block 9620483, with topic[0] decoded asDeviceRegistered(bytes32,bytes32,bytes32,uint8,uint8,bytes32)and indexed args (deviceKeyHash, operatorOmni, actorOmni) shown.B.3. `/contract/0x76D574a107727bE87fc1422661A030FEFda70786/call` exposes a form for the read-only functions (
isActive(bytes32),getDevice(bytes32),operatorMasterWallet(bytes32), etc.) — filling the args + clicking "Call" returns the decoded result without a tx.B.4. `/agentkeys/actor/0x941cb1c3260518bbf40eac7d02663517fc7cff304d9b03e80d2cc54126c6bef2` (the live registered operator from block 9620483) shows:
C. Search
C.1. Pasting
0x14C23B5D1cE20c094af643a20e6b0972dAD12aa8into the search bar routes to the contract overview page.C.2. Pasting
0x8f1d7cca5710c2859b4f8b942c36df41d3c6b8b02a862d1f506285a6176c988b(the device-register tx) routes to the EVM tx detail page, with the decoded function callSidecarRegistry.registerMasterDevice(...)and all its args shown.C.3. Typing the keyword
DeviceRegistered→ autocomplete suggests the SidecarRegistry events route filtered to topic[0] of that event sig.D. Performance (acceptance smoke)
D.1. Indexer cold-start (from genesis) on Heima mainnet completes the four contracts' history in under 30 minutes on a 4-core / 8GB host.
D.2. Frontend pages (overview, txs, events) load under 500ms p50 on the indexed data.
E. Backward-compatibility
E.1. All existing subscan-essentials Substrate-side routes (`/extrinsic/`, `/account/`, etc.) continue to work unchanged.
E.2. No schema migration breaks for existing operators upgrading from the previous indexer version. New EVM tables are additive.
Future revision plan (out of scope for this issue)
These are tracked as follow-up issues, NOT blocking MVP:
forge inspect) and flag verified contracts with a checkmark.Reference
forge inspect <Contract> abiin the chain crate🤖 Generated with Claude Code