Skip to content

feat(intents): batch lookup, list-filter enum validation, token rejection & manual sweep trigger (#269, #270, #275, #276) - #349

Open
priscaenoch wants to merge 1 commit into
stellar-vortex-protocol:mainfrom
priscaenoch:feature/269-276-intent-ops-and-validation
Open

feat(intents): batch lookup, list-filter enum validation, token rejection & manual sweep trigger (#269, #270, #275, #276)#349
priscaenoch wants to merge 1 commit into
stellar-vortex-protocol:mainfrom
priscaenoch:feature/269-276-intent-ops-and-validation

Conversation

@priscaenoch

Copy link
Copy Markdown

Summary

Implements four Stellar Wave issues in the intents surface:

Issue Change
#269 Safe, auditable manual sweep trigger (SIGUSR2) replacing the REPL break-glass procedure
#270 Enum validation for ListIntentsDto.state / chain (@IsIn), returning a real 400 instead of a silently-empty result
#275 New bounded POST /api/v1/intents/batch status-lookup endpoint
#276 Reject intent creation against an unrecognised source/destination token instead of silently defaulting priceUSD

Closes #269
Closes #270
Closes #275
Closes #276

Issue links:


Changes

#269 — Manual sweep trigger

  • IntentsSweeperService.sweep() now returns a SweepResult ({ expiredCount, slashedCount, durationMs }).
  • New IntentsSweeperService.triggerManualSweep(source) runs exactly one sweep cycle and logs the invocation loudly (MANUAL SWEEP TRIGGERED / … COMPLETE / … FAILED with source + timestamp + result) so it stands out in an incident timeline.
  • main.ts installs a SIGUSR2 handler that calls it. Operator-only: it needs shell access to the host (kill -USR2 <pid>), has no HTTP surface, and is unreachable by any API client.
  • docs/runbooks/on-call.md → "Manual sweep trigger (emergency)" rewritten with the real procedure (the "attach a Node.js REPL" text is gone).

#270 — Enum validation for list filters

  • intents.types.ts exports INTENT_STATES (mirrors SUPPORTED_CHAINS); IntentState is now derived from it — single source of truth.
  • ListIntentsDto.state@IsIn(INTENT_STATES), chain@IsIn(SUPPORTED_CHAINS), both still @IsOptional().
  • @ApiPropertyOptional({ enum }) added so /docs shows the real allowed values.
  • GET /api/v1/intents?state=bogus / ?chain=notachain now return 400 with validation details. user is unchanged (free-form address, per the issue).

#275 — Batch intent-status lookup

  • POST /api/v1/intents/batch — body { intentIds: string[] }, capped at 100 via @ArrayMaxSize. Returns { intents, count }; IDs with no match are omitted (not individually 404'd). POST because the ID list can exceed a query-string.
  • IntentsService.getMany(ids) reuses get() per ID and de-duplicates. A code comment notes issue feat: rebuild backend on NestJS, drop Express #1's Prisma adapter should implement this as a single WHERE intent_id IN (...) query.
  • Subject to the existing global rate limit — no new tier. Read-only (batch mutation is out of scope).

#276 — Reject unrecognised tokens

  • TokensService.resolveSrcTokenOrThrow / resolveDstTokenOrThrow wrap the existing resolve* helpers and throw BadRequestException when the token is not in the registry.
  • IntentsController.create() uses them → an unknown srcTokenAddress (on a known chain) or dstTokenContract now returns a clean 400 instead of creating an intent with priceUSD: undefined.
  • quote() applies the same check when a token contract/address is supplied (symbol-only quotes still work, so existing quote fixtures are unaffected).
  • CHANGELOG.md documents the behaviour tightening (previously-accepted malformed requests now rejected).

Merge-corruption restored (required for the touched files to compile)

Two lines dropped by earlier merge-conflict resolutions on main, restored because they block compilation of files this PR edits:

  • src/intents/intents.service.ts — re-added import { INTENTS_REPOSITORY, IIntentsRepository } from "./intents.repository"; (added by 42a8c89, lost in merge 1ef46dd).
  • src/solvers/solvers.service.tsreactivate() { ...solver, isActive }{ ...solver, isActive: true }, matching every sibling method in the file.

Testing / validation

Unit tests (run and pass):

  • src/tokens/tokens.service.spec.tsresolveSrcTokenOrThrow / resolveDstTokenOrThrow (found → returns, unknown/empty → BadRequestException).
  • src/intents/intents-batch-lookup.spec.tsgetMany: all found / some missing / empty input / de-dupes.
  • src/intents/intents-sweeper.manual-trigger.spec.ts — runs exactly one sweep, logs loudly with source, propagates + logs failure.
$ npx jest src/tokens/tokens.service.spec.ts src/intents/intents-batch-lookup.spec.ts src/intents/intents-sweeper.manual-trigger.spec.ts
Test Suites: 3 passed, 3 total
Tests:       31 passed, 31 total

E2E tests added (in test/validation-negative-paths.e2e-spec.ts and test/intents.e2e-spec.ts): batch endpoint shape + array-cap 400; ?state=bogus / ?chain=bogus400; unknown dstTokenContract / srcTokenAddress400; the old "unknown srcToken still succeeds" fixture updated to assert the new 400.

Lint: npm run lint — 0 errors (pre-existing warnings only).


⚠️ Pre-existing CI state (not introduced by this PR)

main is currently red before this branch — npm run typecheck / npm test / npm run test:e2e fail on main at a305ab0 due to merge-conflict corruption from concurrently-merged PRs, in files this PR does not own and was instructed not to modify:

  • src/intents/intents.controller.tslist()/quote() lost async, Throttle import dropped (9 tsc errors).
  • src/stats/stats.service.ts — references renamed method buildProtocolStats.
  • src/intents/intents.service.spec.ts, intents-sweeper.service.spec.ts, intents.gateway.spec.ts — out of sync with the current IntentsService constructor.
  • test/__mocks__/@stellar/stellar-sdk.ts / test/utils/create-test-app.ts — missing exports/imports, blocking the whole e2e suite.

This PR reduces the tsc error count (fixes 4, introduces 0) and repairs the solvers.service unit suite. The remaining failures need a separate reconciliation PR from the owners of those files.

🤖 Generated with Claude Code

… manual sweep trigger

Implements four Stellar Wave issues:

stellar-vortex-protocol#269 — Safe manual sweep trigger
- IntentsSweeperService.sweep() now returns a SweepResult summary.
- New IntentsSweeperService.triggerManualSweep(source) runs exactly one sweep
  cycle and logs the invocation loudly (source, timestamp, result).
- main.ts installs a SIGUSR2 handler that invokes it — operator-only (needs
  shell access), no HTTP surface. Replaces the REPL break-glass procedure in
  docs/runbooks/on-call.md.

stellar-vortex-protocol#270 — Enum validation for ListIntentsDto.state / chain
- intents.types.ts exports INTENT_STATES (mirrors SUPPORTED_CHAINS); IntentState
  is now derived from it.
- ListIntentsDto.state uses @isin(INTENT_STATES); chain uses @isin(SUPPORTED_CHAINS).
  Both stay @IsOptional(). Swagger enum: annotations added.
- ?state=bogus / ?chain=bogus now return 400 with validation details instead of
  a silently-empty result set.

stellar-vortex-protocol#275 — Batch intent-status lookup
- New POST /api/v1/intents/batch accepting { intentIds: string[] } (capped at 100
  via @ArrayMaxSize). Returns the record for each found ID; missing IDs omitted.
- IntentsService.getMany() reuses get() per ID (de-duplicated); Prisma adapter
  should implement this as a single WHERE intent_id IN (...) query.

stellar-vortex-protocol#276 — Reject unrecognised destination/source tokens
- TokensService.resolveSrcTokenOrThrow / resolveDstTokenOrThrow throw a
  BadRequestException when the token is not in the registry.
- IntentsController.create() uses them, so an unknown srcTokenAddress or
  dstTokenContract now returns 400 instead of creating an intent with
  priceUSD undefined. quote() applies the same check when a token identifier
  is supplied.
- CHANGELOG.md notes the behaviour tightening.

Tests: new tokens.service, getMany and manual-trigger unit specs; e2e coverage
for the batch endpoint, enum-filter 400s and unknown-token 400s.

Also restores two lines dropped by earlier merge conflicts that block
compilation of the files touched here: the INTENTS_REPOSITORY import in
intents.service.ts and `isActive: true` in SolversService.reactivate().
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@priscaenoch 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant