Skip to content

implement loan default detection background job#92

Open
khaylebfortune wants to merge 2 commits into
StepFi-app:mainfrom
khaylebfortune:implement-loan
Open

implement loan default detection background job#92
khaylebfortune wants to merge 2 commits into
StepFi-app:mainfrom
khaylebfortune:implement-loan

Conversation

@khaylebfortune

Copy link
Copy Markdown
Contributor

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:
    • Queries Supabase for active loans where next_payment_due + 7 days < now()
    • Builds a mark_defaulted(loan_id) XDR via CreditLineContractClient.buildMarkDefaultedTx()
    • Signs the XDR with STELLAR_ADMIN_SECRET (server-side keypair)
    • Submits via TransactionsService.submitTransaction()
    • Updates loan status to defaulted with defaulted_at timestamp
    • Clears borrower's reputation_cache so next read picks up on-chain score
    • Falls back to off-chain-only marking if no admin keypair is configured
  • src/jobs/jobs.module.ts — Module registering the processor with its StellarModule and TransactionsModule dependencies

Modified Files

  • src/stellar/contracts/clients/creditline.client.ts — Added buildMarkDefaultedTx(loanId) that builds an unsigned Soroban XDR calling mark_defaulted on the creditline contract (follows buildRepayLoanTx pattern)
  • src/stellar/contracts/interfaces/creditline.interface.ts — Added buildMarkDefaultedTx to ICreditLineClient interface
  • src/modules/transactions/dto/submit-transaction-request.dto.ts — Added LOAN_DEFAULT = 'loan_default' to TransactionType enum
  • src/app.module.ts — Imported JobsModule
  • .env.example — Added STELLAR_ADMIN_SECRET env var documentation

Acceptance Criteria

  • Job runs every hour (@Cron('0 * * * *'))
  • Detects loans past grace period (7 days from next_payment_due)
  • Calls mark_defaulted on-chain via prepared XDR
  • Updates local DB status to defaulted
  • Decreases borrower reputation (invalidates reputation cache)
  • Build passes (verified)
  • All 288 tests pass

Verification

  • npm run build — passes
  • npm run test — 24 suites, 288 tests, all passing

Closes #71

- 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
@khaylebfortune khaylebfortune changed the title feat: add default detection cron job for overdue loans implement loan default detection background job Jul 18, 2026

@EmeditWeb EmeditWeb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.
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.

core: implement loan default detection background job

2 participants