Envio-side prerequisites tracked in chain-events/yearn-indexing-test#24 — transactionIndex column, chains 100/146/80094 enabled, per-event coverage audit. Must land before EVMLOG_SOURCE=envio rollout.
Description
Kong currently fetches event logs by calling viem getLogs() against configured RPCs (packages/ingest/extract/evmlogs.ts:41). Direct RPC calls have been observed to drop logs, leaving holes in the evmlog table. The yearn internal envio indexer already maintains a complete, deduped event stream in Postgres. This spec replaces the RPC fetch with a direct pull from the envio DB, gated by an env var so RPC remains a fallback, and adds a maintenance script to backfill historical gaps from envio into kong.
Context
Current implementation
packages/ingest/extract/evmlogs.ts:37-48 — EvmLogsExtractor.extract() calls rpcs.next(chainId, from).getLogs({ address, events, fromBlock, toBlock }) to fetch logs.
- Result feeds hooks (
packages/ingest/extract/evmlogs.ts:50-76) and is queued to mq.job.load.evmlog (:90-95).
packages/ingest/load/index.ts:55-87 upserts each row into evmlog and updates evmlog_strides.
evmlog schema: packages/db/migrations/sqls/20240214020032-eventsource-up.sql:1-15. PK (chain_id, address, signature, block_number, log_index, transaction_hash). transaction_index int4 NOT NULL. topics text[] NOT NULL. block_time timestamptz.
Target system
- Envio replica DB at
postgresql://envio_ix_7f3a:…@dpg-d75faobuibrs73bmodug-b.replica-cyan.virginia-postgres.render.com/envio_indexer_jvm1.
- Schema
envio holds typed per-event tables (one table per event, e.g. envio.\"Transfer\", envio.\"StrategyReported\", envio.\"V3RegistryNewEndorsedVault\", …).
- Common columns:
chainId, vaultAddress (or registry/factory address), blockNumber, blockTimestamp (unix int), blockHash, transactionHash, transactionFrom, logIndex, plus event-specific args.
- Missing vs kong evmlog requirements:
- No
transactionIndex column (any event table inspected so far)
- No
topics[] array (only event_name + typed args)
- No
signature (topic0); must be derived from event ABI
blockTimestamp is unix int, kong needs timestamptz
- Envio's transient
envio.raw_events table has the right shape but is empty in production (rotated/cleared).
- Envio indexer source:
~/Desktop/yearn/yearn-indexing-test/apps/indexer (config at apps/indexer/config.yaml, handlers at src/EventHandlers.ts).
Coverage
- Envio chains observed in
envio.\"Transfer\": 1, 10, 137, 250, 8453, 42161, 747474. Kong also indexes 100 (gnosis), 146 (sonic), 80094 (bera) — must verify per chain whether envio is configured.
- Envio events (per
envio schema): yearn V2/V3 vault, ERC4626 Deposit/Withdraw, V2 Registry/Registry2, V3 Registry/RoleManager/RoleManagerFactory/VaultFactory/Splitter/YieldSplitter/Accountant, VeyfiGaugeRegistered, VotingEscrowCreated, StakingPoolAdded, plus all V3 vault role/config events.
- Kong ABIs (sample, full audit needed against
config/abis.yaml): erc4626, yearn/2/vault, yearn/2/strategy, yearn/2/registry, yearn/2/registry2, yearn/3/vault, yearn/3/strategy, yearn/3/registry, yearn/3/registry2, yearn/3/registry3, yearn/3/vaultFactory, yearn/3/roleManager, yearn/3/roleManagerFactory, yearn/3/debtManagerFactory, yearn/governance/votingEscrow, yearn/staking/registry/{juiced,v3,opboost,veyfi}, yearn/staking/pool. Each has events under packages/ingest/abis/<path>/event/.../hook.ts.
Tasks
1. Envio-side prerequisites (~/Desktop/yearn/yearn-indexing-test/apps/indexer)
- Add
transactionIndex to field_selection.transaction_fields in apps/indexer/config.yaml so envio captures it.
- Update
src/EventHandlers.ts to persist transactionIndex on every event entity write.
- Update
schema.graphql to add transactionIndex: Int! on every event type.
- Run envio migration to add the column to all per-event tables (or accept that a full reindex is needed — confirm with envio team).
- Audit
config.yaml against kong's config/abis.yaml:
- Add chains kong indexes that envio doesn't (
100, 146, 80094 — verify).
- Add any kong events not yet handled by envio. The PR description must list each missing
(abiPath, event_name) tuple and the envio config + handler additions needed.
- Document in this spec PR which events stay RPC-only (if any) until envio catches up.
2. Envio DB client in kong (packages/ingest)
- Add envio DB connection: env vars
ENVIO_DATABASE_URL (full URL, recommended) or split ENVIO_PG_HOST/PORT/USER/PASSWORD/DATABASE/SSL. Validate via zod in packages/lib/types.ts or co-located.
- New file
packages/ingest/extract/envio.ts:
- Export
pool: pg.Pool for the envio replica (read-only — set default_transaction_read_only=on).
- Export
fetchEnvioLogs(chainId, address, from, to, abiPath) returning rows shaped like viem's Log (so the existing extract() flow needs no downstream changes).
- Build a static map
abiPath → { event_name → { table: 'envio.\"Transfer\"', signature: '0x…', topics: ['0x…'], paramsToArgs(row): {...} } }. Keep the map alongside each ABI under packages/ingest/abis/<path>/envio.ts so it ships with the ABI definition.
- Per call, run
UNION ALL across the relevant per-event tables filtered by chainId, vaultAddress=\$, blockNumber BETWEEN \$ AND \$.
- Hydrate each row into the kong log shape:
address checksummed via getAddress(), topics: [signature], args from typed envio columns, blockNumber: bigint, blockTime via to_timestamp(blockTimestamp), transactionIndex from envio (after envio change above).
3. Source switch with env-var gating
- Add to env validation:
EVMLOG_SOURCE=rpc|envio (default rpc) and per-chain override EVMLOG_SOURCE_<chainId>=rpc|envio.
- Add helper
evmlogSource(chainId): 'rpc' | 'envio' resolving global default + per-chain override.
- In
packages/ingest/extract/evmlogs.ts:37-48, branch on evmlogSource(chainId):
rpc → existing rpcs.next(...).getLogs(...).
envio → fetchEnvioLogs(chainId, address, from, to, abiPath).
- For events not in the abi's envio map, fall back to RPC for that event only (so rollout is safe). Log a warning with
(chainId, abiPath, event_name) so coverage gaps are observable.
- Hooks pipeline (
evmlogs.ts:50-76) and load queue stay unchanged.
4. Maintenance/backfill script (packages/ingest/scripts/envio-sync.ts)
- Bun CLI taking
--chain <id>, --abi-path <path> (optional, defaults to all), --address <0x…> (optional), --from <block>, --to <block> (defaults to envio's max block per chain), --dry-run.
- For each (chain, address, abiPath, event_name) covered by envio:
- Select rows from envio tables in the range.
- Left-anti-join against kong
evmlog on (chain_id, address, signature, block_number, log_index, transaction_hash).
- For missing rows: hydrate into log shape, run the same hooks as
EvmLogsExtractor.extract() would (re-use requireHooks + topic filter + hook.module.default), and upsertEvmLog per chunk (batch ~500 rows).
- Update
evmlog_strides via strider.add for the swept range so subsequent fanouts skip.
- Emit a summary per (chain, address, abiPath):
envio_count, kong_count, inserted, skipped.
- Idempotent — safe to re-run.
- Add to
package.json of packages/ingest: \"envio-sync\": \"bun run scripts/envio-sync.ts\".
5. Tests
packages/ingest/extract/envio.spec.ts — mock pg pool, verify fetchEnvioLogs returns log shapes equivalent to viem getLogs (validated via EvmLogSchema).
- Source-switch test in
evmlogs.spec.ts — assert evmlogSource honors global and per-chain overrides.
- Backfill script test with a fixture of envio rows + kong rows that establish a known gap; verify only missing rows insert.
6. Documentation
- Update
CLAUDE.md (or docs/) with the new env vars and rollout playbook (start EVMLOG_SOURCE=rpc, flip per chain after envio coverage validated).
- Add a section to the spec PR description listing every kong event NOT covered by envio at merge time, with the corresponding envio-side change required.
Acceptance Criteria
Technical Notes
- Read-only: envio DB connection MUST be read-only (`default_transaction_read_only=on` on the connection) — that DB is owned by the envio team.
- Connection target: the URL above is a Render replica; confirm with the envio team that it's the right read endpoint and that it's safe to load it with the maintenance script (consider concurrency caps).
- Dedup PK: kong's `evmlog` PK includes `signature, block_number, log_index, transaction_hash`. Envio per-event tables have no `signature` column — kong constructs it from the event name via the ABI map. If two ABIs share an event name with the same selector (e.g. `Transfer`), the address-scoped lookup avoids cross-contamination because hydration is per-(abiPath, address).
- Address case: envio stores addresses as plain text; kong canonicalizes via `getAddress()`. Compare lowercased on join, store checksummed on insert.
- Block time: convert envio's int `blockTimestamp` to `timestamptz` via `to_timestamp(blockTimestamp)` before insert.
- Hook re-execution in backfill: hooks call onchain via multicall; backfilling large historical ranges will hammer archive RPCs. The script should chunk by block range and respect `RPC_BATCH_SIZE`.
- No schema migration on kong's `evmlog`: per the agreed approach, `transaction_index` stays NOT NULL — the envio side is the one that changes to provide it.
- Strider semantics: only update `evmlog_strides` for ranges where envio is a complete source for that address. If only some events of an ABI are envio-covered and the rest still RPC, do NOT extend strides from the backfill (would skip the RPC-only events on next fanout).
- Rollout: stage with `EVMLOG_SOURCE_1=envio` first, observe parity for ~24h, then expand chain by chain.
- Secrets: don't commit the envio DSN. Add to `.env.example` as `ENVIO_DATABASE_URL=` placeholder only.
Description
Kong currently fetches event logs by calling viem
getLogs()against configured RPCs (packages/ingest/extract/evmlogs.ts:41). Direct RPC calls have been observed to drop logs, leaving holes in theevmlogtable. The yearn internal envio indexer already maintains a complete, deduped event stream in Postgres. This spec replaces the RPC fetch with a direct pull from the envio DB, gated by an env var so RPC remains a fallback, and adds a maintenance script to backfill historical gaps from envio into kong.Context
Current implementation
packages/ingest/extract/evmlogs.ts:37-48—EvmLogsExtractor.extract()callsrpcs.next(chainId, from).getLogs({ address, events, fromBlock, toBlock })to fetch logs.packages/ingest/extract/evmlogs.ts:50-76) and is queued tomq.job.load.evmlog(:90-95).packages/ingest/load/index.ts:55-87upserts each row intoevmlogand updatesevmlog_strides.evmlogschema:packages/db/migrations/sqls/20240214020032-eventsource-up.sql:1-15. PK(chain_id, address, signature, block_number, log_index, transaction_hash).transaction_index int4 NOT NULL.topics text[] NOT NULL.block_time timestamptz.Target system
postgresql://envio_ix_7f3a:…@dpg-d75faobuibrs73bmodug-b.replica-cyan.virginia-postgres.render.com/envio_indexer_jvm1.envioholds typed per-event tables (one table per event, e.g.envio.\"Transfer\",envio.\"StrategyReported\",envio.\"V3RegistryNewEndorsedVault\", …).chainId,vaultAddress(or registry/factory address),blockNumber,blockTimestamp(unix int),blockHash,transactionHash,transactionFrom,logIndex, plus event-specific args.transactionIndexcolumn (any event table inspected so far)topics[]array (only event_name + typed args)signature(topic0); must be derived from event ABIblockTimestampis unix int, kong needstimestamptzenvio.raw_eventstable has the right shape but is empty in production (rotated/cleared).~/Desktop/yearn/yearn-indexing-test/apps/indexer(config atapps/indexer/config.yaml, handlers atsrc/EventHandlers.ts).Coverage
envio.\"Transfer\":1, 10, 137, 250, 8453, 42161, 747474. Kong also indexes100(gnosis),146(sonic),80094(bera) — must verify per chain whether envio is configured.envioschema): yearn V2/V3 vault, ERC4626 Deposit/Withdraw, V2 Registry/Registry2, V3 Registry/RoleManager/RoleManagerFactory/VaultFactory/Splitter/YieldSplitter/Accountant, VeyfiGaugeRegistered, VotingEscrowCreated, StakingPoolAdded, plus all V3 vault role/config events.config/abis.yaml):erc4626,yearn/2/vault,yearn/2/strategy,yearn/2/registry,yearn/2/registry2,yearn/3/vault,yearn/3/strategy,yearn/3/registry,yearn/3/registry2,yearn/3/registry3,yearn/3/vaultFactory,yearn/3/roleManager,yearn/3/roleManagerFactory,yearn/3/debtManagerFactory,yearn/governance/votingEscrow,yearn/staking/registry/{juiced,v3,opboost,veyfi},yearn/staking/pool. Each has events underpackages/ingest/abis/<path>/event/.../hook.ts.Tasks
1. Envio-side prerequisites (
~/Desktop/yearn/yearn-indexing-test/apps/indexer)transactionIndextofield_selection.transaction_fieldsinapps/indexer/config.yamlso envio captures it.src/EventHandlers.tsto persisttransactionIndexon every event entity write.schema.graphqlto addtransactionIndex: Int!on every event type.config.yamlagainst kong'sconfig/abis.yaml:100,146,80094— verify).(abiPath, event_name)tuple and the envio config + handler additions needed.2. Envio DB client in kong (
packages/ingest)ENVIO_DATABASE_URL(full URL, recommended) or splitENVIO_PG_HOST/PORT/USER/PASSWORD/DATABASE/SSL. Validate via zod inpackages/lib/types.tsor co-located.packages/ingest/extract/envio.ts:pool: pg.Poolfor the envio replica (read-only — setdefault_transaction_read_only=on).fetchEnvioLogs(chainId, address, from, to, abiPath)returning rows shaped like viem'sLog(so the existingextract()flow needs no downstream changes).abiPath → { event_name → { table: 'envio.\"Transfer\"', signature: '0x…', topics: ['0x…'], paramsToArgs(row): {...} } }. Keep the map alongside each ABI underpackages/ingest/abis/<path>/envio.tsso it ships with the ABI definition.UNION ALLacross the relevant per-event tables filtered bychainId, vaultAddress=\$, blockNumber BETWEEN \$ AND \$.addresschecksummed viagetAddress(),topics: [signature],argsfrom typed envio columns,blockNumber: bigint,blockTimeviato_timestamp(blockTimestamp),transactionIndexfrom envio (after envio change above).3. Source switch with env-var gating
EVMLOG_SOURCE=rpc|envio(defaultrpc) and per-chain overrideEVMLOG_SOURCE_<chainId>=rpc|envio.evmlogSource(chainId): 'rpc' | 'envio'resolving global default + per-chain override.packages/ingest/extract/evmlogs.ts:37-48, branch onevmlogSource(chainId):rpc→ existingrpcs.next(...).getLogs(...).envio→fetchEnvioLogs(chainId, address, from, to, abiPath).(chainId, abiPath, event_name)so coverage gaps are observable.evmlogs.ts:50-76) and load queue stay unchanged.4. Maintenance/backfill script (
packages/ingest/scripts/envio-sync.ts)--chain <id>,--abi-path <path>(optional, defaults to all),--address <0x…>(optional),--from <block>,--to <block>(defaults to envio's max block per chain),--dry-run.evmlogon(chain_id, address, signature, block_number, log_index, transaction_hash).EvmLogsExtractor.extract()would (re-userequireHooks+ topic filter +hook.module.default), andupsertEvmLogper chunk (batch ~500 rows).evmlog_stridesviastrider.addfor the swept range so subsequent fanouts skip.envio_count,kong_count,inserted,skipped.package.jsonofpackages/ingest:\"envio-sync\": \"bun run scripts/envio-sync.ts\".5. Tests
packages/ingest/extract/envio.spec.ts— mock pg pool, verifyfetchEnvioLogsreturns log shapes equivalent to viemgetLogs(validated viaEvmLogSchema).evmlogs.spec.ts— assertevmlogSourcehonors global and per-chain overrides.6. Documentation
CLAUDE.md(ordocs/) with the new env vars and rollout playbook (startEVMLOG_SOURCE=rpc, flip per chain after envio coverage validated).Acceptance Criteria
EVMLOG_SOURCE=enviocausesEvmLogsExtractor.extract()to read from envio Postgres for every event covered by the per-ABI envio map; falls back to RPC for uncovered events with a warning.EVMLOG_SOURCE_<chainId>overrides the global default for that chain only.EvmLogSchema.array().parse(...)and upsert intoevmlogwithout PK conflicts on re-run.transactionIndex; kong rows sourced from envio carry the real transaction_index value.Technical Notes