Skip to content

feat: generate TypeScript bindings for Soroban smart contracts - #168

Merged
0xdevcollins merged 3 commits into
boundlessfi:mainfrom
Josue19-08:feat/soroban-typescript-bindings
Mar 30, 2026
Merged

feat: generate TypeScript bindings for Soroban smart contracts#168
0xdevcollins merged 3 commits into
boundlessfi:mainfrom
Josue19-08:feat/soroban-typescript-bindings

Conversation

@Josue19-08

@Josue19-08 Josue19-08 commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Description

Adds TypeScript client bindings for all four deployed Soroban contracts and installs @stellar/stellar-sdk as a frontend dependency. This enables the frontend to perform read-only on-chain queries and build unsigned transactions for passkey signing, without routing every call through the NestJS backend.

Changes

  • package.json — adds @stellar/stellar-sdk ^14.6.1
  • .env.example — documents all Stellar/Soroban env vars with testnet defaults
  • lib/contracts/bounty-registry/index.ts — typed BountyRegistryClient (create/update/close bounty, apply, get_bounty, list_bounties, get_application)
  • lib/contracts/core-escrow/index.ts — typed CoreEscrowClient (pool_funds, deposit, release_funds, get_pool)
  • lib/contracts/reputation-registry/index.ts — typed ReputationRegistryClient (credit_reputation, get_profile, get_history, get_leaderboard)
  • lib/contracts/project-registry/index.ts — typed ProjectRegistryClient (create/update_project, add_maintainer, get_project, list_projects)
  • lib/contracts/transaction.ts — three core helpers:
    • simulateContract<T> — read-only RPC simulation with typed return
    • buildTransaction — unsigned XDR builder (simulate → assemble → XDR) for passkey signing
    • submitTransaction — submits a signed XDR envelope and returns the tx hash
  • lib/contracts/index.ts — pre-configured singleton clients using env vars, re-exports all types and helpers

Usage example

import { bountyRegistry, simulateContract } from "@/lib/contracts";

// Read-only query
const bounty = await simulateContract<BountyData>(
  bountyRegistry.options.contractId,
  "get_bounty",
  bountyRegistry.getBountyArgs("bounty-id-here"),
  userPublicKey,
);

// Build unsigned XDR for passkey signing
import { buildTransaction } from "@/lib/contracts";

const xdr = await buildTransaction(
  bountyRegistry.options.contractId,
  "create_bounty",
  bountyRegistry.createBountyArgs({ title, description, reward, deadline, tags }),
  userPublicKey,
);

Closes

Closes #139

Notes

  • Contract addresses fall back to the testnet defaults listed in the issue when the corresponding NEXT_PUBLIC_* env vars are not set.
  • All pre-existing TypeScript errors (in .next/ and lib/store.test.ts) were present before this PR; no new type errors introduced.
  • All 41 existing tests pass.

Summary by CodeRabbit

  • New Features

    • Integrated Soroban contract clients for bounties, escrow, reputation, and projects; added transaction helpers to build, simulate, and submit contract invocations.
  • Chores

    • Added a .env.example template with network, RPC, GraphQL, auth, and contract variables.
  • Bug Fixes / UX

    • Minor UI tweaks: simplified loader rendering on bounty CTA and cleaned card imports.
    • Notifications hook: improved immediate sync when switching users.
  • Tests

    • Removed an unused test import.

@vercel

vercel Bot commented Mar 28, 2026

Copy link
Copy Markdown

@Josue19-08 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Mar 28, 2026

Copy link
Copy Markdown

@Josue19-08 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 Mar 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Soroban contract tooling: environment template, TypeScript client bindings for four Soroban contracts, shared contract entrypoint, transaction helpers (simulate/build/submit), and small UI/hook cleanups and test import tweaks.

Changes

Cohort / File(s) Summary
Environment Configuration
\.env\.example
New environment template with core app, Better Auth, DB, GitHub, GraphQL, and Soroban/Stellar RPC, network passphrase, and testnet contract ID placeholders.
Bounty Registry client
lib/contracts/bounty-registry/index.ts
New TypeScript client exposing types (BountyData, ApplicationData, arg interfaces), networks/testnet constant (env override), BountyRegistryClient with arg builders, parsers, and getContract().
Core Escrow client
lib/contracts/core-escrow/index.ts
New TypeScript client with EscrowPool/Deposit types, arg builders for pool/deposit/release, parsers, networks/testnet constant, and CoreEscrowClient.
Project Registry client
lib/contracts/project-registry/index.ts
New TypeScript client with ProjectData and arg builders/parsers, networks/testnet constant, and ProjectRegistryClient.
Reputation Registry client
lib/contracts/reputation-registry/index.ts
New TypeScript client with ReputationProfile/Event types, credit/get args, parsers, networks/testnet constant, and ReputationRegistryClient.
Contracts entry & re-exports
lib/contracts/index.ts
Centralized shared network/rpc resolution, instantiates/export pre-configured clients (bountyRegistry, coreEscrow, reputationRegistry, projectRegistry) and re-exports transaction helpers and client types/classes.
Transaction helpers
lib/contracts/transaction.ts
New helpers: simulateContract, buildTransaction, submitTransaction — server factory, simulation/assembly/submission flows and strict error handling.
Misc UI & hooks
components/bounty-detail/bounty-detail-sidebar-cta.tsx, components/bounty/fee-calculator.tsx, hooks/__tests__/use-submission-draft.test.ts, hooks/use-bounty-mutations.ts, hooks/use-notifications.ts, lib/contracts/reputation-registry.ts
Small UI import/JSX simplifications, test import cleanup, mutation wrapper formatting and cancel wrapper destructuring change (removed reason), notification hydration refactor (sync on render + one-time effect), and removal of initReputationProfile export.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Frontend Component
    participant ClientLib as Contract Client (e.g., BountyRegistryClient)
    participant Transaction as lib/contracts/transaction
    participant RPC as Stellar RPC

    rect rgba(100,150,200,0.5)
        Note over Client,RPC: Read-only (simulateContract) flow
        Client->>ClientLib: getBountyArgs(bountyId)
        ClientLib-->>Client: xdr.ScVal[] args
        Client->>Transaction: simulateContract(contractId, method, args, sourceKey)
        Transaction->>RPC: server.simulateTransaction(tx)
        RPC-->>Transaction: simulation response (retval)
        Transaction->>Client: parsed native result (scValToNative → typed)
        Client->>ClientLib: parseBounty(raw)
        ClientLib-->>Client: typed BountyData
    end

    rect rgba(150,100,200,0.5)
        Note over Client,RPC: Build & Submit flow
        Client->>ClientLib: createBountyArgs(args)
        ClientLib-->>Client: xdr.ScVal[] args
        Client->>Transaction: buildTransaction(contractId, method, args, sourceKey)
        Transaction->>RPC: server.simulateTransaction(tx)
        RPC-->>Transaction: simulation response
        Transaction->>Transaction: rpc.assembleTransaction(tx, simulation)
        Transaction-->>Client: unsigned XDR (base64)
        Client->>Client: Sign XDR (user key)
        Client->>Transaction: submitTransaction(signedXdr)
        Transaction->>RPC: server.sendTransaction(tx)
        RPC-->>Transaction: submission result (hash/status)
        Transaction-->>Client: txHash / error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Suggested Reviewers

  • Benjtalkshow

