feat: generate TypeScript bindings for Soroban smart contracts - #168
Conversation
|
@Josue19-08 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
@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! 🚀 |
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
lib/contracts/project-registry/index.ts (1)
15-19:ContractClientOptionsis 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 typescriptgenerated 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()orserver.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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (8)
.env.examplelib/contracts/bounty-registry/index.tslib/contracts/core-escrow/index.tslib/contracts/index.tslib/contracts/project-registry/index.tslib/contracts/reputation-registry/index.tslib/contracts/transaction.tspackage.json
| 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 |
There was a problem hiding this comment.
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.
| 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.
| 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"); |
There was a problem hiding this comment.
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.
| 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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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.
|
@Josue19-08 |
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
…address element type
970b387 to
30d33a5
Compare
30d33a5 to
c228b8f
Compare
Hey @Benjtalkshow, done |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
.env.examplecomponents/bounty-detail/bounty-detail-sidebar-cta.tsxcomponents/bounty/fee-calculator.tsxhooks/__tests__/use-submission-draft.test.tshooks/use-bounty-mutations.tshooks/use-notifications.tslib/contracts/bounty-registry/index.tslib/contracts/core-escrow/index.tslib/contracts/index.tslib/contracts/project-registry/index.tslib/contracts/reputation-registry/index.tslib/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
| 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), | ||
| ]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the file and verify it exists
find . -name "index.ts" -path "*bounty-registry*" | head -20Repository: 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 dRepository: boundlessfi/bounties
Length of output: 294
🏁 Script executed:
# Search for both methods in the codebase
rg "updateBountyArgs|createBountyArgs" --type ts -B 2 -A 8Repository: 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 10Repository: 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 -10Repository: boundlessfi/bounties
Length of output: 188
🏁 Script executed:
# Look for the contract specification
fd "bounty" -type f | grep -E "\.(ts|rs|wasm)" | head -20Repository: 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 -20Repository: 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 2Repository: 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 -10Repository: 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 -80Repository: 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.
| 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.
| export interface EscrowPool { | ||
| id: string; | ||
| bounty_id: string; | ||
| total_amount: bigint; | ||
| released_amount: bigint; | ||
| status: string; | ||
| token: string; | ||
| } |
There was a problem hiding this comment.
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.
| export interface ReputationEvent { | ||
| address: string; | ||
| delta: bigint; | ||
| reason: string; | ||
| bounty_id?: string; | ||
| timestamp: bigint; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's understand the file structure and locate the target file
find . -name "index.ts" -path "*/reputation-registry/*" | head -20Repository: boundlessfi/bounties
Length of output: 108
🏁 Script executed:
# Also search for files in lib/contracts
fd . lib/contracts 2>/dev/null | head -30Repository: boundlessfi/bounties
Length of output: 397
🏁 Script executed:
# Search for scValToNative usage in the codebase
rg "scValToNative" --type ts --type tsx -A 3 | head -50Repository: 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 -100Repository: 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 -80Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
# Read the full reputation-registry/index.ts file
cat -n lib/contracts/reputation-registry/index.tsRepository: boundlessfi/bounties
Length of output: 4080
🏁 Script executed:
# Search for scValToNative with ts type only
rg "scValToNative" --type ts -B 3 -A 3Repository: boundlessfi/bounties
Length of output: 6677
🏁 Script executed:
# Search for ReputationEvent usage with ts type only
rg "ReputationEvent" --type ts -B 2 -A 2Repository: 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 5Repository: boundlessfi/bounties
Length of output: 7649
🏁 Script executed:
# Look for test files that might test ReputationEvent parsing
fd "test\|spec" --type f | grep -i reputationRepository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
# Search for any example usage or mocking of ReputationEvent
rg "ReputationEvent" --type ts -B 5 -A 5Repository: 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 -20Repository: 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 -20Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
# Search for tests involving ReputationEvent or parseHistory
rg "parseHistory\|ReputationEvent" --type ts -B 3 -A 10Repository: 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 2Repository: 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 -10Repository: 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 5Repository: 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 3Repository: 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.tsRepository: boundlessfi/bounties
Length of output: 7103
🏁 Script executed:
# Check the reputation API to see how data is processed
cat -n lib/api/reputation.tsRepository: boundlessfi/bounties
Length of output: 2141
🏁 Script executed:
# Check the use-reputation hook
cat -n hooks/use-reputation.tsRepository: boundlessfi/bounties
Length of output: 3436
🏁 Script executed:
# Search for ReputationRegistryClient instantiation or usage
rg "ReputationRegistryClient" --type ts -B 3 -A 3Repository: 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 3Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
# Check if there are any actual contract calls being made
rg "contract\." --type ts | head -30Repository: 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/nullRepository: 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 -5Repository: 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 2Repository: 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 -50Repository: 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 10Repository: 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 -10Repository: 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:
- 1: https://stellar.github.io/js-stellar-sdk/global.html?utm_source=openai
- 2: https://github.com/stellar/js-stellar-sdk/releases?utm_source=openai
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.
| 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.
There was a problem hiding this comment.
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.contractIdreads fromNEXT_PUBLIC_PROJECT_REGISTRY_CONTRACT_IDhere, butlib/contracts/index.ts(lines 52-57 per context snippet) also reads the same env var directly when instantiatingprojectRegistry. Consider havinglib/contracts/index.tsimport and usenetworks.testnet.contractIdto 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.
ContractClientOptionsincludesnetworkPassphraseandrpcUrl, but theProjectRegistryClientconstructor only usescontractId. If these fields are intended for external use (e.g., withsimulateContractorbuildTransactionhelpers), 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
parseProjectandparseProjectsmethods useastype 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
components/bounty-detail/bounty-detail-sidebar-cta.tsxcomponents/bounty/fee-calculator.tsxhooks/__tests__/use-submission-draft.test.tshooks/use-bounty-mutations.tshooks/use-notifications.tslib/contracts/project-registry/index.tslib/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
| nativeToScVal(args.maintainers, { | ||
| type: "vec", | ||
| elementType: "address", | ||
| } as unknown as { type: string }), |
There was a problem hiding this comment.
🧩 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:
- 1: https://stellar.github.io/js-stellar-sdk/global.html?utm_source=openai
- 2: https://app.unpkg.com/%40stellar/stellar-base%4014.0.3/files/CHANGELOG.md?utm_source=openai
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.
Description
Adds TypeScript client bindings for all four deployed Soroban contracts and installs
@stellar/stellar-sdkas 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 defaultslib/contracts/bounty-registry/index.ts— typedBountyRegistryClient(create/update/close bounty, apply, get_bounty, list_bounties, get_application)lib/contracts/core-escrow/index.ts— typedCoreEscrowClient(pool_funds, deposit, release_funds, get_pool)lib/contracts/reputation-registry/index.ts— typedReputationRegistryClient(credit_reputation, get_profile, get_history, get_leaderboard)lib/contracts/project-registry/index.ts— typedProjectRegistryClient(create/update_project, add_maintainer, get_project, list_projects)lib/contracts/transaction.ts— three core helpers:simulateContract<T>— read-only RPC simulation with typed returnbuildTransaction— unsigned XDR builder (simulate → assemble → XDR) for passkey signingsubmitTransaction— submits a signed XDR envelope and returns the tx hashlib/contracts/index.ts— pre-configured singleton clients using env vars, re-exports all types and helpersUsage example
Closes
Closes #139
Notes
NEXT_PUBLIC_*env vars are not set..next/andlib/store.test.ts) were present before this PR; no new type errors introduced.Summary by CodeRabbit
New Features
Chores
Bug Fixes / UX
Tests