Problem
src/common/stellar-indexer.service.ts polls Horizon for contract events but the fetchAndStoreEvents method has a critical // TODO and does nothing with the events it receives:
// stellar-indexer.service.ts line ~60
this.logger.debug(Found ${records.length} ${eventType} events for contract ${contractId});
// TODO: Parse and upsert into contributions / loans tables
// This is where you'd decode the XDR event payloads from Soroban
Without this, the database is never populated from on-chain data — the whole sync loop is a no-op.
Task
Implement event decoding and database upsert for contribution and loan events:
- Decode Soroban event XDR payloads
import { xdr, scValToNative } from "@stellar/stellar-sdk";
// Each event record has a value field with base64 XDR
const decoded = scValToNative(xdr.ScVal.fromXDR(record.value.xdr, "base64"));
- Handle contribution events
Event payload from contract: (member_address, amount, period)
Upsert into Contribution table:
await this.prisma.contribution.upsert({
where: { txHash: record.transaction_hash },
create: { groupId, memberAddress: member, amount, period, txHash, ledger },
update: {},
});
-
Handle loan_requested events
Event payload: (loan_id, borrower, amount)
Upsert into Loan table with status "Pending".
-
Handle loan_approved events
Update matching loan to status: "Approved", set approvedAt.
-
Track last-synced ledger per contract
Store a lastSyncedLedger per contractId in a simple in-memory Map (or new Prisma model) to avoid re-processing old events on every poll.
Acceptance Criteria
- XDR payloads decoded using @stellar/stellar-sdk
- contribution events upserted into Contribution table
- loan_requested and loan_approved events upserted into Loan table
- Duplicate events handled gracefully (upsert, not insert)
- Last-synced ledger tracked to avoid duplicate processing
- Unit test with mocked Horizon response
Problem
src/common/stellar-indexer.service.ts polls Horizon for contract events but the fetchAndStoreEvents method has a critical // TODO and does nothing with the events it receives:
// stellar-indexer.service.ts line ~60
this.logger.debug(
Found ${records.length} ${eventType} events for contract ${contractId});// TODO: Parse and upsert into contributions / loans tables
// This is where you'd decode the XDR event payloads from Soroban
Without this, the database is never populated from on-chain data — the whole sync loop is a no-op.
Task
Implement event decoding and database upsert for contribution and loan events:
import { xdr, scValToNative } from "@stellar/stellar-sdk";
// Each event record has a
valuefield with base64 XDRconst decoded = scValToNative(xdr.ScVal.fromXDR(record.value.xdr, "base64"));
Event payload from contract: (member_address, amount, period)
Upsert into Contribution table:
await this.prisma.contribution.upsert({
where: { txHash: record.transaction_hash },
create: { groupId, memberAddress: member, amount, period, txHash, ledger },
update: {},
});
Handle loan_requested events
Event payload: (loan_id, borrower, amount)
Upsert into Loan table with status "Pending".
Handle loan_approved events
Update matching loan to status: "Approved", set approvedAt.
Track last-synced ledger per contract
Store a lastSyncedLedger per contractId in a simple in-memory Map (or new Prisma model) to avoid re-processing old events on every poll.
Acceptance Criteria