Skip to content

feat(api): add blend accrue keeper - #484

Merged
collinsezedike merged 12 commits into
drydocs:mainfrom
Adecom16:feat/blend-accrual-keeper
Aug 13, 2026
Merged

collinsezedike merged 12 commits into
drydocs:mainfrom
Adecom16:feat/blend-accrual-keeper

Conversation

@Adecom16

@Adecom16 Adecom16 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes #466

Summary

  • Add a scheduled Blend accrual keeper.
  • Discover adapters dynamically and submit accrue() only for Blend-backed adapters.
  • Add retry handling and structured failure reporting.
  • Add a protected cron endpoint and configuration.
  • Add tests and operational documentation.

Validation

  • Tests passed
  • Lint passed
  • Build passed

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

@Adecom16 is attempting to deploy a commit to the Collins' projects Team on Vercel.

A member of the Team first needs to authorize it.

@Adecom16 Adecom16 changed the title feat(api): add Blend accrual keeper feat(api): add blend accrue keeper Aug 6, 2026

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CI is failing on Test, not for a failing test (all 117 pass) but on the coverage threshold: Coverage for branches (73.95%) does not meet global threshold (76%).

accrual-keeper.ts is at 64.04% branch coverage, with 120, 126, 131, 153, 191-198, 330-375, 396-399, 405-413 uncovered. That's the retry and failure-handling logic specifically, the part covering what happens when a submission fails, which is exactly what determines whether this keeper degrades safely or silently. Please add tests covering those branches so the coverage threshold passes.

@Adecom16
Adecom16 requested a review from collinsezedike August 8, 2026 13:16
@Adecom16

Adecom16 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

CI is failing on Test, not for a failing test (all 117 pass) but on the coverage threshold: Coverage for branches (73.95%) does not meet global threshold (76%).

accrual-keeper.ts is at 64.04% branch coverage, with 120, 126, 131, 153, 191-198, 330-375, 396-399, 405-413 uncovered. That's the retry and failure-handling logic specifically, the part covering what happens when a submission fails, which is exactly what determines whether this keeper degrades safely or silently. Please add tests covering those branches so the coverage threshold passes.

Addressed the coverage feedback. Added targeted tests covering the keeper's retry, discovery, Blend filtering, submission, failure/recovery, and dependency paths. Branch coverage is now above the required CI threshold.

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is real, substantial infrastructure work, and the coverage fix addresses what CI flagged. But there are several unaddressed issues in code that signs and submits real transactions with a funded key, serious enough to hold this before merge.

api/v1/keepers/accrue.ts has no applyCors/checkRateLimit, unlike every other api/v1/* handler, despite each call potentially signing and submitting a real transaction.

The cron auth has a real bypass. isCronAuthorized returns true unconditionally whenever CRON_SECRET is unset and NODE_ENV !== "production". Vercel serverless functions don't reliably set NODE_ENV=production the way traditional Node apps do (Vercel's own environment signal is VERCEL_ENV), so this could be wide open on the actual production deployment, not just local dev. Please gate on VERCEL_ENV === "production" instead, and fail closed rather than open when the secret is unconfigured.

submitAccrualTransaction signs and submits against a live-discovered adapter address with no assertSubmittable allowlist check, unlike every other signed submission path in tx.ts. adapterId comes from get_adapter at run time; if a vault's stored adapter pointer is ever wrong, the keeper signs and submits against whatever address comes back with none of the known-contract checks assertSubmittable exists to enforce.

KNOWN_POOLS.mainnet has zero protocol: "meridian" entries, so discoverLiveAdapters's filter matches nothing there. The new mainnet cron in vercel.json will run every 15 minutes and silently no-op, returning 200 with empty results indistinguishable from a healthy run, while apps/docs/operations/accrual-keeper.md advertises a 15-minute staleness guarantee that isn't actually enforced for any mainnet vault yet.

Smaller things worth fixing in the same pass:

  • isTransientKeeperError matches unanchored substrings like "500"/"503"/"not_found", so a permanent error whose message happens to contain those digits gets retried instead of failing immediately.
  • waitForTransaction is called without rpcTimeoutMs, so MERIDIAN_KEEPER_RPC_TIMEOUT_MS (documented as a real config knob) doesn't actually bound the confirmation-wait step.
  • The cron route in vercel.json has no functions.maxDuration, while the keeper processes adapters sequentially with retries and confirmation waits, that adds up as more vaults go live.
  • submitAccrualTransaction and withKeeperRetry reimplement tx.ts's submission pipeline and shared/utils.ts's withRetry instead of reusing them, so future fixes to either won't propagate here.

@collinsezedike

Copy link
Copy Markdown
Collaborator

Hey @Adecom16, checking in on the review from a few days ago. The coverage fix was solid, but the second round flagged some real issues in code that signs and submits live transactions (the cron auth bypass, the missing rate limit, the missing assertSubmittable check) that need to be addressed before this can merge. Let me know if anything in there is unclear or if you're stuck on any of it.

@collinsezedike

Copy link
Copy Markdown
Collaborator

Pushed a commit addressing the outstanding review findings, since this has been quiet for a while:

  • Cron auth gate now checks VERCEL_ENV === "production" instead of NODE_ENV, closing the bypass (Vercel doesn't reliably set NODE_ENV=production the way traditional Node apps do).
  • isTransientKeeperError now matches HTTP status codes with word boundaries, so a permanent error whose message happens to contain "500"/"503" elsewhere (an amount, a ledger number) is no longer misclassified as transient and retried.
  • waitForTransaction is now called with rpcTimeoutMs, so MERIDIAN_KEEPER_RPC_TIMEOUT_MS actually bounds the confirmation-wait step as documented.
  • Added functions.maxDuration for the cron route in vercel.json.
  • Clarified the accrual keeper docs: the 15-minute staleness guarantee only covers vaults the keeper actually discovers, and KNOWN_POOLS.mainnet currently has none, so the mainnet cron runs and finds nothing, that's expected, not a failure.
  • Tests added for the auth fail-closed path and the status-code anchoring fix.

The assertSubmittable-style allowlist check on the discovered adapter address and reusing tx.ts's submission pipeline are still open as cleanup, not blocking, since the adapter address comes from a read on an already-trusted, known vault contract, not unvalidated input.

This is solid infrastructure work, thank you for building it. Merging once CI is green.

@collinsezedike

Copy link
Copy Markdown
Collaborator

Two more fixes, both from the follow-up review:

  • Preview deployments now require CRON_SECRET too, not just production. This endpoint triggers real signed transactions off the keeper's funded account, unlike simple rate-limit relaxation elsewhere, so an unauthenticated preview URL was a real gas-drain risk, not just a convenience gap. Only true local dev (no VERCEL_ENV at all) stays permissive.
  • discoverLiveAdapters now runs vault discovery concurrently instead of one at a time. On the free Vercel tier, maxDuration is hard-capped at 60s, and sequential discovery plus sequential submission meant total wall-clock time was the sum of every vault's worst case, already close to the limit for a single vault under retries. Discovery is pure reads with no shared state, so it's safe to parallelize; submission stays sequential since every transaction signs from the same keeper account and Stellar requires strictly increasing sequence numbers per account, running those concurrently would race and mostly fail, not save time.

Also added a test proving the concurrency actually happens (not just that results are still correct), and a test for the preview fail-closed path.

CI should be green now, this looks ready to merge.

collinsezedike
collinsezedike previously approved these changes Aug 13, 2026

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for building this, solid infrastructure work overall. CI is green (the one remaining "Vercel" failure is a deployment authorization issue on our end, unrelated to this PR). Approved and ready to merge.

@collinsezedike
collinsezedike merged commit c8a1793 into drydocs:main Aug 13, 2026
8 of 9 checks passed
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.

[Feature] TVL/APY displays can go stale indefinitely with no accrual keeper

2 participants