Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions backend/src/stellar/stellar.exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { HttpException, HttpStatus } from '@nestjs/common';

/**
* Custom exception for Stellar RPC errors
*/
export class StellarRpcException extends HttpException {
constructor(
message: string,
public readonly originalError?: any,
public readonly contractId?: string,
public readonly method?: string,
) {
super(
{
message,
error: 'Stellar RPC Error',
originalError: originalError?.message || originalError,
contractId,
method,
},
HttpStatus.BAD_GATEWAY,
);
Comment thread
Mmesolove marked this conversation as resolved.
}
Comment thread
Mmesolove marked this conversation as resolved.
}
14 changes: 13 additions & 1 deletion backend/src/stellar/stellar.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { rpc, Contract } from '@stellar/stellar-sdk';
import { rpc, Contract, Address, xdr } from '@stellar/stellar-sdk';
import { ConfigService } from '../config/config.service';
import { StellarRpcException } from './stellar.exceptions';
import {
ChainType,
GetOwnerResult,
ResolveStellarResult,
GetChainAddressResult,
GetVaultBalanceResult,
GetScheduledPaymentResult,
IsVaultActiveResult,
GetCreatedAtResult,
ScheduledPayment,
} from './stellar.types';

@Injectable()
export class StellarService implements OnModuleInit {
Expand Down
49 changes: 49 additions & 0 deletions backend/src/stellar/stellar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* TypeScript interfaces for Stellar contract return types
*/

export enum ChainType {
Evm = 'Evm',
Bitcoin = 'Bitcoin',
Solana = 'Solana',
Cosmos = 'Cosmos',
}

export enum PrivacyMode {
Normal = 'Normal',
Shielded = 'Shielded',
}
Comment on lines +5 to +15
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:

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:


🏁 Script executed:

# Search for usage of ChainType and PrivacyMode in the codebase
rg "ChainType|PrivacyMode" --type ts --type js -B 2 -A 2

Repository: 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 5

Repository: 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 -20

Repository: 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 cat

Repository: 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 -20

Repository: 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 5

Repository: 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 -20

Repository: 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 -100

Repository: 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.rs

Repository: 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.rs

Repository: 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 10

Repository: 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 3

Repository: 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.


export interface ScheduledPayment {
from: string;
to: string;
token: string;
amount: string;
release_at: number;
executed: boolean;
}

export interface VaultConfig {
owner: string;
token: string;
created_at: number;
}

export interface VaultState {
balance: string;
is_active: boolean;
}

export interface ResolveData {
wallet: string;
memo?: number;
}

// Contract method return types
export type GetOwnerResult = string | null;
export type ResolveStellarResult = string;
export type GetChainAddressResult = string | null;
export type GetVaultBalanceResult = string | null;
export type GetScheduledPaymentResult = ScheduledPayment | null;
export type IsVaultActiveResult = boolean | null;
export type GetCreatedAtResult = number | null;
Loading