implement loan default detection background job#92
Conversation
- Add DefaultDetectionProcessor (@Cron '0 * * * *') that queries active loans past grace period, builds mark_defaulted XDR, signs with admin keypair, submits via transactions service, updates DB status, and clears reputation cache - Add buildMarkDefaultedTx to CreditLineContractClient - Add LOAN_DEFAULT to TransactionType enum - Register JobsModule in app.module.ts with required dependencies Closes StepFi-app#71
EmeditWeb
left a comment
There was a problem hiding this comment.
Review verdict: ⚠️ Request changes (money-state divergence)
Good structure — @Cron('0 * * * *') (not BullMQ), an isRunning overlap guard, graceful admin-keypair degradation, no any, per-loan error isolation, and the correct single-arg mark_defaulted(loan_id) call.
But there is a real money-consistency bug in processOverdueLoan: the on-chain mark_defaulted submission is wrapped in a try/catch that only logs on failure, and then markDefaultedOffChain(loan) runs unconditionally afterward. So when the on-chain call fails (RPC hiccup, tx_bad_seq, admin key issue), the DB still records the loan as defaulted. Because fetchOverdueLoans then excludes already-defaulted loans, the on-chain mark_defaulted is never retried → permanent divergence: the DB says "defaulted", the contract never marked it, loss-socialization never runs on-chain, and LPs are left unprotected while the app believes it's handled.
Fix: gate the off-chain marking on on-chain success, or introduce a default_pending / retryable state so a failed on-chain submission is re-attempted on a later cycle instead of being silently finalized off-chain.
Also: once #87 (sequence-number manager) lands, route these admin submissions through it so default-detection doesn't collide with other admin transactions on the source account.
…eManagerService - Only mark loan as defaulted off-chain when on-chain submission succeeds; on failure the loan stays active and is retried next cycle, preventing permanent DB/contract divergence. - Introduce SequenceManagerService to manage admin account sequence numbers and route admin submissions (mark_defaulted) through it, ready for integration with StepFi-app#87 sequence-number manager. - Update CreditLineContractClient.buildMarkDefaultedTx to accept an optional source account from the sequence manager.
Summary
Implements the missing default detection background job that automatically detects loans past their grace period and marks them as defaulted.
Changes
New Files
src/jobs/default-detection.processor.ts— Cron job (@Cron('0 * * * *'), every hour) that:next_payment_due + 7 days < now()mark_defaulted(loan_id)XDR viaCreditLineContractClient.buildMarkDefaultedTx()STELLAR_ADMIN_SECRET(server-side keypair)TransactionsService.submitTransaction()defaultedwithdefaulted_attimestampreputation_cacheso next read picks up on-chain scoresrc/jobs/jobs.module.ts— Module registering the processor with itsStellarModuleandTransactionsModuledependenciesModified Files
src/stellar/contracts/clients/creditline.client.ts— AddedbuildMarkDefaultedTx(loanId)that builds an unsigned Soroban XDR callingmark_defaultedon the creditline contract (followsbuildRepayLoanTxpattern)src/stellar/contracts/interfaces/creditline.interface.ts— AddedbuildMarkDefaultedTxtoICreditLineClientinterfacesrc/modules/transactions/dto/submit-transaction-request.dto.ts— AddedLOAN_DEFAULT = 'loan_default'toTransactionTypeenumsrc/app.module.ts— ImportedJobsModule.env.example— AddedSTELLAR_ADMIN_SECRETenv var documentationAcceptance Criteria
@Cron('0 * * * *'))next_payment_due)mark_defaultedon-chain via prepared XDRdefaultedVerification
npm run build— passesnpm run test— 24 suites, 288 tests, all passingCloses #71