Problem
TransactionsService.submitTransaction() (src/modules/transactions/transactions.service.ts, lines 87–112) accepts any well-formed XDR string and immediately submits it to Horizon. It never checks that:
- Source account == authenticated wallet — the
wallet parameter is used only to attribute the persisted record (line 101), not validated against transaction.source. A user can submit transactions signed by entirely different accounts, and the DB records them as that user's activity — poisoning loan/vendor dashboards, audit trails, and the indexer.
- No submission idempotency — the same XDR submitted twice hits Horizon twice (second may fail on seq, but the race is real for time-bounded txs), and two rows get inserted for one hash.
- No rate limiting — the endpoint is an open relay to Horizon; spamming costs the API's Horizon quota and lets abusers use the backend as a free transaction proxy.
- No operation allowlist — arbitrary operations (payments, account merge, set options, trustlines) pass through, including operations that make no sense in StepFi's flows.
Because persistTransactionRecord is fire-and-forget (line 101), Horizon succeeds even when persistence fails — the transaction hash is then unknown to the status-checker/indexer reconciliation paths.
Ground Rules
- Read context/architecture-context.md, context/code-standards.md, context/progress-tracker.md in full
- Read
SubmitTransactionRequestDto and the transaction-type taxonomy it defines
- Read
src/modules/transactions/transactions.repository.ts and the transactions table usage in getTransactionStatus() before changing persistence
What To Build
- Parse the XDR, extract the source account (and fee-bump inner source), and reject with a structured error unless it equals the authenticated
wallet.
- Enforce an operation allowlist per declared
type (e.g. loan_payment must contain the expected contract-invocation op) — reject mismatched payloads with codes like TRANSACTION_TYPE_MISMATCH.
- Idempotency: unique constraint on transaction hash + pre-insert existence check; duplicate submissions return the original record instead of re-submitting.
- Add per-wallet and global rate limits using the repo's established middleware patterns.
- Make persistence failures surface: retry queue or at minimum return 202-with-warning semantics documented in the DTO — do not silently drop.
- Tests covering every rejection branch plus the happy path regression.
Files To Touch
src/modules/transactions/transactions.service.ts
src/modules/transactions/dto/submit-transaction-request.dto.ts
src/modules/transactions/transactions.repository.ts
- migration for unique hash constraint
- tests
- relevant docs/progress tracker
Acceptance Criteria
Mandatory Checks Before Opening PR
Standard checklist applies. PRs failing any check will be closed without review.
Problem
TransactionsService.submitTransaction()(src/modules/transactions/transactions.service.ts, lines 87–112) accepts any well-formed XDR string and immediately submits it to Horizon. It never checks that:walletparameter is used only to attribute the persisted record (line 101), not validated againsttransaction.source. A user can submit transactions signed by entirely different accounts, and the DB records them as that user's activity — poisoning loan/vendor dashboards, audit trails, and the indexer.Because
persistTransactionRecordis fire-and-forget (line 101), Horizon succeeds even when persistence fails — the transaction hash is then unknown to the status-checker/indexer reconciliation paths.Ground Rules
SubmitTransactionRequestDtoand the transaction-type taxonomy it definessrc/modules/transactions/transactions.repository.tsand thetransactionstable usage ingetTransactionStatus()before changing persistenceWhat To Build
wallet.type(e.g.loan_paymentmust contain the expected contract-invocation op) — reject mismatched payloads with codes likeTRANSACTION_TYPE_MISMATCH.Files To Touch
src/modules/transactions/transactions.service.tssrc/modules/transactions/dto/submit-transaction-request.dto.tssrc/modules/transactions/transactions.repository.tsAcceptance Criteria
Mandatory Checks Before Opening PR
Standard checklist applies. PRs failing any check will be closed without review.