Skip to content

feat(indexer): decode Soroban event XDR and upsert contributions & loans (closes #2) - #18

Merged
Mona-i merged 1 commit into
coopfinance:mainfrom
Vyacheslav-Tomashevskiy:fix/stellar-indexer-xdr-decode
Jul 24, 2026
Merged

feat(indexer): decode Soroban event XDR and upsert contributions & loans (closes #2)#18
Mona-i merged 1 commit into
coopfinance:mainfrom
Vyacheslav-Tomashevskiy:fix/stellar-indexer-xdr-decode

Conversation

@Vyacheslav-Tomashevskiy

Copy link
Copy Markdown

Closes #2.

Problem

src/common/stellar-indexer.service.ts polls Horizon for contract events, but fetchAndStoreEvents stopped at a // TODO: Parse and upsert… — so the whole sync loop was a no-op and the DB was never populated from on-chain data.

What this does

Implements the decode + upsert step inside the existing structure (keeps the @Cron pollers and the (groupId, contractId, eventType) signature intact — no rewrite of the polling design):

  1. Decode each event's topic/value XDR with scValToNative(xdr.ScVal.fromXDR(base64, "base64")). Routing is driven by the event name in the first topic, so it's robust regardless of the poller's eventType hint.
  2. Contribution(member: Address, amount: i128, period: u32)Contribution, idempotent via upsert on the unique txHash.
  3. Loanloan_requested / loan_approved / loan_repaid (id, borrower, amount[, status])Loan, matched by (groupId, onChainId) so lifecycle events update the same row instead of duplicating it (approvedAt / repaidAt set accordingly).
  4. Skips events where in_successful_contract_call === false; a single unparseable record is logged and skipped instead of aborting the batch.

The event shapes match exactly what the contracts emit (treasury::contribute publishes ("contribution", (member, amount, period)); loan::request/approve/repay publish (id, borrower, amount[, status])).

Tests

Adds a ts-jest config (ts-jest was already a devDependency but no jest config existed) and stellar-indexer.service.spec.ts, which builds real event XDR with nativeToScVal the same way the contracts emit it and asserts the decode + upsert routing:

PASS src/common/stellar-indexer.service.spec.ts
  ✓ decodes a contribution event and upserts it on txHash
  ✓ decodes a loan_requested event and creates a Pending loan
  ✓ updates an existing loan instead of duplicating it
  ✓ ignores unrelated events (e.g. withdrawal)

No new runtime dependencies (@stellar/stellar-sdk was already present); package-lock.json is untouched.

…ans (closes coopfinance#2)

fetchAndStoreEvents had a TODO where decoded events should be persisted,
so the Horizon sync loop was a no-op. Decode each event's topic/value XDR
with scValToNative and route by event name onto the Prisma models:

- contribution (member, amount, period) -> Contribution, idempotent on txHash
- loan_requested/approved/repaid (id, borrower, amount[, status]) -> Loan,
  matched by (groupId, onChainId) so lifecycle updates don't duplicate rows

Unsuccessful calls are skipped; a single unparseable record is logged and
skipped instead of aborting the batch. Adds a ts-jest config and a spec
that builds real event XDR the same way the contracts emit it.
@Mona-i
Mona-i merged commit 2e40ffc into coopfinance:main Jul 24, 2026
1 check failed
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.

[API] Implement Soroban event XDR decoder in StellarIndexerService

2 participants