Problem
The API writes loan and transaction state to Supabase optimistically and
submits to Soroban separately. Several paths make the two sources diverge
permanently: provisional loan IDs are persisted before the real on-chain ID
is known (loans.service.generateProvisionalLoanId), transaction records are
written fire-and-forget (transactions.service.persistTransactionRecord,
.catch() only logs), and indexed events can be missed. There is no process
that periodically compares indexed on-chain contract state against the
database and repairs drift, so the API can serve loan/balance data the chain
does not agree with.
Before Starting
Read ALL of these before writing any code:
- context/architecture-context.md
- context/code-standards.md
- context/progress-tracker.md
What To Build
A scheduled reconciliation job that treats the chain (via the event indexer)
as the source of truth:
- Periodically read authoritative on-chain state for loans, liquidity
positions, and reputation using the indexer / contract clients.
- Diff against the corresponding Supabase rows; classify each mismatch
(missing row, stale status, provisional ID never resolved, orphaned
pending transaction).
- Repair safely and idempotently: resolve provisional IDs, backfill missed
events, mark stuck transactions, and emit a structured drift report.
- Expose metrics (drift count by type) and never mutate on-chain state.
Files To Touch
- src/jobs/state-reconciliation/* (new module + processor)
- src/indexer/* (read authoritative state)
- src/modules/loans, src/modules/transactions repositories
- src/modules/metrics (drift gauges)
Acceptance Criteria
Mandatory Checks Before Opening PR
Problem
The API writes loan and transaction state to Supabase optimistically and
submits to Soroban separately. Several paths make the two sources diverge
permanently: provisional loan IDs are persisted before the real on-chain ID
is known (
loans.service.generateProvisionalLoanId), transaction records arewritten fire-and-forget (
transactions.service.persistTransactionRecord,.catch()only logs), and indexed events can be missed. There is no processthat periodically compares indexed on-chain contract state against the
database and repairs drift, so the API can serve loan/balance data the chain
does not agree with.
Before Starting
Read ALL of these before writing any code:
What To Build
A scheduled reconciliation job that treats the chain (via the event indexer)
as the source of truth:
positions, and reputation using the indexer / contract clients.
(missing row, stale status, provisional ID never resolved, orphaned
pending transaction).
events, mark stuck transactions, and emit a structured drift report.
Files To Touch
Acceptance Criteria
Mandatory Checks Before Opening PR
anytypes introduced