Skip to content

feat: track state-change block and canceler on IndexingAgreement#7

Open
MoonBoi9001 wants to merge 1 commit intomb9/track-on-chain-rca-offersfrom
mb9/track-state-change-block-and-canceler
Open

feat: track state-change block and canceler on IndexingAgreement#7
MoonBoi9001 wants to merge 1 commit intomb9/track-on-chain-rca-offersfrom
mb9/track-state-change-block-and-canceler

Conversation

@MoonBoi9001
Copy link
Copy Markdown
Member

Motivation

The IndexingAgreement entity currently captures the full business state of an agreement -- payer, indexer, pricing, timestamps of major transitions -- but two gaps make it awkward to consume as a source of truth for state reconciliation.

First, there is no block-indexed marker of the most recent state change. The existing lastUpdatedAt timestamp is only written in handleAgreementUpdated, so polling lastUpdatedAt_gt would miss accepts, cancels, and collections. Consumers that want a delta-style "what moved since block N" have to either iterate every entity on every poll or fall back to reading the event-log entities (which cuts against the aggregated-entity direction of PR #5).

Second, when an agreement is canceled the entity records state = CanceledByServiceProvider | CanceledByPayer but loses the actual signer address. Most cancels come directly from the payer or the indexer so the enum is sufficient, but in production an operator can sign on behalf of either party and the enum collapses that to just the party category. Dipper's chain_listener compares the canceler address to its own signer address to decide whether a cancel was self-initiated or not -- without the address on the entity, that check has to fall back to the event-log entity.

Summary

  • Adds lastStateChangeBlock: BigInt! to IndexingAgreement. Every handler that transitions state stamps it with event.block.number: handleAgreementAccepted / Canceled / Updated / RCACollected on the RecurringCollector data source, and handleIndexingAgreementAccepted / Canceled / Updated on the SubgraphService data source. createOrLoadIndexingAgreement initialises it to zero.
  • Adds canceledBy: Bytes! to IndexingAgreement, initialised to Bytes.empty() and written in a new handleIndexingAgreementCanceled handler that reads canceledOnBehalfOf from the SubgraphService event. The RecurringCollector handler continues to set the state enum; neither handler overwrites the other's field.
  • Binds IndexingAgreementCanceled on the SubgraphService data source in subgraph.template.yaml.
  • Matchstick tests: a new handleIndexingAgreementCanceled test that verifies canceledBy captures a distinct operator address (not inferred from payer/indexer) and that lastStateChangeBlock is stamped. Existing accept and update tests gain lastStateChangeBlock assertions.

Both new fields are additive and backwards-compatible: existing consumers that don't read them see no change, and new consumers that filter on lastStateChangeBlock_gt or read canceledBy get the delta-friendly shape they need.

Generated with Claude Code

@MoonBoi9001 MoonBoi9001 force-pushed the mb9/track-state-change-block-and-canceler branch from 704daf4 to b4c94d8 Compare April 23, 2026 09:27
@MoonBoi9001 MoonBoi9001 changed the base branch from main to mb9/track-on-chain-rca-offers April 23, 2026 09:27
@MoonBoi9001 MoonBoi9001 force-pushed the mb9/track-state-change-block-and-canceler branch from b4c94d8 to 60ec575 Compare April 23, 2026 09:32
MoonBoi9001 added a commit that referenced this pull request Apr 23, 2026
Drop the branches: [main] filter so stacked PRs whose base is another
branch in the same repo (e.g. PR #7 stacked on this one) still get
build-and-test runs. Keep the push trigger narrow to main so CI does
not fire on every force-push across feature branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MoonBoi9001 MoonBoi9001 force-pushed the mb9/track-state-change-block-and-canceler branch from 60ec575 to 9bafd57 Compare April 23, 2026 09:45
Consumers that reconcile agreement state against the on-chain truth
(dipper's chain_listener, primarily) need two things the aggregated
IndexingAgreement entity does not currently expose:

1. A block-indexed marker of the most recent state change, so a poll
   can fetch only agreements that moved since the last seen block.
   Adds `lastStateChangeBlock: BigInt!` to the entity and stamps it
   with `event.block.number` on every handler that transitions state:
   AgreementAccepted / Canceled / Updated / RCACollected in
   recurringCollector.ts, and IndexingAgreementAccepted / Canceled /
   Updated in subgraphService.ts. Distinct from the existing
   `lastUpdatedAt` timestamp which is only written on AgreementUpdated.

2. The address that initiated a cancel, preserved as data rather than
   derived from the enum. Adds `canceledBy: Bytes!` to the entity and
   a new `handleIndexingAgreementCanceled` on the SubgraphService data
   source that reads `canceledOnBehalfOf` from the event. That captures
   operator-initiated cancels correctly -- the RecurringCollector's
   AgreementCanceled event only carries an enum (ServiceProvider or
   Payer) and the real signer is lost.

Matchstick tests cover the new field stamping and the new cancel
handler; existing tests gain `lastStateChangeBlock` assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MoonBoi9001 MoonBoi9001 force-pushed the mb9/track-state-change-block-and-canceler branch from 9bafd57 to 8360209 Compare April 24, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant