Skip to content

fix: indexer collects final payment after payer-side cancel#1216

Open
MoonBoi9001 wants to merge 6 commits into
main-dipsfrom
mb9/collect-after-payer-cancel
Open

fix: indexer collects final payment after payer-side cancel#1216
MoonBoi9001 wants to merge 6 commits into
main-dipsfrom
mb9/collect-after-payer-cancel

Conversation

@MoonBoi9001
Copy link
Copy Markdown
Member

TL;DR

Stops the indexer-agent from losing payment for honest work when the payer (via dipper) cancels an agreement on-chain before the agent has its own chance to cancel. Adds the missing final collect on the already-canceled path.

Motivation

When the payer cancels an indexing agreement on-chain (Studio shrinking the indexer set is the common case), the agent observes the resulting CanceledByPayer state, sweeps the deployment's DIPs rule, closes the allocation, and writes a NEVER rule so the deployment stays synced without being allocated to. The next collection tick is where the indexer loses money — the agent's "cancel blocklisted agreements" pass sees the NEVER rule and routes the already-canceled agreement back through the cancel function.

The cancel function tries a second on-chain cancel, the contract reverts because the agreement is already canceled, and the function returns early before reaching its own final-collection step. The indexer is never paid for the period the agreement was active, even though those funds are sitting in the payer's escrow and would have been collectable.

Summary

  • Skip already-canceled agreements when sweeping for opt-out rules.
  • In the cancel function, skip the doomed on-chain step when already canceled.
  • Proceed straight to the final collect so the indexer gets paid.
  • Two new unit tests covering both branches.
  • Tests added but not run locally — CI will run them.

Generated with Claude Code

When the payer cancels first via the on-chain contract, the agent's
own opt-out rule from closing the allocation routes the agreement
back through a second cancel. That cancel reverts, the function
returns early, and the indexer is never paid for the active period.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-project-automation github-project-automation Bot moved this to 🗃️ Inbox in Indexer May 14, 2026
MoonBoi9001 and others added 3 commits May 14, 2026 20:24
CI's format check rejected the previous commit because the new test
assertions exceeded the line-length budget. No behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Existing comments in the file cap at 4 lines; the additions ran to 6-7.
Trims both to two lines while keeping the why intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-link the two CanceledByPayer guards, document the return contract
of cancelAgreement, and raise the lost-fees log to error with the
deployment id attached. Consolidate the duplicate test block and add
the failure-path coverage that was missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

// Step 1: Cancel on-chain
// Step 1: Cancel on-chain (skipped if payer already canceled — a second
// cancel reverts on `InvalidAgreementState` and would skip the final collect).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would avoid adding:

— a second cancel reverts on `InvalidAgreementState` and would skip the final collect

// Already-canceled agreements need a final collect, not another cancel —
// the regular collection loop handles them. cancelAgreement also guards
// this state internally as defense-in-depth.
if (agreement.state === 'CanceledByPayer') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we could expand this and only check agreements which state is active

if (agreement.state !== 'Active') continue

@github-project-automation github-project-automation Bot moved this from 🗃️ Inbox to ✅ Approved in Indexer May 20, 2026
MoonBoi9001 and others added 2 commits May 22, 2026 00:42
The sweep that routes blocklisted agreements to the cancel function
now only acts on actively-collectable ones, mirroring the GraphQL
query that already filters server-side. The cancel-step comment
drops a redundant parenthetical that overstated the rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MoonBoi9001 MoonBoi9001 requested a review from Maikol May 21, 2026 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Approved

Development

Successfully merging this pull request may close these issues.

2 participants