Poem

🐰 I dug a burrow of TypeScript light,
Four contracts snug for Soroban night,
XDRs tucked in a stellar nest,
Simulate, build, then sign the rest,
Hops of code—onchain dreams take flight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: generating TypeScript bindings for Soroban smart contracts, which is the main objective of the PR.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #139: @stellar/stellar-sdk dependency added, TypeScript bindings generated for all four contracts, contract clients configured with env vars and testnet defaults, transaction helpers (simulateContract, buildTransaction, submitTransaction) implemented, and types properly exported for frontend use.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #139. Minor refactoring (removed unused imports, simplified function signatures in bounty-detail-sidebar-cta.tsx, fee-calculator.tsx, use-bounty-mutations.ts, use-notifications.ts, and use-submission-draft.test.ts) aligns with PR objective and supports the main contract-binding implementation without introducing unrelated scope.

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

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

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
lib/contracts/project-registry/index.ts (1)

15-19: ContractClientOptions is duplicated across contract modules.

This interface is identical in all four contract client files. Consider extracting to a shared types file to maintain DRY principles, though this may be intentional if following the stellar contract bindings typescript generated output pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/project-registry/index.ts` around lines 15 - 19, The
ContractClientOptions interface is duplicated across multiple contract client
modules; extract this shared type into a single exported type (e.g., export
interface ContractClientOptions) in a new shared types file (e.g.,
contracts-types.ts) and update each contract client module (references:
ContractClientOptions in project-registry index and the other three contract
client files) to import that type instead of redeclaring it; ensure the new
module exports the type and replace local declarations with the imported type to
keep typings consistent.
lib/contracts/transaction.ts (1)

43-78: Consider adding network error context for debugging.

The function correctly handles simulation errors but network failures from server.getAccount() or server.simulateTransaction() will propagate as raw errors without context. Wrapping these in try-catch could provide better debugging information.

💡 Optional: Add error wrapping for network calls
 export async function simulateContract<T>(
   contractId: string,
   method: string,
   args: xdr.ScVal[],
   sourcePublicKey: string,
 ): Promise<T> {
   const server = getServer();
-  const accountData = await server.getAccount(sourcePublicKey);
+  let accountData;
+  try {
+    accountData = await server.getAccount(sourcePublicKey);
+  } catch (err) {
+    throw new Error(`Failed to fetch account ${sourcePublicKey}: ${err}`);
+  }
   const account = new Account(accountData.accountId(), accountData.sequenceNumber());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/transaction.ts` around lines 43 - 78, Wrap the network calls
inside simulateContract (notably server.getAccount(sourcePublicKey) and
server.simulateTransaction(tx)) in a try/catch and rethrow errors with
additional context; catch any thrown error, and throw a new Error that includes
which operation failed (e.g., "failed to fetch account for sourcePublicKey" or
"failed to simulate transaction for contractId/method") along with the original
error message/details so debugging shows network context while preserving the
original information.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Line 25: The NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE value contains spaces and
a semicolon which can break .env parsing; update the .env.example by wrapping
the NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE value in double quotes (e.g.,
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE="...") so loaders treat it as a single
string.

In `@lib/contracts/project-registry/index.ts`:
- Around line 72-79: The createProjectArgs conversion currently serializes
maintainers as a generic vec of strings; update the CreateProjectArgs type to
reflect address elements (consistent with AddMaintainerArgs) and change the
conversion in createProjectArgs to pass an elementType of "address" to
nativeToScVal (i.e., call nativeToScVal(args.maintainers, { type: "vec",
elementType: "address" })), ensuring maintainers are typed as the contract's
address type and serialized the same way as in addMaintainerArgs.

In `@lib/contracts/transaction.ts`:
- Around line 126-138: The submitTransaction function currently only treats
sent.status === "ERROR" as failure; update submitTransaction to explicitly
handle sent.status === "TRY_AGAIN_LATER" (and consider sent.status ===
"PENDING") by surfacing an error to the caller instead of returning sent.hash;
when TRY_AGAIN_LATER occurs, throw an Error that includes the status and any
available sent.errorResult XDR/details (similar to the existing ERROR branch) so
callers can retry or handle backoff. Identify the logic in submitTransaction and
adjust the status check to cover these cases and include the status and
resultXdr in the thrown error message.
- Around line 110-117: The code calls server.simulateTransaction(tx) and only
checks rpc.Api.isSimulationError(simulation) before assembling; add a check for
rpc.Api.isSimulationSuccess(simulation) and handle non-success (e.g., a
"restore" response) by throwing or returning an appropriate error instead of
proceeding to rpc.assembleTransaction(tx, simulation).build(); update the logic
around simulateTransaction, rpc.Api.isSimulationError,
rpc.Api.isSimulationSuccess and rpc.assembleTransaction to only call
assembleTransaction when isSimulationSuccess(simulation) is true, and surface a
clear error when the simulation is not successful.

---

Nitpick comments:
In `@lib/contracts/project-registry/index.ts`:
- Around line 15-19: The ContractClientOptions interface is duplicated across
multiple contract client modules; extract this shared type into a single
exported type (e.g., export interface ContractClientOptions) in a new shared
types file (e.g., contracts-types.ts) and update each contract client module
(references: ContractClientOptions in project-registry index and the other three
contract client files) to import that type instead of redeclaring it; ensure the
new module exports the type and replace local declarations with the imported
type to keep typings consistent.

In `@lib/contracts/transaction.ts`:
- Around line 43-78: Wrap the network calls inside simulateContract (notably
server.getAccount(sourcePublicKey) and server.simulateTransaction(tx)) in a
try/catch and rethrow errors with additional context; catch any thrown error,
and throw a new Error that includes which operation failed (e.g., "failed to
fetch account for sourcePublicKey" or "failed to simulate transaction for
contractId/method") along with the original error message/details so debugging
shows network context while preserving the original information.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ceae0ee9-81c4-4cba-8515-6c31dcf87bc2

📥 Commits

Reviewing files that changed from the base of the PR and between 3aad250 and c6a62b0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .env.example
  • lib/contracts/bounty-registry/index.ts
  • lib/contracts/core-escrow/index.ts
  • lib/contracts/index.ts
  • lib/contracts/project-registry/index.ts
  • lib/contracts/reputation-registry/index.ts
  • lib/contracts/transaction.ts
  • package.json

Comment thread .env.example
NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org

# Network passphrase — "Test SDF Network ; September 2015" for testnet
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Quote the network passphrase to prevent parsing issues.

The passphrase contains spaces and a semicolon, which can cause parsing issues with some .env loaders. Wrap the value in double quotes.

🔧 Proposed fix
-NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
+NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 25-25: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example at line 25, The NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE value
contains spaces and a semicolon which can break .env parsing; update the
.env.example by wrapping the NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE value in
double quotes (e.g., NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE="...") so loaders
treat it as a single string.

Comment thread lib/contracts/project-registry/index.ts
Comment on lines +110 to +117
const simulation = await server.simulateTransaction(tx);

if (rpc.Api.isSimulationError(simulation)) {
throw new Error(`Transaction simulation error: ${simulation.error}`);
}

const assembled = rpc.assembleTransaction(tx, simulation).build();
return assembled.toEnvelope().toXDR("base64");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add simulation success check before assembling transaction.

Unlike simulateContract, this function only checks for isSimulationError but doesn't verify isSimulationSuccess. The simulation could return a "restore" response for expired ledger state, which requires different handling.

🔧 Proposed fix
   const simulation = await server.simulateTransaction(tx);
 
   if (rpc.Api.isSimulationError(simulation)) {
     throw new Error(`Transaction simulation error: ${simulation.error}`);
   }
 
+  if (!rpc.Api.isSimulationSuccess(simulation)) {
+    throw new Error("Transaction simulation did not succeed");
+  }
+
   const assembled = rpc.assembleTransaction(tx, simulation).build();
   return assembled.toEnvelope().toXDR("base64");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const simulation = await server.simulateTransaction(tx);
if (rpc.Api.isSimulationError(simulation)) {
throw new Error(`Transaction simulation error: ${simulation.error}`);
}
const assembled = rpc.assembleTransaction(tx, simulation).build();
return assembled.toEnvelope().toXDR("base64");
const simulation = await server.simulateTransaction(tx);
if (rpc.Api.isSimulationError(simulation)) {
throw new Error(`Transaction simulation error: ${simulation.error}`);
}
if (!rpc.Api.isSimulationSuccess(simulation)) {
throw new Error("Transaction simulation did not succeed");
}
const assembled = rpc.assembleTransaction(tx, simulation).build();
return assembled.toEnvelope().toXDR("base64");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/transaction.ts` around lines 110 - 117, The code calls
server.simulateTransaction(tx) and only checks
rpc.Api.isSimulationError(simulation) before assembling; add a check for
rpc.Api.isSimulationSuccess(simulation) and handle non-success (e.g., a
"restore" response) by throwing or returning an appropriate error instead of
proceeding to rpc.assembleTransaction(tx, simulation).build(); update the logic
around simulateTransaction, rpc.Api.isSimulationError,
rpc.Api.isSimulationSuccess and rpc.assembleTransaction to only call
assembleTransaction when isSimulationSuccess(simulation) is true, and surface a
clear error when the simulation is not successful.

Comment on lines +126 to +138
export async function submitTransaction(signedXdr: string): Promise<string> {
const server = getServer();

const tx = TransactionBuilder.fromXDR(signedXdr, NETWORK_PASSPHRASE);
const sent = await server.sendTransaction(tx);

if (sent.status === "ERROR") {
const resultXdr = sent.errorResult?.toXDR("base64") ?? "unknown";
throw new Error(`Transaction rejected by network. Result XDR: ${resultXdr}`);
}

return sent.hash;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle TRY_AGAIN_LATER status from sendTransaction.

The sendTransaction RPC method can return status: "ERROR", "PENDING", or "TRY_AGAIN_LATER". Currently only ERROR is handled; TRY_AGAIN_LATER would silently return a hash that may not be valid. Consider surfacing this to the caller.

🔧 Proposed fix
   const sent = await server.sendTransaction(tx);
 
   if (sent.status === "ERROR") {
     const resultXdr = sent.errorResult?.toXDR("base64") ?? "unknown";
     throw new Error(`Transaction rejected by network. Result XDR: ${resultXdr}`);
   }
 
+  if (sent.status === "TRY_AGAIN_LATER") {
+    throw new Error("Network congested. Please try again later.");
+  }
+
   return sent.hash;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function submitTransaction(signedXdr: string): Promise<string> {
const server = getServer();
const tx = TransactionBuilder.fromXDR(signedXdr, NETWORK_PASSPHRASE);
const sent = await server.sendTransaction(tx);
if (sent.status === "ERROR") {
const resultXdr = sent.errorResult?.toXDR("base64") ?? "unknown";
throw new Error(`Transaction rejected by network. Result XDR: ${resultXdr}`);
}
return sent.hash;
}
export async function submitTransaction(signedXdr: string): Promise<string> {
const server = getServer();
const tx = TransactionBuilder.fromXDR(signedXdr, NETWORK_PASSPHRASE);
const sent = await server.sendTransaction(tx);
if (sent.status === "ERROR") {
const resultXdr = sent.errorResult?.toXDR("base64") ?? "unknown";
throw new Error(`Transaction rejected by network. Result XDR: ${resultXdr}`);
}
if (sent.status === "TRY_AGAIN_LATER") {
throw new Error("Network congested. Please try again later.");
}
return sent.hash;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/transaction.ts` around lines 126 - 138, The submitTransaction
function currently only treats sent.status === "ERROR" as failure; update
submitTransaction to explicitly handle sent.status === "TRY_AGAIN_LATER" (and
consider sent.status === "PENDING") by surfacing an error to the caller instead
of returning sent.hash; when TRY_AGAIN_LATER occurs, throw an Error that
includes the status and any available sent.errorResult XDR/details (similar to
the existing ERROR branch) so callers can retry or handle backoff. Identify the
logic in submitTransaction and adjust the status check to cover these cases and
include the status and resultXdr in the thrown error message.

@Benjtalkshow

Benjtalkshow commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

@Josue19-08
Also fix CI failures and conflict

Install @stellar/stellar-sdk and add typed client bindings for all four
Soroban contracts (Bounty Registry, Core Escrow, Reputation Registry,
Project Registry). Provide simulateContract, buildTransaction, and
submitTransaction helpers to support read-only queries and unsigned XDR
generation for passkey signing. Contract addresses are env-configurable
with testnet defaults.

Closes boundlessfi#139
@Josue19-08
Josue19-08 force-pushed the feat/soroban-typescript-bindings branch 2 times, most recently from 970b387 to 30d33a5 Compare March 30, 2026 15:18
@Josue19-08
Josue19-08 force-pushed the feat/soroban-typescript-bindings branch from 30d33a5 to c228b8f Compare March 30, 2026 15:23
@Josue19-08

Copy link
Copy Markdown
Contributor Author

@Josue19-08 Also fix CI failures and conflict

Hey @Benjtalkshow, done

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/contracts/bounty-registry/index.ts`:
- Around line 104-111: The updateBountyArgs implementation is missing explicit
numeric type hints for reward and deadline causing nativeToScVal to infer
incorrect integer sizes; update updateBountyArgs so the reward argument calls
nativeToScVal(args.reward ?? null, { type: "i128" }) and the deadline argument
calls nativeToScVal(args.deadline ?? null, { type: "u64" }) (matching
createBountyArgs), preserving the existing null/optional handling and keeping
other fields unchanged.

In `@lib/contracts/core-escrow/index.ts`:
- Around line 21-28: The exported EscrowPool interface in
lib/contracts/core-escrow/index.ts is a contract/DTO (snake_case fields and
bigint amounts) that clashes with the UI/domain EscrowPool type; rename this
contract type (e.g., EscrowPoolDTO or EscrowPoolContract) and update any
internal references, or keep the name but do not re-export it from the public
barrel—alternatively add an explicit mapper function (e.g.,
mapEscrowPoolFromContract) that converts the contract shape (id, bounty_id,
total_amount: bigint, released_amount: bigint, status, token) into the domain
shape (poolId, totalAmount, releasedAmount, etc.) and export only the mapper and
the domain type for consumers so they cannot accidentally import the raw DTO.

In `@lib/contracts/reputation-registry/index.ts`:
- Around line 31-37: The ReputationEvent interface's bounty_id currently types
as optional string but scValToNative() (and parseHistory()) produces null for
absent values; update the type of bounty_id on ReputationEvent to allow null
(e.g., bounty_id?: string | null) so runtime shape matches the type used by
parseHistory() and scValToNative(); change the declaration in the
ReputationEvent interface accordingly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 42477a18-199e-4e58-821e-fd4c525b5ec2

📥 Commits

Reviewing files that changed from the base of the PR and between 1a132be and 30d33a5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • .env.example
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty/fee-calculator.tsx
  • hooks/__tests__/use-submission-draft.test.ts
  • hooks/use-bounty-mutations.ts
  • hooks/use-notifications.ts
  • lib/contracts/bounty-registry/index.ts
  • lib/contracts/core-escrow/index.ts
  • lib/contracts/index.ts
  • lib/contracts/project-registry/index.ts
  • lib/contracts/reputation-registry/index.ts
  • lib/contracts/transaction.ts
✅ Files skipped from review due to trivial changes (3)
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty/fee-calculator.tsx
  • hooks/tests/use-submission-draft.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/contracts/transaction.ts
  • lib/contracts/project-registry/index.ts

Comment on lines +104 to +111
updateBountyArgs(args: UpdateBountyArgs): xdr.ScVal[] {
return [
nativeToScVal(args.bounty_id, { type: "string" }),
nativeToScVal(args.title ?? null),
nativeToScVal(args.description ?? null),
nativeToScVal(args.reward ?? null),
nativeToScVal(args.deadline ?? null),
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the file and verify it exists
find . -name "index.ts" -path "*bounty-registry*" | head -20

Repository: boundlessfi/bounties

Length of output: 104


🏁 Script executed:

# Get the file size and read the relevant sections
wc -l lib/contracts/bounty-registry/index.ts 2>/dev/null || echo "File not found, searching..."
fd "bounty-registry" -type d

Repository: boundlessfi/bounties

Length of output: 294


🏁 Script executed:

# Search for both methods in the codebase
rg "updateBountyArgs|createBountyArgs" --type ts -B 2 -A 8

Repository: boundlessfi/bounties

Length of output: 1702


🏁 Script executed:

# Look at the contract interface to understand expected types
rg "UpdateBountyArgs|CreateBountyArgs" --type-add "ts:*.ts" -t ts -B 3 -A 10

Repository: boundlessfi/bounties

Length of output: 5026


🏁 Script executed:

# Check if there's a contract spec or interface file
find . -name "*.ts" -o -name "*.rs" | xargs grep -l "update_bounty\|UpdateBounty" | head -10

Repository: boundlessfi/bounties

Length of output: 188


🏁 Script executed:

# Look for the contract specification
fd "bounty" -type f | grep -E "\.(ts|rs|wasm)" | head -20

Repository: boundlessfi/bounties

Length of output: 234


🏁 Script executed:

# Look for contract spec files or wasm files
find . -name "*bounty*" -type f | grep -E "\.(wasm|wat|txt|json)$" | head -20

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Check for any contract interface definitions or comments
grep -r "i128\|u64" lib/contracts/bounty-registry/ -B 2 -A 2

Repository: boundlessfi/bounties

Length of output: 570


🏁 Script executed:

# Look at the full updateBountyArgs and createBountyArgs implementations with context
cat -n lib/contracts/bounty-registry/index.ts | sed -n '85,120p'

Repository: boundlessfi/bounties

Length of output: 1335


🏁 Script executed:

# Check if there's any contract specification or type definitions
find . -name "*.rs" -o -name "*.json" | xargs grep -l "update_bounty\|reward\|deadline" 2>/dev/null | head -10

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Look for any tests that might show the expected behavior
grep -r "updateBountyArgs\|update_bounty" . --include="*.ts" -B 3 -A 5 | head -80

Repository: boundlessfi/bounties

Length of output: 838


🏁 Script executed:

# Check the UpdateBountyArgs interface definition
cat -n lib/contracts/bounty-registry/index.ts | sed -n '1,80p' | grep -A 15 "UpdateBountyArgs"

Repository: boundlessfi/bounties

Length of output: 473


Add explicit numeric type hints to updateBountyArgs to match createBountyArgs.

Lines 109–110 lack type hints for reward and deadline. Without explicit types, nativeToScVal() infers the smallest fitting integer representation—likely u64/i64 for reward instead of the i128 used in createBountyArgs, risking contract rejections on otherwise valid updates. Use the same { type: "i128" } and { type: "u64" } as in createBountyArgs, with conditional null handling since these fields are optional.

🔧 Minimal fix
  updateBountyArgs(args: UpdateBountyArgs): xdr.ScVal[] {
    return [
      nativeToScVal(args.bounty_id, { type: "string" }),
      nativeToScVal(args.title ?? null),
      nativeToScVal(args.description ?? null),
-     nativeToScVal(args.reward ?? null),
-     nativeToScVal(args.deadline ?? null),
+     args.reward === undefined
+       ? nativeToScVal(null)
+       : nativeToScVal(args.reward, { type: "i128" }),
+     args.deadline === undefined
+       ? nativeToScVal(null)
+       : nativeToScVal(args.deadline, { type: "u64" }),
    ];
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updateBountyArgs(args: UpdateBountyArgs): xdr.ScVal[] {
return [
nativeToScVal(args.bounty_id, { type: "string" }),
nativeToScVal(args.title ?? null),
nativeToScVal(args.description ?? null),
nativeToScVal(args.reward ?? null),
nativeToScVal(args.deadline ?? null),
];
updateBountyArgs(args: UpdateBountyArgs): xdr.ScVal[] {
return [
nativeToScVal(args.bounty_id, { type: "string" }),
nativeToScVal(args.title ?? null),
nativeToScVal(args.description ?? null),
args.reward === undefined
? nativeToScVal(null)
: nativeToScVal(args.reward, { type: "i128" }),
args.deadline === undefined
? nativeToScVal(null)
: nativeToScVal(args.deadline, { type: "u64" }),
];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/bounty-registry/index.ts` around lines 104 - 111, The
updateBountyArgs implementation is missing explicit numeric type hints for
reward and deadline causing nativeToScVal to infer incorrect integer sizes;
update updateBountyArgs so the reward argument calls nativeToScVal(args.reward
?? null, { type: "i128" }) and the deadline argument calls
nativeToScVal(args.deadline ?? null, { type: "u64" }) (matching
createBountyArgs), preserving the existing null/optional handling and keeping
other fields unchanged.

Comment on lines +21 to +28
export interface EscrowPool {
id: string;
bounty_id: string;
total_amount: bigint;
released_amount: bigint;
status: string;
token: string;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't expose this under the app's existing EscrowPool name.

types/escrow.ts:16-24 already uses EscrowPool for the UI/domain shape (poolId, totalAmount, releasedAmount, etc.). This contract DTO is snake_case with bigint amounts, and the public barrel re-exports it, so consumers can easily import the wrong type and then fail to pass parsed pools through the existing escrow flow. Please rename the contract DTO or add an explicit mapper before exporting it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/core-escrow/index.ts` around lines 21 - 28, The exported
EscrowPool interface in lib/contracts/core-escrow/index.ts is a contract/DTO
(snake_case fields and bigint amounts) that clashes with the UI/domain
EscrowPool type; rename this contract type (e.g., EscrowPoolDTO or
EscrowPoolContract) and update any internal references, or keep the name but do
not re-export it from the public barrel—alternatively add an explicit mapper
function (e.g., mapEscrowPoolFromContract) that converts the contract shape (id,
bounty_id, total_amount: bigint, released_amount: bigint, status, token) into
the domain shape (poolId, totalAmount, releasedAmount, etc.) and export only the
mapper and the domain type for consumers so they cannot accidentally import the
raw DTO.

Comment on lines +31 to +37
export interface ReputationEvent {
address: string;
delta: bigint;
reason: string;
bounty_id?: string;
timestamp: bigint;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the file structure and locate the target file
find . -name "index.ts" -path "*/reputation-registry/*" | head -20

Repository: boundlessfi/bounties

Length of output: 108


🏁 Script executed:

# Also search for files in lib/contracts
fd . lib/contracts 2>/dev/null | head -30

Repository: boundlessfi/bounties

Length of output: 397


🏁 Script executed:

# Search for scValToNative usage in the codebase
rg "scValToNative" --type ts --type tsx -A 3 | head -50

Repository: boundlessfi/bounties

Length of output: 91


🏁 Script executed:

# Search for ReputationEvent usage and assignments
rg "ReputationEvent" --type ts --type tsx -B 2 -A 2 | head -100

Repository: boundlessfi/bounties

Length of output: 91


🏁 Script executed:

# Search for bounty_id assignments specifically
rg "bounty_id" --type ts --type tsx -B 2 -A 2 | head -80

Repository: boundlessfi/bounties

Length of output: 91


🏁 Script executed:

# Read the full reputation-registry/index.ts file
cat -n lib/contracts/reputation-registry/index.ts

Repository: boundlessfi/bounties

Length of output: 4080


🏁 Script executed:

# Search for scValToNative with ts type only
rg "scValToNative" --type ts -B 3 -A 3

Repository: boundlessfi/bounties

Length of output: 6677


🏁 Script executed:

# Search for ReputationEvent usage with ts type only
rg "ReputationEvent" --type ts -B 2 -A 2

Repository: boundlessfi/bounties

Length of output: 1069


🏁 Script executed:

# Search for tests or usage of ReputationEvent with bounty_id
rg "bounty_id" --type ts -B 5 -A 5

Repository: boundlessfi/bounties

Length of output: 7649


🏁 Script executed:

# Look for test files that might test ReputationEvent parsing
fd "test\|spec" --type f | grep -i reputation

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Search for any example usage or mocking of ReputationEvent
rg "ReputationEvent" --type ts -B 5 -A 5

Repository: boundlessfi/bounties

Length of output: 2377


🏁 Script executed:

# Check package.json for stellar SDK version
cat package.json | grep -A 5 -B 5 "stellar"

Repository: boundlessfi/bounties

Length of output: 961


🏁 Script executed:

# Search for any test files that might show scValToNative behavior
find . -name "*.test.*" -o -name "*.spec.*" | head -20

Repository: boundlessfi/bounties

Length of output: 1143


🏁 Script executed:

# Look for node_modules stellar SDK if available to check actual behavior
ls -la node_modules/@stellar/stellar-sdk 2>/dev/null | head -20

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Search for tests involving ReputationEvent or parseHistory
rg "parseHistory\|ReputationEvent" --type ts -B 3 -A 10

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Check if there's any data showing null handling in tests
rg "bounty_id.*null\|null.*bounty_id" --type ts -B 2 -A 2

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Look at the stellar SDK types/behavior - check if there's any type definition file
find . -name "*.d.ts" -path "*stellar*" 2>/dev/null | head -10

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Search for any usage of parseHistory method across the codebase
rg "parseHistory" --type ts -B 5 -A 5

Repository: boundlessfi/bounties

Length of output: 956


🏁 Script executed:

# Search for how ReputationEvent is used in the actual application code
rg "ReputationEvent\[" --type ts -B 3 -A 3

Repository: boundlessfi/bounties

Length of output: 760


🏁 Script executed:

# Check for reputation-related hooks or services
fd "reputation" --type f | grep -E "\.(ts|tsx)$"

Repository: boundlessfi/bounties

Length of output: 333


🏁 Script executed:

# Check the reputation service to see how parseHistory is used
cat -n lib/services/reputation.ts

Repository: boundlessfi/bounties

Length of output: 7103


🏁 Script executed:

# Check the reputation API to see how data is processed
cat -n lib/api/reputation.ts

Repository: boundlessfi/bounties

Length of output: 2141


🏁 Script executed:

# Check the use-reputation hook
cat -n hooks/use-reputation.ts

Repository: boundlessfi/bounties

Length of output: 3436


🏁 Script executed:

# Search for ReputationRegistryClient instantiation or usage
rg "ReputationRegistryClient" --type ts -B 3 -A 3

Repository: boundlessfi/bounties

Length of output: 1782


🏁 Script executed:

# Look for getHistory or get_history calls on the contract
rg "getHistory\|get_history" --type ts -B 3 -A 3

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Check if there are any actual contract calls being made
rg "contract\." --type ts | head -30

Repository: boundlessfi/bounties

Length of output: 904


🏁 Script executed:

# Check if there are actual test files using the contract clients
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "ReputationRegistryClient\|parseHistory" 2>/dev/null

Repository: boundlessfi/bounties

Length of output: 48


🏁 Script executed:

# Look for any Soroban or smart contract test data
find . -type f -name "*.json" | xargs grep -l "bounty_id" 2>/dev/null | head -5

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Verify the behavior claim by checking similar optional fields in other contract interfaces
rg "nativeToScVal.*\?\?" --type ts -B 2 -A 2

Repository: boundlessfi/bounties

Length of output: 2475


🏁 Script executed:

# Let's check the exact TypeScript types for optional fields in other contract response types
rg "export interface" --type ts -A 10 | grep -A 10 "reputation-registry\|bounty-registry" | head -50

Repository: boundlessfi/bounties

Length of output: 2827


🏁 Script executed:

# Verify the pattern: check if all optional fields in response types are marked with ?
cat -n lib/contracts/bounty-registry/index.ts | sed -n '20,100p'

Repository: boundlessfi/bounties

Length of output: 2418


🏁 Script executed:

# Check what types are returned from parseHistory
rg "parseHistory\|parse.*Event\|ReputationEvent\[\]" --type ts -B 5 -A 10

Repository: boundlessfi/bounties

Length of output: 46


🏁 Script executed:

# Look at the stellar SDK in package.json to understand the version
cat package.json | grep -A 2 -B 2 "@stellar/stellar-sdk"

Repository: boundlessfi/bounties

Length of output: 269


🏁 Script executed:

# Search for any type definition or documentation in node_modules about scValToNative
find . -name "*.d.ts" -o -name "*.ts" | xargs grep -l "scValToNative" 2>/dev/null | grep -v "node_modules" | head -10

Repository: boundlessfi/bounties

Length of output: 259


🌐 Web query:

stellar SDK scValToNative null void behavior Soroban JavaScript

💡 Result:

In the Soroban JavaScript SDK (@stellar/stellar-sdk / @stellar/stellar-base), scValToNative() converts Soroban “void” (SCV_VOID) to JavaScript null (not undefined). It’s explicitly documented as void -> null. [1]

Related behavior change: the JS SDK release notes state that Spec.scValToNative returns null for voids or Option “none” values to avoid the ambiguity of undefined. [2]

The inverse is also supported: nativeToScVal(null) produces an scvVoid (SCV_VOID). [1]

Sources:
[1] scValToNative / nativeToScVal docs (stellar.github.io/js-stellar-sdk/global.html) (stellar.github.io)
[2] js-stellar-sdk release notes mentioning the null behavior change (github.com)

Citations:


Ensure bounty_id type reflects runtime shape from scValToNative() parsing.

When the contract returns void for an absent bounty_id, scValToNative() converts it to null. The current type bounty_id?: string only accounts for undefined | string, but runtime consumers will receive null from parseHistory(). Update to bounty_id?: string | null to match the actual parsed shape.

🔧 Minimal fix
 export interface ReputationEvent {
   address: string;
   delta: bigint;
   reason: string;
-  bounty_id?: string;
+  bounty_id?: string | null;
   timestamp: bigint;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ReputationEvent {
address: string;
delta: bigint;
reason: string;
bounty_id?: string;
timestamp: bigint;
}
export interface ReputationEvent {
address: string;
delta: bigint;
reason: string;
bounty_id?: string | null;
timestamp: bigint;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/reputation-registry/index.ts` around lines 31 - 37, The
ReputationEvent interface's bounty_id currently types as optional string but
scValToNative() (and parseHistory()) produces null for absent values; update the
type of bounty_id on ReputationEvent to allow null (e.g., bounty_id?: string |
null) so runtime shape matches the type used by parseHistory() and
scValToNative(); change the declaration in the ReputationEvent interface
accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
lib/contracts/project-registry/index.ts (3)

51-58: Centralize contract address resolution to avoid duplication.

The networks.testnet.contractId reads from NEXT_PUBLIC_PROJECT_REGISTRY_CONTRACT_ID here, but lib/contracts/index.ts (lines 52-57 per context snippet) also reads the same env var directly when instantiating projectRegistry. Consider having lib/contracts/index.ts import and use networks.testnet.contractId to ensure a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/project-registry/index.ts` around lines 51 - 58, The project
registry contract ID is being read from the env var in two places; update the
code that constructs projectRegistry (the instantiation code in the module that
currently reads process.env.NEXT_PUBLIC_PROJECT_REGISTRY_CONTRACT_ID) to import
and use networks.testnet.contractId from the exported networks constant in
project-registry (the `networks` object) so there is a single source of truth;
replace the direct env var access in the projectRegistry creation path with
networks.testnet.contractId and ensure the import references the same exported
symbol (`networks`) so future changes use the centralized value.

15-19: Consider removing unused options or documenting their external use.

ContractClientOptions includes networkPassphrase and rpcUrl, but the ProjectRegistryClient constructor only uses contractId. If these fields are intended for external use (e.g., with simulateContract or buildTransaction helpers), consider adding a brief doc comment clarifying this. Otherwise, they add interface clutter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/project-registry/index.ts` around lines 15 - 19, The
ContractClientOptions interface exposes networkPassphrase and rpcUrl but
ProjectRegistryClient's constructor only uses contractId; either remove these
unused fields to avoid clutter or add a brief doc comment on
ContractClientOptions stating that networkPassphrase and rpcUrl are provided for
external helpers (e.g., simulateContract, buildTransaction) and not consumed by
ProjectRegistryClient, referencing the ContractClientOptions type and
ProjectRegistryClient constructor so reviewers can locate the change.

124-131: Consider adding runtime validation or documenting the trust assumption.

The parseProject and parseProjects methods use as type assertions without runtime validation. If the contract returns an unexpected structure (e.g., due to contract version mismatch), errors will propagate to callers. This is acceptable if the contract schema is guaranteed, but consider either:

  • Adding a brief doc comment noting the assumption that the contract returns the expected schema
  • Adding optional runtime validation (e.g., with zod) for defensive coding
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/project-registry/index.ts` around lines 124 - 131, The
parseProject and parseProjects functions currently cast scValToNative(raw) to
ProjectData/ProjectData[] without runtime checks, which can fail if the contract
returns an unexpected shape; either add a short doc comment on parseProject and
parseProjects stating the trust assumption that the contract returns the exact
ProjectData schema, or add optional runtime validation (e.g. using zod or a
small type guard) after calling scValToNative(raw) to verify and throw a clear
error if the shape is wrong; reference the functions parseProject,
parseProjects, the helper scValToNative, and the ProjectData type when making
the change so reviewers can locate the spots to add the doc or validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/contracts/project-registry/index.ts`:
- Around line 77-80: Replace the incorrect elementType assertion used when
calling nativeToScVal with the SDK's proper type specification: remove the cast
and the elementType property on the vector and instead provide the correct type
for vector elements (e.g., use type: ["address"] or a vector type that specifies
"address" uniformly) when converting args.maintainers via nativeToScVal; update
the call site referencing nativeToScVal and args.maintainers to use the
SDK-supported type form rather than the as unknown as { type: string }
workaround.

---

Nitpick comments:
In `@lib/contracts/project-registry/index.ts`:
- Around line 51-58: The project registry contract ID is being read from the env
var in two places; update the code that constructs projectRegistry (the
instantiation code in the module that currently reads
process.env.NEXT_PUBLIC_PROJECT_REGISTRY_CONTRACT_ID) to import and use
networks.testnet.contractId from the exported networks constant in
project-registry (the `networks` object) so there is a single source of truth;
replace the direct env var access in the projectRegistry creation path with
networks.testnet.contractId and ensure the import references the same exported
symbol (`networks`) so future changes use the centralized value.
- Around line 15-19: The ContractClientOptions interface exposes
networkPassphrase and rpcUrl but ProjectRegistryClient's constructor only uses
contractId; either remove these unused fields to avoid clutter or add a brief
doc comment on ContractClientOptions stating that networkPassphrase and rpcUrl
are provided for external helpers (e.g., simulateContract, buildTransaction) and
not consumed by ProjectRegistryClient, referencing the ContractClientOptions
type and ProjectRegistryClient constructor so reviewers can locate the change.
- Around line 124-131: The parseProject and parseProjects functions currently
cast scValToNative(raw) to ProjectData/ProjectData[] without runtime checks,
which can fail if the contract returns an unexpected shape; either add a short
doc comment on parseProject and parseProjects stating the trust assumption that
the contract returns the exact ProjectData schema, or add optional runtime
validation (e.g. using zod or a small type guard) after calling
scValToNative(raw) to verify and throw a clear error if the shape is wrong;
reference the functions parseProject, parseProjects, the helper scValToNative,
and the ProjectData type when making the change so reviewers can locate the
spots to add the doc or validation.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e7a95a8b-39a3-435a-a6fb-8d56158a3e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 30d33a5 and c228b8f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty/fee-calculator.tsx
  • hooks/__tests__/use-submission-draft.test.ts
  • hooks/use-bounty-mutations.ts
  • hooks/use-notifications.ts
  • lib/contracts/project-registry/index.ts
  • lib/contracts/reputation-registry.ts
💤 Files with no reviewable changes (1)
  • lib/contracts/reputation-registry.ts
✅ Files skipped from review due to trivial changes (3)
  • hooks/tests/use-submission-draft.test.ts
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty/fee-calculator.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/use-bounty-mutations.ts

Comment on lines +77 to +80
nativeToScVal(args.maintainers, {
type: "vec",
elementType: "address",
} as unknown as { type: string }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does @stellar/stellar-sdk nativeToScVal support elementType option in TypeScript types version 14?

💡 Result:

No. In the v14 TypeScript typings for nativeToScVal (re-exported by @stellar/stellar-sdk from @stellar/stellar-base), the options object is driven by opts.type (including type as an array to control per-element types for vectors), and there is no elementType option in the public API/types. The v14 changelog/docs describe using type (and type: [...] for arrays), not elementType. [1] [2]

Use instead (v14):

  • nativeToScVal([1, 2, 3], { type: 'i128' }) (uniform element type)
  • nativeToScVal([1, 'x'], { type: ['i128', 'symbol'] }) (per-element typing) [2]

Sources
[1] Stellar JS SDK docs (nativeToScVal uses opts.type): (stellar.github.io)
[2] @stellar/stellar-base v14 changelog (arrays with differing types via type: [...]): (app.unpkg.com)

Citations:


Replace the type assertion with the correct SDK API for element typing.

The elementType option does not exist in @stellar/stellar-sdk v14. The type assertion as unknown as { type: string } masks this API misuse. Use the correct approach instead:

nativeToScVal(args.maintainers, {
  type: ["address"],
})

Or for a uniform type across all elements, use type: "address" directly within the vector type specification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/contracts/project-registry/index.ts` around lines 77 - 80, Replace the
incorrect elementType assertion used when calling nativeToScVal with the SDK's
proper type specification: remove the cast and the elementType property on the
vector and instead provide the correct type for vector elements (e.g., use type:
["address"] or a vector type that specifies "address" uniformly) when converting
args.maintainers via nativeToScVal; update the call site referencing
nativeToScVal and args.maintainers to use the SDK-supported type form rather
than the as unknown as { type: string } workaround.

@0xdevcollins
0xdevcollins merged commit 20effde into boundlessfi:main Mar 30, 2026
2 of 3 checks passed
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.

Generate TypeScript Bindings for Soroban Smart Contracts

3 participants