feat: implement Stellar contract interaction methods in StellarService#429
Conversation
- Add typed methods for all stable contract calls (getOwner, resolveUsername, getChainAddress, etc.) - Implement comprehensive error handling with StellarRpcException - Add complete TypeScript interfaces for all return types in stellar.types.ts - Create comprehensive test suite with mocked RPC responses - Fix package.json and tsconfig.json configuration issues - Ensure no any types used throughout implementation Resolves contract interaction requirements for backend integration
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis pull request introduces foundational infrastructure for Stellar contract interactions by adding a dedicated Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~5 minutes Suggested labels
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. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
|
please review. |
|
@Mmesolove 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! 🚀 |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/stellar/stellar.service.ts (1)
32-32:⚠️ Potential issue | 🟡 Minor
error.messageaccess on implicitanyin catch handlers.In
tsconfigstrict mode,catch (error)makeserror: unknown, anderror.messagewon't compile. The repeated pattern${error.message}(Lines 32, 83, 116, 155, 193, 243, 280, 317) relies onanytyping. Narrow witherror instanceof Error ? error.message : String(error)or a small helper to stay aligned with the PR's "noany" goal.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/stellar/stellar.service.ts` at line 32, The catch blocks in StellarService that call this.logger.error with `${error.message}` use an implicit any; update those logger calls (the ones inside the StellarService class where you currently do this.logger.error(`Failed to connect to Stellar RPC: ${error.message}`)` and the similar error logs repeated elsewhere) to first narrow the caught value to a string safely—either inline with `error instanceof Error ? error.message : String(error)` or by adding a small helper like `formatError(error)` that returns `Error` messages or stringifies otherwise—and use that result in the log message so compilation under strict mode succeeds and no any is introduced.
🧹 Nitpick comments (3)
backend/src/stellar/stellar.service.ts (3)
56-325: Significant duplication across the seven RPC read methods — extract a helper.Each method follows the same skeleton: get contract → simulate → check
result.error→ checkretval→ parse XDR → checkscvVoid→ decode → wrap errors asStellarRpcException. The transaction-wrapping fix (see earlier comment) and thescvVoidcomparison fix would both benefit from being centralized. Suggest extracting:private async callContract<T>( contract: Contract, method: string, args: xdr.ScVal[], decode: (val: xdr.ScVal) => T, errorContext: { contractId: string; logPrefix: string }, ): Promise<T | null> { ... }This roughly halves the file size, makes the seven methods one-liners, and means future fixes (e.g., switching to
scValToNative) happen in one place.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/stellar/stellar.service.ts` around lines 56 - 325, Extract a common helper (e.g., private async callContract<T>(contract: Contract, method: string, args: any[], decode: (val: xdr.ScVal)=>T, errorContext: {contractId:string; logPrefix:string}): Promise<T|null>) to centralize the repeated pattern used by getOwner, resolveUsername, getChainAddress, getVaultBalance, getScheduledPayment, isVaultActive and getCreatedAt: call this.getCoreContract()/getEscrowContract, invoke this.server.simulateTransaction(contract.call(method, ...args)), check result.error and result.result?.retval, parse with xdr.ScVal.fromXDR, detect scvVoid correctly, then run the supplied decode() to produce T (or return null), and on catch log with errorContext.logPrefix and rethrow a StellarRpcException constructed with errorContext.contractId and the method name; replace each RPC method body with a single call to callContract passing the appropriate contract, method string, args, decode lambda and errorContext.
77-79:parsed.switch() === xdr.ScValType.scvVoid()relies on enum-singleton identity — prefer comparing.valueor.name.In
js-xdr(used by@stellar/stellar-sdk), enum members are typically singletons, so reference equality usually works. However, this isn't part of any documented contract; it's safer and more obviously correct to compare the underlying numeric tag. The same pattern is repeated at Lines 147, 186, 224, 274, and 311.- if (parsed.switch() === xdr.ScValType.scvVoid()) { + if (parsed.switch().value === xdr.ScValType.scvVoid().value) { return null; }Or use
scValToNative(parsed)from@stellar/stellar-sdkand check forundefined/null, which avoids manual switch-case parsing entirely and handles all the type conversions (i128 → bigint, u64 → bigint, address → string, etc.) below.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/stellar/stellar.service.ts` around lines 77 - 79, The checks like parsed.switch() === xdr.ScValType.scvVoid() in stellar.service.ts are relying on enum-singleton identity; change them to compare the underlying tag (e.g., parsed.switch().value === xdr.ScValType.scvVoid().value or .name) or — preferably — replace the manual switch/parsing logic and use scValToNative(parsed) from `@stellar/stellar-sdk` and test for undefined/null to handle all types safely; update all occurrences (the parsed.switch() checks at the places noted and the surrounding conversion logic) to use the tag comparison or scValToNative to avoid fragile reference-equality checks.
96-124:resolveUsernamelacks a timeout/cancellation safeguard.This is a blocking external RPC call from a request thread. If
simulateTransactionhangs (network partition, slow RPC), the NestJS request will hang with it. Given theBAD_GATEWAYmapping inStellarRpcExceptionalready implies upstream-failure semantics, consider wrappingsimulateTransactioncalls withAbortController/ aPromise.racetimeout, or configurerpc.Serverwith HTTP timeouts. Same applies to all seven methods.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/stellar/stellar.service.ts` around lines 96 - 124, The resolveUsername method calls this.server.simulateTransaction (in resolveUsername) without any timeout/cancellation, so a hung RPC will block the request thread; wrap the simulateTransaction call with a cancellable timeout (e.g., use AbortController if the rpc.Server supports AbortSignal or implement a Promise.race with a configurable timeout) and ensure you abort/cleanup the controller on completion; surface a clear timeout error into the existing catch so StellarRpcException still contains context (coreContractId, 'resolve_stellar'), and apply the same pattern to the other six methods that call this.server.simulateTransaction to ensure all RPC calls have consistent timeout/cancellation handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/package.json`:
- Line 21: The "test:unit" npm script currently filters tests with
--testPathPattern=keeper which omits the new Stellar tests; update package.json
by either removing the --testPathPattern to run the full test suite, or change
it to a broader pattern such as --testPathPattern="keeper|stellar" (or include
'src/stellar') so backend/src/stellar/stellar.service.spec.ts is executed, or
alternatively delete the "test:unit" script and rely on the existing
"test"/"test:cov" targets.
In `@backend/src/stellar/stellar.exceptions.ts`:
- Around line 13-22: The current Stellar RPC exception passes originalError and
contractId into the response body (the object passed to super), which can leak
internal details; change the payload sent to super to contain only a sanitized
message and generic error code (e.g., message: 'Upstream service error', error:
'Stellar RPC Error'), and move the raw originalError and contractId onto the
exception instance for logging only (store them in properties like
this.originalError and this.contractId or similar) so they are not serialized in
the HTTP response; update the class in stellar.exceptions.ts where super(...) is
called to implement this separation and ensure logging routines read the
internal fields instead of the response payload.
- Around line 9-23: Change the public constructor parameter originalError?: any
to originalError?: unknown in the Stellar exception class (the constructor that
assigns originalError and passes originalError?.message into the super payload)
and update any places that read error.message in stellar.service.ts catch blocks
to first narrow the unknown (e.g., check typeof error === 'object' && error !==
null && 'message' in error) before accessing message; keep the payload behavior
the same by extracting the message when present and otherwise using the error
value.
In `@backend/src/stellar/stellar.service.spec.ts`:
- Around line 91-198: Tests currently allow any value to be passed into
mockServer.simulateTransaction, hiding the real bug where
Contract.prototype.call returns an Operation but simulateTransaction expects a
built Transaction/FeeBumpTransaction; update the tests (those exercising
getOwner, resolveUsername, getChainAddress) to assert the simulated RPC receives
a Transaction instance by checking mockServer.simulateTransaction was called
with expect.any(Transaction) or by capturing the argument and asserting it is an
instance of Transaction or FeeBumpTransaction (referencing mockContract.call and
mockServer.simulateTransaction and the service methods
getOwner/resolveUsername/getChainAddress) so the test suite will fail if
contract.call() is not wrapped into a real Transaction before
simulateTransaction is invoked.
- Around line 55-57: Tests currently call jest.spyOn on xdr.ScVal.fromXDR,
xdr.ScValType.scvVoid, and Address.fromScVal but only use jest.clearAllMocks()
in afterEach, which leaves spy implementations in place; change the cleanup to
call jest.restoreAllMocks() in the afterEach (or add jest.restoreAllMocks()
alongside clearAllMocks) so all jest.spyOn mocks are restored between tests and
no mock implementations leak across tests.
In `@backend/src/stellar/stellar.service.ts`:
- Line 237: The code coerces u64 Hyper values to Number (see release_at
assignment and getCreatedAt) which will silently lose precision for values >
Number.MAX_SAFE_INTEGER; update these sites to preserve full precision by
returning a bigint or string instead of Number, adjust the return
types/interfaces and any callers that consume release_at/getCreatedAt to accept
bigint|string, and/or add a clear comment/docstring stating the timestamp is a
Unix seconds value and that Number truncation is acceptable if you decide to
keep it.
- Around line 228-241: The current decoding in getScheduledPayment uses
parsed.instance().instanceValue().map() and positional indexing which is
incorrect for a Soroban struct return; instead use parsed.map() to get the
xdr.ScMapEntry[] and extract fields by their string keys (e.g.
"from","to","token","amount","release_at","executed") rather than by index so
decoding is alphabetical-order independent, then convert values with the same
helpers you already use (Address.fromScVal, .i128(), .u64(), .b(),
Buffer.from(...).toString('hex')); alternatively replace the manual decode with
scValToNative(parsed) and then map that native object into the ScheduledPayment
shape.
- Line 67: The throw currently uses new Error(result.error) which stringifies
structured SimulateTransactionResponse.error to "[object Object]"; replace it
with new Error(typeof result.error === 'string' ? result.error :
JSON.stringify(result.error)) wherever you construct an Error from result.error
(e.g., in the simulate/submit handling blocks that reference result and
SimulateTransactionResponse), and apply the same change to the other occurrences
that build an Error from result.error so the full structured error is preserved
in the thrown message.
- Line 63: The contract.call(...) invocations (get_owner, resolve_stellar,
get_chain_address, get_balance, get_scheduled_payment, is_vault_active,
get_created_at) are passing raw JS values; convert arguments to ScVal instances:
for hex 32-byte values (commitment, usernameHash) do Buffer.from(hex, 'hex')
then wrap with StellarSdk.xdr.ScVal.scvBytes(...); for paymentId use
StellarSdk.xdr.ScVal.scvU32(paymentId); for chain use the ScVal representation
the contract expects (e.g., scvU32(enumIndex) or scvSymbol(enumName) depending
on the contract ABI); update each contract.call(...) call site to pass these
ScVal-wrapped args (follow the pattern used in soroban.service.ts: convert hex →
Buffer → scvBytes, numbers → scvU32, enums → appropriate ScVal) so on-chain
types match.
- Around line 151-153: The current code decodes parsed.bytes() as UTF-8 which
corrupts non-text addresses; update the address conversion in the function that
handles parsed.bytes() (in stellar.service.ts) to branch on the provided chain:
ChainType parameter and use chain-specific encodings — for EVM return '0x' +
Buffer.from(bytes).toString('hex'), for Bitcoin use base58 encoding, for Cosmos
use bech32 encoding (and other chains as required); replace the existing
Buffer.from(bytes).toString('utf8') path with this branching logic and ensure
the function name handling parsed.bytes() uses the ChainType argument to choose
the correct encoder.
- Around line 62-64: The current calls pass an xdr.Operation to
this.server.simulateTransaction but simulateTransaction expects a built
Transaction; create a helper method (e.g., simulateContractCall(operation:
xdr.Operation)) that: imports TransactionBuilder, BASE_FEE and Account from
'@stellar/stellar-sdk', constructs a throwaway source Account with sequence '0',
builds a TransactionBuilder using the service's ConfigService.networkPassphrase
and BASE_FEE, .addOperation(operation).setTimeout(30).build(), and then calls
this.server.simulateTransaction(tx); replace direct
simulateTransaction(contract.call(...)) calls in getOwner, resolveUsername,
getChainAddress, getVaultBalance, getScheduledPayment, isVaultActive, and
getCreatedAt to use this new helper so all operations are wrapped into a proper
Transaction before simulation.
In `@backend/src/stellar/stellar.types.ts`:
- Around line 5-15: Change the string-valued enums ChainType and PrivacyMode to
numeric (C-like) enums so they emit integer discriminants matching the Rust
contract; update the definitions of ChainType and PrivacyMode (the enum
declarations) to assign explicit numeric values (0,1,2,3 for ChainType and 0,1
for PrivacyMode) instead of string literals so the values sent to the contract
are u32 discriminants rather than strings.
---
Outside diff comments:
In `@backend/src/stellar/stellar.service.ts`:
- Line 32: The catch blocks in StellarService that call this.logger.error with
`${error.message}` use an implicit any; update those logger calls (the ones
inside the StellarService class where you currently do this.logger.error(`Failed
to connect to Stellar RPC: ${error.message}`)` and the similar error logs
repeated elsewhere) to first narrow the caught value to a string safely—either
inline with `error instanceof Error ? error.message : String(error)` or by
adding a small helper like `formatError(error)` that returns `Error` messages or
stringifies otherwise—and use that result in the log message so compilation
under strict mode succeeds and no any is introduced.
---
Nitpick comments:
In `@backend/src/stellar/stellar.service.ts`:
- Around line 56-325: Extract a common helper (e.g., private async
callContract<T>(contract: Contract, method: string, args: any[], decode: (val:
xdr.ScVal)=>T, errorContext: {contractId:string; logPrefix:string}):
Promise<T|null>) to centralize the repeated pattern used by getOwner,
resolveUsername, getChainAddress, getVaultBalance, getScheduledPayment,
isVaultActive and getCreatedAt: call this.getCoreContract()/getEscrowContract,
invoke this.server.simulateTransaction(contract.call(method, ...args)), check
result.error and result.result?.retval, parse with xdr.ScVal.fromXDR, detect
scvVoid correctly, then run the supplied decode() to produce T (or return null),
and on catch log with errorContext.logPrefix and rethrow a StellarRpcException
constructed with errorContext.contractId and the method name; replace each RPC
method body with a single call to callContract passing the appropriate contract,
method string, args, decode lambda and errorContext.
- Around line 77-79: The checks like parsed.switch() === xdr.ScValType.scvVoid()
in stellar.service.ts are relying on enum-singleton identity; change them to
compare the underlying tag (e.g., parsed.switch().value ===
xdr.ScValType.scvVoid().value or .name) or — preferably — replace the manual
switch/parsing logic and use scValToNative(parsed) from `@stellar/stellar-sdk` and
test for undefined/null to handle all types safely; update all occurrences (the
parsed.switch() checks at the places noted and the surrounding conversion logic)
to use the tag comparison or scValToNative to avoid fragile reference-equality
checks.
- Around line 96-124: The resolveUsername method calls
this.server.simulateTransaction (in resolveUsername) without any
timeout/cancellation, so a hung RPC will block the request thread; wrap the
simulateTransaction call with a cancellable timeout (e.g., use AbortController
if the rpc.Server supports AbortSignal or implement a Promise.race with a
configurable timeout) and ensure you abort/cleanup the controller on completion;
surface a clear timeout error into the existing catch so StellarRpcException
still contains context (coreContractId, 'resolve_stellar'), and apply the same
pattern to the other six methods that call this.server.simulateTransaction to
ensure all RPC calls have consistent timeout/cancellation handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 98ea4b3b-e438-43ee-876b-0a86f74d3771
📒 Files selected for processing (6)
backend/package.jsonbackend/src/stellar/stellar.exceptions.tsbackend/src/stellar/stellar.service.spec.tsbackend/src/stellar/stellar.service.tsbackend/src/stellar/stellar.types.tsbackend/tsconfig.json
💤 Files with no reviewable changes (1)
- backend/tsconfig.json
| ); | ||
|
|
||
| if (result.error) { | ||
| throw new Error(result.error); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@stellar/stellar-sdk 11 SimulateTransactionResponse error field type
💡 Result:
In @stellar/stellar-sdk version 11 (and later versions), the SimulateTransactionResponse interface includes an optional 'error' field that captures simulation failure details when present. This field is only populated if the transaction simulation failed; successful simulations omit it and provide fields like transactionData, minResourceFee, results, etc. instead. The exact TypeScript type of the 'error' field is a structured object containing details about the failure reason, as defined in the Soroban RPC specification. Official docs describe it as: "(optional) - This field will include details about why the invoke host function call failed. Only present if the transaction simulation failed." (Stellar Developers documentation). In SDK code examples, it is accessed as sim.error, and utility functions like isSimulationSuccess check if error is absent to determine success. The SDK parses raw RPC responses into this typed interface via parseRawSimulation, ensuring XDR fields are decoded. For version-specific details in v11, the structure aligns with Soroban RPC spec, where error provides diagnostic info (e.g., contract errors, resource limits) even with partial context like events.
Citations:
- 1: https://developers.stellar.org/docs/data/apis/rpc/api-reference/methods/simulateTransaction
- 2: https://developers.stellar.org/network/soroban-rpc/api-reference/methods/simulateTransaction
- 3: https://github.com/stellar/js-stellar-sdk/blob/2f52d0e7/src/rpc/server.ts
- 4: https://www.ccxt.com
🏁 Script executed:
cat -n backend/src/stellar/stellar.service.ts | head -80Repository: Alien-Protocol/Alien-Protocol
Length of output: 2908
🏁 Script executed:
# Search for SimulateTransactionResponse type definition
rg "SimulateTransactionResponse" --type ts -A 5 -B 2Repository: Alien-Protocol/Alien-Protocol
Length of output: 55
🏁 Script executed:
# Check imports and type definitions in stellar.service.ts
head -50 backend/src/stellar/stellar.service.tsRepository: Alien-Protocol/Alien-Protocol
Length of output: 1576
🏁 Script executed:
# Check all lines mentioned in the review
rg "throw new Error\(result\.error\)" --type ts -B 2 -A 2Repository: Alien-Protocol/Alien-Protocol
Length of output: 1981
🏁 Script executed:
# Look at the entire file to find all instances
wc -l backend/src/stellar/stellar.service.tsRepository: Alien-Protocol/Alien-Protocol
Length of output: 116
🏁 Script executed:
# Get the specific lines mentioned
sed -n '60,70p' backend/src/stellar/stellar.service.ts
sed -n '100,110p' backend/src/stellar/stellar.service.ts
sed -n '133,143p' backend/src/stellar/stellar.service.tsRepository: Alien-Protocol/Alien-Protocol
Length of output: 912
🏁 Script executed:
# Search for package.json to determine SDK version
cat -n backend/package.json | grep -A 5 -B 5 "stellar-sdk"Repository: Alien-Protocol/Alien-Protocol
Length of output: 512
🏁 Script executed:
# Check for type definition files or stellar SDK imports
find node_modules/@stellar/stellar-sdk -name "*.d.ts" -type f 2>/dev/null | head -20Repository: Alien-Protocol/Alien-Protocol
Length of output: 55
🏁 Script executed:
# Search in the codebase for how error is accessed/used from result
rg "result\.error" --type ts -B 2 -A 2Repository: Alien-Protocol/Alien-Protocol
Length of output: 2317
🏁 Script executed:
# Check if there are TypeScript definitions we can inspect
ls -la node_modules/@stellar/stellar-sdk/ 2>/dev/null | head -20Repository: Alien-Protocol/Alien-Protocol
Length of output: 55
🏁 Script executed:
# Check if there's any TypeScript interface definition in comments or types
rg "SimulateTransactionResponse|type.*error" --type ts -B 3 -A 3Repository: Alien-Protocol/Alien-Protocol
Length of output: 1231
🏁 Script executed:
# Look at the stellar SDK source to understand the error type
# Since node_modules may not have the source, let's check what we can infer from usage
cat -n backend/src/stellar/stellar.service.ts | sed -n '50,330p'Repository: Alien-Protocol/Alien-Protocol
Length of output: 10017
Improve error message by stringifying result.error objects.
The error field in SimulateTransactionResponse is a structured object, not a string. Passing it directly to new Error() results in Error: [object Object], losing error details. Use new Error(typeof result.error === 'string' ? result.error : JSON.stringify(result.error)) instead. This pattern is repeated at lines 104, 137, 176, 214, 264, 301.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/src/stellar/stellar.service.ts` at line 67, The throw currently uses
new Error(result.error) which stringifies structured
SimulateTransactionResponse.error to "[object Object]"; replace it with new
Error(typeof result.error === 'string' ? result.error :
JSON.stringify(result.error)) wherever you construct an Error from result.error
(e.g., in the simulate/submit handling blocks that reference result and
SimulateTransactionResponse), and apply the same change to the other occurrences
that build an Error from result.error so the full structured error is preserved
in the thrown message.
| // Convert bytes to string | ||
| const bytes = parsed.bytes(); | ||
| return Buffer.from(bytes).toString('utf8'); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
soroban username service get_chain_address EVM bytes representation hex
💡 Result:
No official or widely documented "Soroban Username Service" or specific function "get_chain_address" was found in authoritative sources like Stellar/Soroban documentation. Soroban Name Service (SNS) exists as community projects, such as the one by developer tosinshada (GitHub discussion from 2023, result 19), with contracts in sns-registry, sns-registrar, and sns-resolver folders. These allow registering names associated with Soroban addresses (Stellar account or contract IDs, 32-byte XDR-serialized, often represented as 56/64-char hex strings prefixed "C" for contracts). Soroban addresses are opaque 32-byte identifiers. Their "bytes representation" is the raw 32 bytes. In hex, typically the full XDR AccountContractId (discriminant + 32 bytes), e.g., contract addresses are 64 hex chars. EVM addresses are 20-byte hex (40 chars, "0x" prefixed). No evidence of built-in "get_chain_address" returning EVM-specific bytes/hex in Soroban name services. If it exists in a custom contract, it likely retrieves a stored chain address (e.g., EVM) as BytesN<20> or Bytes, output as base64 or hex via SDK/RPC. To invoke: Use soroban-contract-sdk to call the function on the resolver contract ID, passing username as Symbol or String. Example Rust client: use soroban_client::rpc::Server; use soroban_client::Address; let server = Server::new(&rpc_url, &network); let contract = Address::from_string(&contract_id)?; // SNS resolver contract ID let username = Symbol::new(&env, "example"); // or String let invoke = soroban_client::InvokeBuilder::new(&server, &contract, "get_chain_address", (&username,)); let response = invoke.call?; The response would be SCVal::Bytes(bytes), convertible to hex: hex::encode(bytes.to_vec). No exact function signature or EVM-specific handling confirmed; likely custom to store/retrieve cross-chain addresses. Check specific SNS contract source for details (e.g., tosinshada's repo).
Citations:
- 1: https://devpost.com/software/soroban-name-service
- 2: Building a Name Resolution Service in Soroban stellar/sorobounty-spectacular#19
- 3: https://docs.rs/soroban-sdk/latest/soroban_sdk/struct.Address.html
- 4: https://hexdocs.pm/soroban/api-reference.html
- 5: https://hexdocs.pm/soroban/0.11.2/Soroban.Contract.html
🏁 Script executed:
# First, let's examine the file structure and find the specific file
fd -t f "stellar.service.ts" backend/Repository: Alien-Protocol/Alien-Protocol
Length of output: 112
🏁 Script executed:
# Now let's look at the specific lines and surrounding context
head -160 backend/src/stellar/stellar.service.ts | tail -30Repository: Alien-Protocol/Alien-Protocol
Length of output: 1024
🏁 Script executed:
# Let's search for ChainType enum definition
rg "enum ChainType" -A 10Repository: Alien-Protocol/Alien-Protocol
Length of output: 1865
🏁 Script executed:
# Search for the unit test mentioned in the comment
fd -t f "stellar.service.spec.ts" -o "stellar.test.ts" -o "*.spec.ts" backend/ | head -5Repository: Alien-Protocol/Alien-Protocol
Length of output: 223
🏁 Script executed:
# Look for test files that might test this functionality
fd -t f "*.spec.ts" backend/ | rg -i stellarRepository: Alien-Protocol/Alien-Protocol
Length of output: 362
🏁 Script executed:
# Find test files properly
find backend -name "*.spec.ts" -type fRepository: Alien-Protocol/Alien-Protocol
Length of output: 245
🏁 Script executed:
# Get more context around the problematic code - the full function
sed -n '130,170p' backend/src/stellar/stellar.service.tsRepository: Alien-Protocol/Alien-Protocol
Length of output: 1260
🏁 Script executed:
# Search for how this function is called and if chain is available
rg "get_chain_address" -B 5 -A 15 backend/src/stellar/stellar.service.tsRepository: Alien-Protocol/Alien-Protocol
Length of output: 1363
🏁 Script executed:
# Look at the stellar service spec file
cat backend/src/stellar/stellar.service.spec.tsRepository: Alien-Protocol/Alien-Protocol
Length of output: 14915
🏁 Script executed:
# Also check what GetChainAddressResult type is
rg "GetChainAddressResult" -A 5 backend/Repository: Alien-Protocol/Alien-Protocol
Length of output: 1430
🏁 Script executed:
# Look for the actual Soroban contract code to understand what it returns
find onchain -name "*.rs" -type f | xargs rg "get_chain_address" -A 10 2>/dev/null | head -50Repository: Alien-Protocol/Alien-Protocol
Length of output: 3826
Replace UTF-8 decoding with chain-specific address encoding.
EVM addresses are 20 raw bytes; Bitcoin and Cosmos addresses are also binary data, not UTF-8 text. Decoding raw bytes as UTF-8 will corrupt addresses with high bytes (≥ 0x80). The unit test passes only because it mocks an ASCII hex string rather than binary data.
For EVM, use hex encoding: '0x' + Buffer.from(bytes).toString('hex'). Other chains require different encodings (base58, bech32). Branch on the available chain: ChainType parameter:
Suggested fix
- // Convert bytes to string
- const bytes = parsed.bytes();
- return Buffer.from(bytes).toString('utf8');
+ const bytes = Buffer.from(parsed.bytes());
+ switch (chain) {
+ case ChainType.Evm:
+ return '0x' + bytes.toString('hex');
+ // TODO: confirm representation for Bitcoin/Solana/Cosmos
+ default:
+ return bytes.toString('hex');
+ }📝 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.
| // Convert bytes to string | |
| const bytes = parsed.bytes(); | |
| return Buffer.from(bytes).toString('utf8'); | |
| const bytes = Buffer.from(parsed.bytes()); | |
| switch (chain) { | |
| case ChainType.Evm: | |
| return '0x' + bytes.toString('hex'); | |
| // TODO: confirm representation for Bitcoin/Solana/Cosmos | |
| default: | |
| return bytes.toString('hex'); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/src/stellar/stellar.service.ts` around lines 151 - 153, The current
code decodes parsed.bytes() as UTF-8 which corrupts non-text addresses; update
the address conversion in the function that handles parsed.bytes() (in
stellar.service.ts) to branch on the provided chain: ChainType parameter and use
chain-specific encodings — for EVM return '0x' +
Buffer.from(bytes).toString('hex'), for Bitcoin use base58 encoding, for Cosmos
use bech32 encoding (and other chains as required); replace the existing
Buffer.from(bytes).toString('utf8') path with this branching logic and ensure
the function name handling parsed.bytes() uses the ChainType argument to choose
the correct encoder.
| // Parse the ScheduledPayment struct | ||
| const instance = parsed.instance(); | ||
| const fields = instance.instanceValue().map(); | ||
|
|
||
| const payment: ScheduledPayment = { | ||
| from: Buffer.from(fields[0].val().bytes()).toString('hex'), | ||
| to: Buffer.from(fields[1].val().bytes()).toString('hex'), | ||
| token: Address.fromScVal(fields[2].val()).toString(), | ||
| amount: fields[3].val().i128().toString(), | ||
| release_at: Number(fields[4].val().u64()), | ||
| executed: fields[5].val().b(), | ||
| }; | ||
|
|
||
| return payment; |
There was a problem hiding this comment.
Two correctness bugs in getScheduledPayment: wrong XDR API and order-dependent field indexing.
-
parsed.instance().instanceValue().map()(Line 229-230) is the API forScContractInstance(the storage map of a deployed contract), not for a struct return value. A Soroban struct return is encoded asScValType.scvMap, so the correct accessor isparsed.map(), which returns an array ofxdr.ScMapEntry { key, val }. -
Indexing the resulting entries by position (
fields[0] … fields[5]) assumes the contract emits fields in the declared Rust order (from, to, token, amount, release_at, executed). Soroban canonicalizes struct maps with alphabetically sorted keys, so on the wire you'll receive:amount, executed, from, release_at, to, token. Index-based access will silently mis-bind every field —fromwill be parsed as the i128amount, etc. The added unit test mocks the array in declaration order, so it doesn't catch this.
Recommended fix is to look up by key Symbol so the code is order-independent:
🛠️ Suggested fix
- // Parse the ScheduledPayment struct
- const instance = parsed.instance();
- const fields = instance.instanceValue().map();
-
- const payment: ScheduledPayment = {
- from: Buffer.from(fields[0].val().bytes()).toString('hex'),
- to: Buffer.from(fields[1].val().bytes()).toString('hex'),
- token: Address.fromScVal(fields[2].val()).toString(),
- amount: fields[3].val().i128().toString(),
- release_at: Number(fields[4].val().u64()),
- executed: fields[5].val().b(),
- };
+ // Parse the ScheduledPayment struct (ScMap of ScSymbol -> ScVal)
+ const entries = parsed.map() ?? [];
+ const byKey = new Map<string, xdr.ScVal>(
+ entries.map((e) => [e.key().sym().toString(), e.val()]),
+ );
+ const need = (k: string): xdr.ScVal => {
+ const v = byKey.get(k);
+ if (!v) throw new Error(`ScheduledPayment missing field "${k}"`);
+ return v;
+ };
+
+ const payment: ScheduledPayment = {
+ from: Buffer.from(need('from').bytes()).toString('hex'),
+ to: Buffer.from(need('to').bytes()).toString('hex'),
+ token: Address.fromScVal(need('token')).toString(),
+ amount: need('amount').i128().toString(),
+ release_at: Number(need('release_at').u64()),
+ executed: need('executed').b(),
+ };Or, much simpler, drop the manual decoding in favor of scValToNative(parsed) and shape the result into ScheduledPayment.
soroban struct scvMap key sort canonical encoding alphabetical
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/src/stellar/stellar.service.ts` around lines 228 - 241, The current
decoding in getScheduledPayment uses parsed.instance().instanceValue().map() and
positional indexing which is incorrect for a Soroban struct return; instead use
parsed.map() to get the xdr.ScMapEntry[] and extract fields by their string keys
(e.g. "from","to","token","amount","release_at","executed") rather than by index
so decoding is alphabetical-order independent, then convert values with the same
helpers you already use (Address.fromScVal, .i128(), .u64(), .b(),
Buffer.from(...).toString('hex')); alternatively replace the manual decode with
scValToNative(parsed) and then map that native object into the ScheduledPayment
shape.
| export enum ChainType { | ||
| Evm = 'Evm', | ||
| Bitcoin = 'Bitcoin', | ||
| Solana = 'Solana', | ||
| Cosmos = 'Cosmos', | ||
| } | ||
|
|
||
| export enum PrivacyMode { | ||
| Normal = 'Normal', | ||
| Shielded = 'Shielded', | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Soroban contract enum string variant ScVal encoding js-stellar-sdk Contract.call argument
💡 Result:
In Soroban contracts, enums defined as user-defined types (UDT) via #[contracttype] are encoded as ScVal values for contract function arguments. There are two main kinds: 1. C-like integer enums (SC_SPEC_ENTRY_UDT_ENUM_V0): Encoded as a simple SCV_U32 containing the integer value corresponding to the variant (e.g., Red=1, Green=2). 2. Named/tuple variants (unnamed enums or unions): Encoded as a SCV_VEC where the first element is an SCV_SYMBOL with the variant name (string), followed by any tuple fields as additional ScVal elements. For js-stellar-sdk Contract.call(method, ...args), pass native JS values (strings for symbols, numbers, arrays for vecs, objects for maps). The SDK's nativeToScVal (or spec-aware conversion if using contract specs) automatically converts: - For integer enum: pass the number directly, e.g., contract.call("func", 1) - For named variant without fields: pass ["variant_name"] (array with string), converts to vec[symbol] - With fields: pass ["variant_name", field1, field2, ...] Example for enum Color { Red, Green(u64) }: - Red: contract.call("set_color", ["Red"]) - Green(42): contract.call("set_color", ["Green", 42n]) // 42n as bigint for u64 Strings in symbols/variants must use valid Soroban symbol chars ([0-9a-zA-Z_], <=10 chars for small symbols). Sources confirm via SEP-0048 (result 5, first search), Rust SDK examples (result 29), JS SDK Contract.call docs (result 10), nativeToScVal handling arrays as vec (result 22), and contract_spec handling enums/unions as vec[symbol,...] (result 30, result 27).
Citations:
- 1: https://docs.rs/soroban-client/latest/soroban_client/xdr/enum.ScValType.html
- 2: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0048.md
- 3: https://stellar.github.io/js-stellar-sdk/Contract.html
- 4: https://docs.rs/soroban-sdk/latest/soroban_sdk/xdr/enum.ScVal.html
- 5: https://stellar.github.io/js-stellar-sdk/js-stellar-base_src_scval.js.html
- 6: Allow specifying mixed types for vectors in
nativeToScValstellar/js-stellar-base#803 - 7: https://github.com/Soneso/stellar_flutter_sdk/blob/7ba0e5d9/lib/src/soroban/contract_spec.dart
- 8: https://github.com/stellar/soroban-examples/blob/9f46378a/other_custom_types/src/lib.rs
- 9: https://stellar.github.io/js-soroban-client/lib_contract_spec.js.html
🏁 Script executed:
# Search for usage of ChainType and PrivacyMode in the codebase
rg "ChainType|PrivacyMode" --type ts --type js -B 2 -A 2Repository: Alien-Protocol/Alien-Protocol
Length of output: 2179
🏁 Script executed:
# Look for contract.call invocations and how arguments are passed
rg "contract\.call" --type ts --type js -A 5Repository: Alien-Protocol/Alien-Protocol
Length of output: 3596
🏁 Script executed:
# Search for Soroban contract specifications or contract type definitions
fd -e "spec" -e "contract" -e "abi" | head -20Repository: Alien-Protocol/Alien-Protocol
Length of output: 55
🏁 Script executed:
# Check stellar.service.ts specifically for how enums are used
find . -name "stellar.service.ts" -o -name "stellar.service.js" | xargs catRepository: Alien-Protocol/Alien-Protocol
Length of output: 12081
🏁 Script executed:
# Search for Soroban contract files
find . -type f \( -name "*.rs" -o -name "*contract*spec*" -o -name "*.abi" \) | grep -v node_modules | head -20Repository: Alien-Protocol/Alien-Protocol
Length of output: 1092
🏁 Script executed:
# Look for any Rust contract code
find . -type d -name "contracts" -o -name "soroban"Repository: Alien-Protocol/Alien-Protocol
Length of output: 93
🏁 Script executed:
# Search for contract specifications in JSON or similar
rg "get_chain_address|ChainType" -t json -t rust --max-count 5Repository: Alien-Protocol/Alien-Protocol
Length of output: 1822
🏁 Script executed:
# Check if there's a contracts directory with actual source
git ls-files | grep -E "(contract|soroban)" | grep -v node_modules | head -20Repository: Alien-Protocol/Alien-Protocol
Length of output: 1009
🏁 Script executed:
# Read the Rust contract types definition
cat -n onchain/contracts/core_contract/src/types.rs | head -100Repository: Alien-Protocol/Alien-Protocol
Length of output: 1517
🏁 Script executed:
# Search for the complete ChainType enum definition in Rust
rg "enum ChainType" -A 10 onchain/contracts/core_contract/src/types.rsRepository: Alien-Protocol/Alien-Protocol
Length of output: 233
🏁 Script executed:
# Check PrivacyMode as well
rg "enum PrivacyMode" -A 10 onchain/contracts/core_contract/src/types.rsRepository: Alien-Protocol/Alien-Protocol
Length of output: 268
🏁 Script executed:
# Verify by looking at how the Permission enum is defined in TypeScript
grep -n "enum Permission" backend/src/stellar/stellar.types.ts -A 10Repository: Alien-Protocol/Alien-Protocol
Length of output: 55
🏁 Script executed:
# Check if there are any other numeric enum usages in the backend for comparison
rg "enum.*=\s*\d+" backend/src --type ts -B 1 -A 3Repository: Alien-Protocol/Alien-Protocol
Length of output: 55
Change ChainType and PrivacyMode from string-valued to numeric enums.
The Rust contract defines these as C-like enums compiled to integer discriminants (0, 1, 2, 3), but the TypeScript code passes string values ('Evm', 'Bitcoin', etc.). This causes a type mismatch—the contract receives strings instead of the expected u32 discriminants, resulting in failed calls or silent data corruption.
Update to:
export enum ChainType {
Evm = 0,
Bitcoin = 1,
Solana = 2,
Cosmos = 3,
}
export enum PrivacyMode {
Normal = 0,
Shielded = 1,
}Match the Rust contract's integer enum discriminants.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/src/stellar/stellar.types.ts` around lines 5 - 15, Change the
string-valued enums ChainType and PrivacyMode to numeric (C-like) enums so they
emit integer discriminants matching the Rust contract; update the definitions of
ChainType and PrivacyMode (the enum declarations) to assign explicit numeric
values (0,1,2,3 for ChainType and 0,1 for PrivacyMode) instead of string
literals so the values sent to the contract are u32 discriminants rather than
strings.
|
please review and merge. |
|
please review and merge |
|
hi,maintainer please merge so i can get my points |
Closes #398
Resolves contract interaction requirements for backend integration
Summary by CodeRabbit