Skip to content

Indexer: ingest Bounty/* chain events into bounties table (follow-up to #75) #76

Description

@proofmancer

Follow-up after the API surface for bounty matching landed in #75. The bounties table is empty until the indexer learns to ingest the chain's bounty events; this issue tracks that work.

Dependencies

  • Blocked on ligate-chain v0.4.0 shipping. The chain side (PRs #531, #532, #533) is in main but unreleased; devnet-2 is still on v0.3.1 + chain_hash eec077f4… which has no bounty module. Once v0.4.0 cuts, the chain emits Bounty/BountyPosted, BountyClaimed, BountyDisputed, DisputeResolved, BountyExpired events.

Scope

1. Event payload types in ligate-api-types

Add typed structs mirroring the chain's bounty::Event<S> payloads:

  • BountyBountyPostedEvent { bounty_posted: { bounty_id, poster, pool } }
  • BountyBountyClaimedEvent { bounty_claimed: { bounty_id, attestation_id, payout, attester } }
  • BountyBountyDisputedEvent { bounty_disputed: { bounty_id, attestation_id, disputer, bond } }
  • BountyDisputeResolvedEvent { dispute_resolved: { bounty_id, attestation_id, decision, bond_recipient } }
  • BountyBountyExpiredEvent { bounty_expired: { bounty_id, refunded_to_poster } }

Same naming convention as the attestation events: outer wrapper struct + #[serde(rename)] on the inner field. Capture the real wire format from a devnet-3 (or whatever chain id ships at v0.4) /v1/ledger/events sample before locking the field names.

2. Event key constants in indexer::parser

const KEY_BOUNTY_POSTED:       &str = "Bounty/BountyPosted";
const KEY_BOUNTY_CLAIMED:      &str = "Bounty/BountyClaimed";
const KEY_BOUNTY_DISPUTED:     &str = "Bounty/BountyDisputed";
const KEY_BOUNTY_RESOLVED:     &str = "Bounty/DisputeResolved";
const KEY_BOUNTY_EXPIRED:      &str = "Bounty/BountyExpired";

3. IndexerTx enum variants + parser branches

Add IndexerPostBounty, IndexerClaimBounty, IndexerDisputeAttestation, IndexerResolveDispute, IndexerCancelBounty variants to IndexerTx. Add parser branches in classify_events() (after the Attestation passes, before the Bank fallback) so a bounty tx's semantic event wins over its fee transfer event.

4. Chain RPC enrichment

Critical detail: the chain's BountyPosted event only carries (bounty_id, poster, pool). To populate acceptance / expiry_da_height / dispute_window_blocks / per_attestation_nano columns, the indexer needs to fetch from the chain's GET /v1/modules/bounty/bounties/{id} after seeing each BountyPosted event.

Pattern matches the existing schema-detail fetch the indexer already does. Add a chain_client.fetch_bounty(id) helper that returns the full BountyState, call it in db::write_bounty_posted().

5. DB write functions in indexer::db

  • write_bounty_posted(tx, event_details, full_state) — INSERT into bounties with status='open'
  • write_bounty_claimed(tx, event_details) — UPDATE escrow_remaining_nano + claim_count + last_claim_at_slot; transition to 'exhausted' when escrow < per_attestation
  • write_bounty_disputed(tx, event_details) — no bounties-table change at v0; dispute state lives in chain only (could add disputes table mirror later)
  • write_dispute_resolved(tx, event_details) — same; v0 noop on bounties table
  • write_bounty_expired(tx, event_details) — UPDATE status to 'expired' (or 'cancelled' depending on which path triggered)

6. Tests

  • Indexer e2e: feed a fixture event stream through ingest, assert bounties table rows match expected lifecycle.
  • API: existing query function should now return populated matches when the indexer has ingested events.

Acceptance

  • All 5 bounty events parsed + persisted
  • Test fixture covers post → claim → disputed → resolved → expired lifecycle
  • The GET /v1/bounties/matching/{address} endpoint surfaces matching bounties on devnet (whatever chain id v0.4 cuts to)
  • CHANGELOG entry under [Unreleased] documenting the new types + tables

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions