Skip to content

feat: chain-aware fill window, per-user intent cap, remove dead metho… - #362

Merged
james2177 merged 2 commits into
stellar-vortex-protocol:mainfrom
abrak01:feat/chain-aware-fill-window-user-cap-cleanup-slash-reconciliation
Sep 2, 2026
Merged

feat: chain-aware fill window, per-user intent cap, remove dead metho…#362
james2177 merged 2 commits into
stellar-vortex-protocol:mainfrom
abrak01:feat/chain-aware-fill-window-user-cap-cleanup-slash-reconciliation

Conversation

@abrak01

@abrak01 abrak01 commented Sep 1, 2026

Copy link
Copy Markdown

…ds, slash reconciliation

Issue 1 — chain-aware fill window in acceptIfOpen

  • Add CHAIN_FILL_WINDOW_DEFAULTS and DEFAULT_FILL_WINDOW_SECONDS to configuration.ts (mirrors CHAIN_DEADLINE_DEFAULTS shape, documented with per-chain rationale: stellar=120s, base/optimism/arbitrum/avalanche=600s, polygon=900s, ethereum=1800s).
  • IntentsService.acceptIfOpen() now looks up the intent's srcChain and uses CHAIN_FILL_WINDOW_DEFAULTS[srcChain] instead of the hardcoded 300s; falls back to DEFAULT_FILL_WINDOW_SECONDS for unknown chains.
  • Add 4 unit tests to acceptIfOpen describe block: stellar 120s window, ethereum 1800s window, stellar < ethereum assert distinct deadlines, and unknown-chain fallback to DEFAULT_FILL_WINDOW_SECONDS.
  • Update docs/solver-onboarding.md slashing section with a per-chain fill window table and operator guidance on planning within these windows.

Issue 2 — per-user open-intent cap

  • Add MAX_OPEN_INTENTS_PER_USER=50 named constant and countOpenByUser() method to IntentsService; designed to stay efficient against a future Prisma COUNT query without touching the interface (issue feat: rebuild backend on NestJS, drop Express #1).
  • IntentsController.create() checks countOpenByUser before creating and throws ConflictException(409) with an explicit message distinguishing it from the existing rate-limit 429.
  • Add e2e test in test/intent-lifecycle.e2e-spec.ts: creates exactly MAX_OPEN_INTENTS_PER_USER intents, asserts N+1 returns 409 with cap message, cancels one, asserts creation succeeds again.
  • Update docs/runbooks/on-call.md troubleshooting table to reference the real cap (MAX_OPEN_INTENTS_PER_USER in src/intents/intents.service.ts) instead of the old "consider adding a cap" suggestion.

Issue 3 — remove dead markLive/markOffline from SolversService Decision: removed (not wired up, no distinct call site from reactivate/ deactivate); will file a separate issue for real WS-driven liveness detection once issue #96 WS auth lands.

  • Delete markLive() and markOffline() from SolversService.
  • Fix pre-existing bug in reactivate(): isActive was an undefined variable reference; corrected to isActive: true.
  • Remove the markLive test from solvers.service.spec.ts.

Issue 4 — recordFailedFill pending-slash reconciliation

  • Add SolverPenaltyState ('pending'|'confirmed'|'failed') type and SolverPendingPenalty interface to solvers.types.ts.
  • SolversService: add pendingPenalties Map, update recordFailedFill to accept intentId and store a pending entry; add confirmPenalty(intentId, slashAmount) which reconciles bondAmount on on-chain confirmation; add rollbackPenalty(intentId) which decrements fillsFailed when the on-chain slash submission fails or never confirms.
  • IntentsSweeperService.slashMissedFill: call recordFailedFill(solver, intentId) first (pending state), then submit on-chain; on submission failure call rollbackPenalty so the solver is not permanently penalised for an unenforced slash.
  • EventIngestionService: inject SolversService, add solver_slashed branch to processEvent, add handleSolverSlashed which extracts (solverAddress, intentId, slashAmount) from the event topic and calls confirmPenalty; malformed events are warned and skipped without stalling the poll loop.
  • SorobanModule: import SolversModule so SolversService is available to EventIngestionService.
  • event-ingestion.service.spec.ts: pass fakeSolversService() to constructor.
  • Update docs/solver-onboarding.md with pending-vs-confirmed slash lifecycle.

Fixes: sweeper spec missing imports (ALPHA_ADDR, SOLVERS_REPOSITORY, buildIntentsService), gateway spec 3-arg IntentsService construction, intents.service.spec.ts makeService/buildService missing repo arg and PrismaService provider. All 4 modified test suites pass (30/30 tests).

Summary

Related issue

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI / tooling

Component

  • Contract (vortex-contract)
  • Backend (vortex-backend)
  • Frontend (vortex-frontend)

Checklist

  • My code follows the project's style and conventions
  • I ran lint / type-check / build locally and they pass
  • I added or updated tests where appropriate
  • I updated documentation where appropriate
  • My commits follow Conventional Commits

Screenshots / notes

closes #265
closes #266
closes #267
closes #268

…ds, slash reconciliation

Issue 1 — chain-aware fill window in acceptIfOpen
- Add CHAIN_FILL_WINDOW_DEFAULTS and DEFAULT_FILL_WINDOW_SECONDS to
  configuration.ts (mirrors CHAIN_DEADLINE_DEFAULTS shape, documented with
  per-chain rationale: stellar=120s, base/optimism/arbitrum/avalanche=600s,
  polygon=900s, ethereum=1800s).
- IntentsService.acceptIfOpen() now looks up the intent's srcChain and uses
  CHAIN_FILL_WINDOW_DEFAULTS[srcChain] instead of the hardcoded 300s; falls
  back to DEFAULT_FILL_WINDOW_SECONDS for unknown chains.
- Add 4 unit tests to acceptIfOpen describe block: stellar 120s window,
  ethereum 1800s window, stellar < ethereum assert distinct deadlines, and
  unknown-chain fallback to DEFAULT_FILL_WINDOW_SECONDS.
- Update docs/solver-onboarding.md slashing section with a per-chain fill
  window table and operator guidance on planning within these windows.

Issue 2 — per-user open-intent cap
- Add MAX_OPEN_INTENTS_PER_USER=50 named constant and countOpenByUser()
  method to IntentsService; designed to stay efficient against a future
  Prisma COUNT query without touching the interface (issue stellar-vortex-protocol#1).
- IntentsController.create() checks countOpenByUser before creating and
  throws ConflictException(409) with an explicit message distinguishing
  it from the existing rate-limit 429.
- Add e2e test in test/intent-lifecycle.e2e-spec.ts: creates exactly
  MAX_OPEN_INTENTS_PER_USER intents, asserts N+1 returns 409 with cap
  message, cancels one, asserts creation succeeds again.
- Update docs/runbooks/on-call.md troubleshooting table to reference the
  real cap (MAX_OPEN_INTENTS_PER_USER in src/intents/intents.service.ts)
  instead of the old "consider adding a cap" suggestion.

Issue 3 — remove dead markLive/markOffline from SolversService
Decision: removed (not wired up, no distinct call site from reactivate/
deactivate); will file a separate issue for real WS-driven liveness detection
once issue stellar-vortex-protocol#96 WS auth lands.
- Delete markLive() and markOffline() from SolversService.
- Fix pre-existing bug in reactivate(): isActive was an undefined variable
  reference; corrected to isActive: true.
- Remove the markLive test from solvers.service.spec.ts.

Issue 4 — recordFailedFill pending-slash reconciliation
- Add SolverPenaltyState ('pending'|'confirmed'|'failed') type and
  SolverPendingPenalty interface to solvers.types.ts.
- SolversService: add pendingPenalties Map, update recordFailedFill to
  accept intentId and store a pending entry; add confirmPenalty(intentId,
  slashAmount) which reconciles bondAmount on on-chain confirmation; add
  rollbackPenalty(intentId) which decrements fillsFailed when the on-chain
  slash submission fails or never confirms.
- IntentsSweeperService.slashMissedFill: call recordFailedFill(solver, intentId)
  first (pending state), then submit on-chain; on submission failure call
  rollbackPenalty so the solver is not permanently penalised for an unenforced
  slash.
- EventIngestionService: inject SolversService, add solver_slashed branch to
  processEvent, add handleSolverSlashed which extracts (solverAddress, intentId,
  slashAmount) from the event topic and calls confirmPenalty; malformed events
  are warned and skipped without stalling the poll loop.
- SorobanModule: import SolversModule so SolversService is available to
  EventIngestionService.
- event-ingestion.service.spec.ts: pass fakeSolversService() to constructor.
- Update docs/solver-onboarding.md with pending-vs-confirmed slash lifecycle.

Fixes: sweeper spec missing imports (ALPHA_ADDR, SOLVERS_REPOSITORY,
buildIntentsService), gateway spec 3-arg IntentsService construction,
intents.service.spec.ts makeService/buildService missing repo arg and
PrismaService provider. All 4 modified test suites pass (30/30 tests).
@drips-wave

drips-wave Bot commented Sep 1, 2026

Copy link
Copy Markdown

@abrak01 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@james2177
james2177 merged commit 4ecd8f8 into stellar-vortex-protocol:main Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants