Skip to content

feat(ingest): EVM ingest worker — ERC-20 Transfer log scanning #221

Description

@Emmyt24

Depends on: #218, #220, #223. Blocks: #222.

Description

octo-ingest polls Horizon /payments oldest-first from a saved
paging_token, attributes each payment by muxed id or memo id, and records it idempotently, with
dedup on the Horizon TOID. EVM deposit detection is structurally different:

  • No per-account payments feed. You scan eth_getLogs for ERC-20 Transfer events whose
    to topic matches one of your deposit addresses.
  • The cursor is a block number, not an opaque token.
  • Native ETH transfers emit no logs at all — they are only visible by inspecting block
    transactions or debug_traceBlock. Decide and document whether native ETH deposits are in scope
    (recommendation: out of scope for v1, ERC-20 stablecoins only, stated explicitly in the docs
    rather than silently unsupported).
  • Non-standard ERC-20s exist: fee-on-transfer tokens where the amount received ≠ the amount in the
    event, and rebasing tokens. The registry (feat(store): ERC-20 token registry #223) is the defence; this worker must trust the
    registry, not the token.

Build the EVM ingest worker to the same reliability bar as the Stellar one.

Requirements and context

Suggested execution

Branch: feat/evm-ingest-worker

Implement changes

Test and commit

  • Anvil integration tests (chore(testing): Anvil-based EVM integration test harness #219): a real ERC-20 transfer to a deposit address is detected, attributed,
    and recorded with the exact amount at 6 and 18 decimals.
  • Adversarial test — the critical one: deploy a hostile contract that emits a
    Transfer event with a deposit address in the to topic and a huge value, and assert it is
    not credited because the emitting contract is not a registered token. Model on
    crates/ingest/tests/adversarial_replay_tests.rs.
  • Replay/idempotency test: processing the same log range twice records nothing new.
  • Crash-resume test: kill mid-batch, restart, assert exactly-once processing. Mirror
    resume_replay_tests.rs.
  • Range-bisection test: a mock RPC returning RangeTooLarge causes bisection, not a stall.
  • Test that a transfer of an unregistered token to a known address is quarantined, not credited.
  • Update docs/ingest-integration.md.

Example commit message

feat(ingest): EVM deposit detection via ERC-20 Transfer logs

Scans eth_getLogs for registered token contracts with a block-number
cursor, advancing only after durable processing so a crash resumes
exactly-once — the same guarantee the Horizon path gives.

Logs are matched on the emitting contract address, not topics alone:
any contract can emit a Transfer event with arbitrary topics, so
topic-only attribution would let an attacker mint balances.

Deposits are recorded unconfirmed; crediting is gated on #222.

Refs #221

Guidelines

Large issue — split into stacked PRs (cursor/scanning, then decoding/attribution) if that helps
review. Do not merge a version that credits balances before #222 lands.


Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea/backendBackend crates: api, store, ingest, webhooks, bin/serverdifficulty/hardHard, complex, cross-cuttingtype/epicTracking issue for a multi-issue epic

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions