Skip to content

feat: implement indexer foundation#42

Open
ayomideadeniran wants to merge 1 commit into
Fundable-Protocol:devfrom
ayomideadeniran:feature/indexer-foundation
Open

feat: implement indexer foundation#42
ayomideadeniran wants to merge 1 commit into
Fundable-Protocol:devfrom
ayomideadeniran:feature/indexer-foundation

Conversation

@ayomideadeniran

@ayomideadeniran ayomideadeniran commented Jun 25, 2026

Copy link
Copy Markdown

Resolves the foundational indexer issues:

Fixes #27
Fixes #28
Fixes #31
Fixes #32

Summary by CodeRabbit

  • New Features

    • Added support for event indexing with duplicate-safe processing and retry handling.
    • Introduced stream tracking, including withdrawals and cancellations, with database-backed records.
    • Added runtime configuration defaults and validation for more reliable startup.
  • Bug Fixes

    • Improved polling behavior so progress only advances after events are processed successfully.
    • Better handling of transient network/RPC failures during event fetching.
  • Tests

    • Added coverage for event processing, retry logic, and RPC client URL handling.

@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@ayomideadeniran Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds shared indexer config validation, Soroban RPC client construction, processed-event repository support, poller retry/cursor handling, and new Streams entities plus migration and exports.

Changes

Common indexer infrastructure

Layer / File(s) Summary
Shared runtime and client wiring
indexer/common/package.json, indexer/tsconfig.base.json, indexer/common/src/config/index.ts, indexer/common/src/rpc/*
Adds zod-validated config, the Soroban RPC client wrapper, and the package/compiler settings needed for the new runtime dependencies.
Indexed event repository
indexer/common/src/db/*
Adds IndexedEvent plus EventRepository lookup and duplicate-safe insert behavior, with repository tests.
Poller retry and cursor handling
indexer/common/src/poller/*
Adds SorobanPoller retry options, transient error handling, sequential event processing, cursor advancement rules, and tests.
Common package exports
indexer/common/src/index.ts
Re-exports config, RPC, database, and poller symbols from the common package entry point.

Streams database schema

Layer / File(s) Summary
Streams entities and migration
indexer/streams/package.json, indexer/streams/src/db/entity/*, indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts, indexer/streams/src/index.ts
Adds the TypeORM dependency, defines Stream, WithdrawalAction, and CancelAction, creates the initial streams schema migration, and re-exports the entities.

Sequence Diagram(s)

sequenceDiagram
  participant SorobanPoller
  participant fetchEvents
  participant processEvent
  participant updateCursor
  SorobanPoller->>fetchEvents: fetchEvents(startLedger, endLedger)
  alt transient fetch error
    SorobanPoller->>SorobanPoller: retry fetchEvents()
    SorobanPoller->>fetchEvents: fetchEvents(startLedger, endLedger)
  end
  loop each event
    SorobanPoller->>processEvent: processEvent(event)
  end
  alt all events succeed
    SorobanPoller->>updateCursor: updateCursor(endLedger)
  else processEvent fails
    SorobanPoller-->>SorobanPoller: return success=false
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mubarak23

Poem

A rabbit thumps: “hop, hop, hooray!”
New tables bloom and cursors stay.
RPC carrots crunch just right,
Polls retry through the velvet night.
Hoppity-ho, the index sings! 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing the required template sections like Summary, Area, Scope, Verification, Indexer Safety, and Notes. Fill out the repository template with Summary, Area, Scope, Verification, Indexer Safety, and Notes, plus a concise change rationale.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: implementing foundational indexer work.
Linked Issues check ✅ Passed The changes cover the indexed-event repository, Soroban RPC wrapper, poller retries, and streams schema with tests and migrations.
Out of Scope Changes check ✅ Passed I don't see unrelated code changes beyond the indexer common and streams work tied to the linked issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@indexer/common/src/db/repository.ts`:
- Around line 40-57: In recordEventProcessed, the current .orIgnore path can
silently skip duplicate inserts but still returns true, so update the logic to
detect the ignored insert result and return false when no row was written. Use
the repository/createQueryBuilder insert flow in repository.ts to distinguish a
successful insert from a conflict-suppressed no-op, and keep the
unique-violation fallback in the catch block consistent with the duplicate case.
Add test coverage around recordEventProcessed for the duplicate-suppressed path
to verify it returns false when the insert is ignored.

In `@indexer/common/src/index.ts`:
- Around line 6-7: The package entrypoint is pulling in eager side effects by
re-exporting from config and RPC modules that call loadConfig() and
createSorobanClient() at import time. Update the barrel to avoid importing those
singleton-producing modules directly; instead export only factories/types from
index.ts and move singleton initialization behind an explicit runtime entrypoint
or separate import. Keep the symbols config, loadConfig, createSorobanClient,
and sorobanClient available without forcing module-load configuration/client
construction.

In `@indexer/common/src/poller/index.ts`:
- Around line 53-60: The transient error check in isTransientError() is missing
plain HTTP 429 rate-limit failures, so polling can stop instead of retrying;
update the matching logic to recognize 429 and “too many requests” in addition
to the existing timeout/rate limit cases. Normalize the error message casing
before checking, and keep the changes localized to isTransientError() inside the
retry path used by withRetry().

In `@indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts`:
- Line 23: The initial streams schema migration uses uuid_generate_v4() for
stream_withdrawal_action.id and stream_cancel_action.id, but the uuid-ossp
extension is not guaranteed to exist on a fresh database. Update
00001_InitialStreamsSchema.ts to provision the extension first by adding CREATE
EXTENSION IF NOT EXISTS "uuid-ossp"; before the table definitions that depend on
uuid_generate_v4(), keeping the migration order in sync with the
stream_withdrawal_action and stream_cancel_action setup.
- Around line 13-16: The stream amount columns are still using 64-bit-safe
bigint even though Soroban token values are i128-sized. Update the initial
streams schema migration and the matching entity definitions for the relevant
amount fields in the streams database model to use a 128-bit-safe type such as
numeric, keeping the names totalAmount, amountWithdrawn, and amount aligned
across the migration and entity classes.

In `@indexer/tsconfig.base.json`:
- Line 23: The shared tsconfig is incorrectly applying Vitest globals to all
packages through the base config, so move the vitest/globals entry out of
tsconfig.base.json and into a dedicated test-only tsconfig. Update the relevant
package test configuration to extend the base settings while keeping normal
source builds for indexer/common, indexer/distributions, and indexer/streams
free of test ambient types, and verify any package-specific ambient types are
still declared where needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47e4d0a6-fd2e-4880-ae73-f339e0df3899

📥 Commits

Reviewing files that changed from the base of the PR and between bcfa08d and 315e10d.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • indexer/common/package.json
  • indexer/common/src/config/index.ts
  • indexer/common/src/db/entity/IndexedEvent.ts
  • indexer/common/src/db/repository.test.ts
  • indexer/common/src/db/repository.ts
  • indexer/common/src/index.ts
  • indexer/common/src/poller/index.test.ts
  • indexer/common/src/poller/index.ts
  • indexer/common/src/rpc/client.test.ts
  • indexer/common/src/rpc/client.ts
  • indexer/streams/package.json
  • indexer/streams/src/db/entity/CancelAction.ts
  • indexer/streams/src/db/entity/Stream.ts
  • indexer/streams/src/db/entity/WithdrawalAction.ts
  • indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts
  • indexer/streams/src/index.ts
  • indexer/tsconfig.base.json

Comment on lines +40 to +57
try {
await this.repo
.createQueryBuilder()
.insert()
.into(IndexedEvent)
.values({
contractId,
ledgerNumber,
txHash,
eventIndex,
})
.orIgnore("uq_indexed_event_identity") // Safe duplicate insert
.execute();
return true;
} catch (err) {
// If the driver doesn't support orIgnore, fallback to simple insert and catch unique violation.
if (err instanceof Error && err.message.includes("unique constraint")) {
return false;

@coderabbitai coderabbitai Bot Jun 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Inspect the repository file and surrounding call sites/tests for EventRepository behavior
git ls-files | rg '^indexer/common/src/db/repository\.ts$|^indexer/common/src/db/.*test.*|^indexer/common/src/db/.*spec.*|^indexer/common/src/db/.*repository.*'

Repository: Fundable-Protocol/Backend

Length of output: 240


🏁 Script executed:

# Read the target file with line numbers and map its structure
wc -l indexer/common/src/db/repository.ts && cat -n indexer/common/src/db/repository.ts | sed -n '1,220p'

Repository: Fundable-Protocol/Backend

Length of output: 2195


🏁 Script executed:

# Search for repository usage and tests that assert boolean semantics
rg -n "recordEventProcessed|isEventProcessed|orIgnore\\(|unique constraint|already processed|IndexedEvent" indexer/common/src

Repository: Fundable-Protocol/Backend

Length of output: 1772


🏁 Script executed:

# Read the repository tests to see what is already covered
cat -n indexer/common/src/db/repository.test.ts | sed -n '1,220p'

Repository: Fundable-Protocol/Backend

Length of output: 3082


🏁 Script executed:

# Inspect TypeORM insert/orIgnore result shape from public docs or source references
web_search
TypeORM InsertQueryBuilder orIgnore execute InsertResult affected rows duplicate ignored behavior

Repository: Fundable-Protocol/Backend

Length of output: 252


🌐 Web query:

TypeORM InsertQueryBuilder orIgnore execute InsertResult affected rows duplicate ignored behavior

💡 Result:

In TypeORM, using .orIgnore() with InsertQueryBuilder enables the ON CONFLICT DO NOTHING behavior (or INSERT IGNORE for MySQL) to suppress errors when unique constraint violations occur [1][2][3]. Key behaviors and limitations regarding InsertResult and affected rows include: 1. No Reliable Affected Row Count: TypeORM's InsertResult does not consistently or accurately report the number of "affected" rows when orIgnore() is used [4]. Because the database operation is suppressed by the SQL engine, TypeORM often cannot distinguish between successfully inserted rows and those that were ignored due to conflicts [4][5]. 2. Limitations on Conflict Targets: The .orIgnore() method currently functions as a simple boolean toggle [6][7]. It does not support specifying a conflict target (e.g., a specific unique index or column) [6][8]. Providing a string argument to orIgnore() (e.g., .orIgnore("id")) is discarded because the method coerces the input to a boolean [6][7]. Consequently, it will ignore conflicts on any unique index defined on the table, not just the one you might intend [8]. 3. Impact on Subscribers and Result Mapping: When using .orIgnore(), you may encounter issues where: - Subscribers like afterInsert() are triggered even for rows that were not actually inserted [5]. - Entity ID mapping or generated column assignment may become inconsistent because the InsertResult does not account for the skipped rows, potentially leading to incorrect data mapping back to your entity objects [4]. If you require precise control over conflict handling or need accurate feedback on which rows were inserted versus ignored, .orIgnore() is generally insufficient. It is recommended to handle conflict logic through database-specific constraints or, where supported, more granular upsert logic [1][2].

Citations:


🏁 Script executed:

cat -n indexer/common/src/db/repository.test.ts | sed -n '1,220p'

Repository: Fundable-Protocol/Backend

Length of output: 3082


🌐 Web query:

TypeORM InsertQueryBuilder orIgnore execute InsertResult affected rows duplicate ignored behavior

💡 Result:

In TypeORM, the .orIgnore() function is used within an InsertQueryBuilder to suppress errors caused by duplicate key conflicts or invalid data during an insertion [1][2]. When executed, this method instructs the database to perform an "insert ignore" operation, such as INSERT IGNORE (MySQL) or ON CONFLICT DO NOTHING (PostgreSQL, SQLite, etc.) [1][3][4][5]. Regarding the behavior of orIgnore() and the returned InsertResult: 1. Effect on Affected Rows: When orIgnore() is used, the affected property in the InsertResult may not accurately reflect the number of rows actually written to the database [6]. Because the database is instructed to skip conflicts rather than throw an error, it may still report the operation as successful, and the ORM might count intended rows rather than rows successfully committed [6]. 2. Limitations: The orIgnore() method applies a blanket "ignore" policy for the entire insert statement [7]. It does not support specifying individual columns or specific constraints to ignore (e.g., ON CONFLICT (col_name) DO NOTHING) [7][8]. It simply maps to the database's native "ignore" or "do nothing" syntax [3][4]. 3. Subscriber Behavior: A known side effect is that TypeORM's lifecycle subscribers (like afterInsert()) may still be triggered for entities that were ultimately ignored by the database [6]. Developers have reported needing to implement manual checks (e.g., verifying if a generated date or ID field is populated) within these subscribers to determine if an entity was truly persisted [6]. In summary, while orIgnore() is effective for suppressing errors during bulk inserts of potentially redundant data, it provides limited granularity and may require cautious handling of returned result data and lifecycle hooks [7][6].

Citations:


recordEventProcessed() still returns true when the row is ignored. .orIgnore("uq_indexed_event_identity") does not target that constraint; it suppresses conflicts broadly, so a duplicate can resolve without writing anything. Return false for the ignored case and add coverage for the duplicate-suppressed path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/common/src/db/repository.ts` around lines 40 - 57, In
recordEventProcessed, the current .orIgnore path can silently skip duplicate
inserts but still returns true, so update the logic to detect the ignored insert
result and return false when no row was written. Use the
repository/createQueryBuilder insert flow in repository.ts to distinguish a
successful insert from a conflict-suppressed no-op, and keep the
unique-violation fallback in the catch block consistent with the duplicate case.
Add test coverage around recordEventProcessed for the duplicate-suppressed path
to verify it returns false when the insert is ignored.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

recordEventProcessed() still returns true when the row is ignored. .orIgnore("uq_indexed_event_identity") does not target that constraint; it suppresses conflicts broadly, so a duplicate can resolve without writing anything. Return false for the ignored case and add coverage for the duplicate-suppressed path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@pragmaticAweds, good catch — I’d make this a small two-part fix.

  • Update indexer/common/src/db/repository.ts so recordEventProcessed() distinguishes a real insert from an orIgnore() no-op and returns false for the ignored duplicate case.
  • Keep the existing unique-violation fallback in the catch block aligned with that same duplicate semantics.
  • Add a focused test in indexer/common/src/db/repository.test.ts for the duplicate-suppressed path.

Failed to handle agent chat message. Please try again.

Comment on lines +6 to +7
export { config, loadConfig } from "./config/index.js";
export { createSorobanClient, sorobanClient } from "./rpc/client.js";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Avoid import-time config/client side effects in the package entrypoint.

These barrel exports now force ./config/index.js and ./rpc/client.js to execute on every @fundable-indexer/common import. Because those modules eagerly call loadConfig() and createSorobanClient(), even consumers that only import unrelated metadata become dependent on valid runtime env and client construction at module-load time. Please keep the barrel side-effect-free by exporting factories/types here and moving the singletons behind an explicit runtime import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/common/src/index.ts` around lines 6 - 7, The package entrypoint is
pulling in eager side effects by re-exporting from config and RPC modules that
call loadConfig() and createSorobanClient() at import time. Update the barrel to
avoid importing those singleton-producing modules directly; instead export only
factories/types from index.ts and move singleton initialization behind an
explicit runtime entrypoint or separate import. Keep the symbols config,
loadConfig, createSorobanClient, and sorobanClient available without forcing
module-load configuration/client construction.

Comment on lines +53 to +60
return (
message.includes("timeout") ||
message.includes("rate limit") ||
message.includes("ECONNRESET") ||
message.includes("503") ||
message.includes("504") ||
message.includes("502")
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Retry classification misses HTTP 429 responses.

withRetry() claims to handle rate limiting, but isTransientError() never matches a plain 429 Too Many Requests error. Those failures will abort polling immediately instead of retrying. Add 429/too many requests handling and normalize message casing before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/common/src/poller/index.ts` around lines 53 - 60, The transient error
check in isTransientError() is missing plain HTTP 429 rate-limit failures, so
polling can stop instead of retrying; update the matching logic to recognize 429
and “too many requests” in addition to the existing timeout/rate limit cases.
Normalize the error message casing before checking, and keep the changes
localized to isTransientError() inside the retry path used by withRetry().

Comment on lines +13 to +16
"totalAmount" bigint NOT NULL,
"startTime" bigint NOT NULL,
"endTime" bigint NOT NULL,
"amountWithdrawn" bigint NOT NULL DEFAULT '0',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files | rg 'indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts|indexer/streams/src/db|uuid-ossp|migration' || true

printf '\n== outline migration ==\n'
ast-grep outline indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts --view expanded || true

printf '\n== relevant snippets ==\n'
sed -n '1,220p' indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts

Repository: Fundable-Protocol/Backend

Length of output: 3165


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search for stream entities and column types ==\n'
rg -n --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  'totalAmount|amountWithdrawn|amount:|uuid_generate_v4|CREATE EXTENSION|uuid-ossp|numeric\(39,0\)|bigint' \
  indexer/streams/src || true

printf '\n== find entity files ==\n'
git ls-files indexer/streams/src | rg 'entity|typeorm|model|schema|migration' || true

Repository: Fundable-Protocol/Backend

Length of output: 2447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search for uuid extension setup ==\n'
rg -n --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  'CREATE EXTENSION|uuid-ossp|uuid_generate_v4\(\)' .

printf '\n== inspect migration/entity types around IDs and amounts ==\n'
cat -n indexer/streams/src/db/entity/Stream.ts
printf '\n---\n'
cat -n indexer/streams/src/db/entity/WithdrawalAction.ts
printf '\n---\n'
cat -n indexer/streams/src/db/entity/CancelAction.ts

Repository: Fundable-Protocol/Backend

Length of output: 4301


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search for stream/token amount domain assumptions ==\n'
rg -n --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  'i128|int128|128-bit|amountWithdrawn|totalAmount|WithdrawalAction|Stream' indexer docs src || true

Repository: Fundable-Protocol/Backend

Length of output: 3264


🌐 Web query:

Soroban TokenInterface amount type i128 docs.rs bigint 64-bit PostgreSQL

💡 Result:

In Soroban smart contracts, the amount type used in the TokenInterface is i128 (a signed 128-bit integer) [1][2][3]. This type is used for balances, allowances, and transfer amounts to provide a wide range of values that accommodate high-precision token amounts (even with 18 decimal places) while supporting negative values if necessary [3]. Regarding the integration of Soroban i128 values into a PostgreSQL database: 1. PostgreSQL Integer Limits: PostgreSQL's bigint type is a signed 8-byte (64-bit) integer [4][5][6]. It has a range of approximately -9.22 quintillion to +9.22 quintillion [4][6]. 2. Compatibility Gap: Because Soroban uses a 128-bit integer (i128) and PostgreSQL's native bigint (int8) is only 64-bit, you cannot store all possible Soroban i128 values directly in a PostgreSQL bigint column without risk of overflow [4][5]. 3. Recommended Storage Strategy: - Numeric/Decimal: The most straightforward way to store 128-bit integers in PostgreSQL is to use the NUMERIC type [4][5]. While potentially slower than fixed-width integers, it supports arbitrary precision and can easily accommodate the range of a 128-bit integer [4][5]. - Application-Level Handling: If you must maintain performance for aggregations and are confident the values will not exceed the 64-bit range, you might store them as bigint; however, this is unsafe for general i128 data [4][7]. - Custom Extensions: There is no native unsigned 64-bit or signed 128-bit integer type in standard PostgreSQL [4][7], meaning the NUMERIC type remains the standard, reliable choice for handling values exceeding the 64-bit signed integer limit [4][5].

Citations:


🌐 Web query:

Soroban TokenInterface transfer amount i128 docs.rs

💡 Result:

In the Soroban SDK, the TokenInterface trait defines the transfer method with the following signature [1][2]: fn transfer(env: Env, from: Address, to: MuxedAddress, amount: i128) The amount parameter is of type i128, which represents the quantity of tokens to be transferred [1][2]. This i128 type is standard for token amounts in Soroban to accommodate the full range of potential token balances while maintaining compatibility with the underlying Stellar ledger's representation [1][3]. When implementing or calling this interface: - The from parameter specifies the source address holding the tokens [1][2]. - The to parameter is a MuxedAddress, which allows for flexible destination handling (e.g., direct transfers to an exchange) [1][4][2]. - The amount must be a non-negative value; contract implementations typically include logic to verify this (e.g., checking that the amount is greater than or equal to zero) before executing the balance update [5][6]. For transfer_from, which consumes an allowance, the signature also uses i128 for the amount [1][2]: fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128) In this case, the spender must have a sufficient allowance established by the from address, and the amount transferred reduces that allowance accordingly [1][2].

Citations:


Use a 128-bit-safe type for token amounts. bigint is only 64-bit, while Soroban token amounts use i128, so totalAmount, amountWithdrawn, and amount can overflow. Update the migration and matching entity columns to numeric (or another 128-bit-safe type) to keep the schema aligned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts` around lines
13 - 16, The stream amount columns are still using 64-bit-safe bigint even
though Soroban token values are i128-sized. Update the initial streams schema
migration and the matching entity definitions for the relevant amount fields in
the streams database model to use a 128-bit-safe type such as numeric, keeping
the names totalAmount, amountWithdrawn, and amount aligned across the migration
and entity classes.

);

CREATE TABLE "stream_withdrawal_action" (
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for UUID extension provisioning or alternative UUID defaults..."
rg -n 'uuid_generate_v4|gen_random_uuid|uuidv4|CREATE EXTENSION|uuid-ossp|pgcrypto' indexer

echo
echo "Searching for datasource / migration config that pins the target database..."
fd -i 'data-source|datasource|ormconfig|package.json|README' indexer

Repository: Fundable-Protocol/Backend

Length of output: 696


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Outline of the migration file:"
ast-grep outline indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts --view expanded || true

echo
echo "Relevant lines from the migration:"
sed -n '1,120p' indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts

echo
echo "Search for database setup / extension provisioning across indexer:"
rg -n 'CREATE EXTENSION|uuid-ossp|pgcrypto|gen_random_uuid|uuid_generate_v4|DataSource|typeorm|migrations' indexer/streams indexer/common indexer -g '!**/node_modules/**'

Repository: Fundable-Protocol/Backend

Length of output: 6273


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Outline of the migration file:"
ast-grep outline indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts --view expanded || true

echo
echo "Relevant lines from the migration:"
sed -n '1,120p' indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts

echo
echo "Search for database setup / extension provisioning across indexer:"
rg -n 'CREATE EXTENSION|uuid-ossp|pgcrypto|gen_random_uuid|uuid_generate_v4|DataSource|typeorm|migrations' indexer/streams indexer/common indexer -g '!**/node_modules/**'

Repository: Fundable-Protocol/Backend

Length of output: 6273


Provision uuid-ossp before using uuid_generate_v4()

stream_withdrawal_action.id and stream_cancel_action.id rely on uuid_generate_v4(), which will fail on a fresh PostgreSQL database unless uuid-ossp is enabled first. Add CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; before these tables.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/streams/src/db/migrations/00001_InitialStreamsSchema.ts` at line 23,
The initial streams schema migration uses uuid_generate_v4() for
stream_withdrawal_action.id and stream_cancel_action.id, but the uuid-ossp
extension is not guaranteed to exist on a fresh database. Update
00001_InitialStreamsSchema.ts to provision the extension first by adding CREATE
EXTENSION IF NOT EXISTS "uuid-ossp"; before the table definitions that depend on
uuid_generate_v4(), keeping the migration order in sync with the
stream_withdrawal_action and stream_cancel_action setup.

"strict": true,
"target": "ES2023",
"types": ["vitest/globals"]
"types": ["vitest/globals"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the shared tsconfig and the configs that extend it.
git ls-files 'indexer/tsconfig.base.json' 'indexer/**/tsconfig*.json' | sort
printf '\n--- indexer/tsconfig.base.json ---\n'
cat -n indexer/tsconfig.base.json

printf '\n--- configs extending tsconfig.base.json ---\n'
rg -n '"extends"\s*:\s*"\.\.?/tsconfig\.base\.json"|"\.\./tsconfig\.base\.json"|"\./tsconfig\.base\.json"' indexer -g 'tsconfig*.json'

Repository: Fundable-Protocol/Backend

Length of output: 1515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'indexer/tsconfig.base.json' 'indexer/**/tsconfig*.json' | sort
printf '\n--- indexer/tsconfig.base.json ---\n'
cat -n indexer/tsconfig.base.json

printf '\n--- configs extending tsconfig.base.json ---\n'
rg -n '"extends"\s*:\s*"\.\.?/tsconfig\.base\.json"|"\.\./tsconfig\.base\.json"|"\./tsconfig\.base\.json"' indexer -g 'tsconfig*.json'

Repository: Fundable-Protocol/Backend

Length of output: 1515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the package tsconfigs for include/exclude or overrides, and look for separate test tsconfigs.
for f in indexer/common/tsconfig.json indexer/distributions/tsconfig.json indexer/streams/tsconfig.json; do
  printf '\n--- %s ---\n' "$f"
  cat -n "$f"
done

printf '\n--- test tsconfig files under indexer ---\n'
fd -a 'tsconfig*.json' indexer

Repository: Fundable-Protocol/Backend

Length of output: 1227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in indexer/common/tsconfig.json indexer/distributions/tsconfig.json indexer/streams/tsconfig.json; do
  printf '\n--- %s ---\n' "$f"
  cat -n "$f"
done

printf '\n--- test tsconfig files under indexer ---\n'
fd -a 'tsconfig*.json' indexer

Repository: Fundable-Protocol/Backend

Length of output: 1227


Move Vitest globals out of the shared base tsconfig.

indexer/tsconfig.base.json:23 applies vitest/globals to every package build (indexer/common, indexer/distributions, indexer/streams), so test globals are available in normal source compilation and package-specific ambient types must be re-added elsewhere. Put this in a dedicated test tsconfig instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/tsconfig.base.json` at line 23, The shared tsconfig is incorrectly
applying Vitest globals to all packages through the base config, so move the
vitest/globals entry out of tsconfig.base.json and into a dedicated test-only
tsconfig. Update the relevant package test configuration to extend the base
settings while keeping normal source builds for indexer/common,
indexer/distributions, and indexer/streams free of test ambient types, and
verify any package-specific ambient types are still declared where needed.

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.

Define Streams database schema Add poller retry and error handling Add Soroban RPC client wrapper Implement indexed event repository

2 participants