Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
0ac31c1
add contract bindings generation
Ryang-21 Nov 20, 2025
a802b40
add cli for binding generation
Ryang-21 Nov 20, 2025
b348428
change cli name to avoid naming conflict with stellar-cli
Ryang-21 Nov 21, 2025
ee062fe
fix scSymbol type conversion to string
Ryang-21 Nov 21, 2025
c03ae81
extract duplicated typedef parsing and create util for generating imp…
Ryang-21 Nov 24, 2025
2fcf367
include null in Option union type and Address for scVal Address
Ryang-21 Nov 24, 2025
e5ca345
fix parsing of ts type for scVoid
Ryang-21 Nov 24, 2025
d4c571e
simplify type generation
Ryang-21 Nov 24, 2025
b320cde
set option field for contract methods to be type MethodOptions
Ryang-21 Nov 24, 2025
7a0b1f5
sanitize names of functions,interfaces, and enums for js reserved key…
Ryang-21 Nov 24, 2025
f065fe7
add js docs to contract method interface
Ryang-21 Nov 25, 2025
095f164
replace sh -> js script for fetching sac spec
Ryang-21 Nov 25, 2025
33e8b6c
cleanup redundant code
Ryang-21 Nov 25, 2025
dcd94cf
Merge branch 'master' into binding-gen
Ryang-21 Nov 25, 2025
d38ddeb
fix wasm parser import
Ryang-21 Nov 25, 2025
98b0ba0
use --network option for setting passphrase in cli
Ryang-21 Nov 25, 2025
c4b6ad3
fix linter errors
Ryang-21 Nov 25, 2025
f0497dd
parse scVoid typedef as null
Ryang-21 Nov 25, 2025
08974d5
handle nested option types when parsing typedef
Ryang-21 Nov 26, 2025
cb59a14
fix export of types in index file
Ryang-21 Dec 5, 2025
3a8bad2
dedeplicate import formatting from client and type generator
Ryang-21 Dec 5, 2025
5e5b562
refactor import generation
Ryang-21 Dec 5, 2025
6709e01
fortify cleaning of jsdoc
Ryang-21 Dec 5, 2025
d8dcfab
fix formatting
Ryang-21 Jan 8, 2026
0f4bbd3
sanitize method names for dynamic method generation and sanitize meth…
Ryang-21 Jan 8, 2026
8fcf247
change method name from sanitizeName -> sanitizeIdentifier
Ryang-21 Jan 8, 2026
174278c
restrict binding exports to strictly BindingGenerator
Ryang-21 Jan 8, 2026
4e158ea
add testing for binding generation and cli functionalites
Ryang-21 Jan 8, 2026
0ccb076
fix changed method name
Ryang-21 Jan 8, 2026
c777522
improve jsdocs for BindingGeneration class
Ryang-21 Jan 8, 2026
6f6cb68
add cli documentation
Ryang-21 Jan 8, 2026
f11ea04
Merge branch 'master' into binding-gen
Ryang-21 Jan 8, 2026
0437123
allow RpcServer options to be passed into cli
Ryang-21 Jan 9, 2026
ff2bc97
sort functions and types by name for deterministic generation
Ryang-21 Jan 9, 2026
05c25bf
Revert "sort functions and types by name for deterministic generation"
Ryang-21 Jan 9, 2026
8a873ff
update stellar-cli for e2e CI testing
Ryang-21 Jan 9, 2026
d2741ad
replace snapshot testing for a e2e generation -> compile -> execution…
Ryang-21 Jan 9, 2026
695eb39
rename binding e2e test file name
Ryang-21 Jan 9, 2026
ecd3f41
update docs for CLI's optional args
Ryang-21 Jan 9, 2026
8eb6376
include all options in --network description
Ryang-21 Jan 12, 2026
8a09e56
log message from error cause
Ryang-21 Jan 12, 2026
b57ca2d
provide default rpc-urls for tesntet, futurenet, and localnet
Ryang-21 Jan 12, 2026
e6e1744
update test-contracts submodule hash
Ryang-21 Jan 13, 2026
a3bab12
add snapshot tests for generated bindings
Ryang-21 Jan 13, 2026
fc776f0
update quickstart container hash
Ryang-21 Jan 13, 2026
3bfb72e
run snapshot binding test on fixed wasm
Ryang-21 Jan 13, 2026
058a140
sanitize jsdoc escapes with a space
Ryang-21 Jan 13, 2026
58409a1
change Val type parsing from xdr.ScVal to any for drop in replacement
Ryang-21 Jan 13, 2026
0fe49c5
add values: void field to nonvalue unions for cli generation parity
Ryang-21 Jan 13, 2026
e99d97d
implement specific generation for tuple structs for cli generation pa…
Ryang-21 Jan 13, 2026
21d140f
port all cli binding gen method tests
Ryang-21 Jan 14, 2026
916abe3
update changelog
Ryang-21 Jan 14, 2026
07c3842
remove log
Ryang-21 Jan 14, 2026
55be278
cleanup interface instantiation
Ryang-21 Jan 14, 2026
9eb10e5
update submodule to 17190efb9833cb7e4a92059202dfdac41662d6e1
Ryang-21 Jan 15, 2026
d1e7b8d
Fix rpc name from Soroban -> Stellar
Ryang-21 Jan 16, 2026
cc697d8
set SAC spec download to a pinned hash
Ryang-21 Jan 20, 2026
1ff86bf
refactor: simplify WASM fetching logic and config generation
Ryang-21 Jan 20, 2026
6784ed0
refactor binding generator to take rpc server instance
Ryang-21 Jan 20, 2026
caf34bd
refactor: have cli utilize BindingGenerator for wasm fetching
Ryang-21 Jan 20, 2026
0ddb24f
fix jsdoc returns statement
Ryang-21 Jan 20, 2026
30e6181
Merge branch 'master' into binding-gen
Ryang-21 Jan 21, 2026
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
4 changes: 4 additions & 0 deletions bin/stellar
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

// Entry point for the stellar CLI
require("../lib/cli/index.js").runCli();
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
"files": [
"/types",
"/lib",
"/dist"
"/dist",
"/bin"
],
"bin": {
"stellar": "./bin/stellar"
},
"exports": {
".": {
"browser": "./dist/stellar-sdk.min.js",
Expand Down Expand Up @@ -197,6 +201,7 @@
"@stellar/stellar-base": "^14.0.2",
"axios": "^1.12.2",
"bignumber.js": "^9.3.1",
"commander": "^14.0.2",
"eventsource": "^2.0.2",
"feaxios": "^0.0.23",
"randombytes": "^2.1.0",
Expand Down
62 changes: 62 additions & 0 deletions scripts/download-sac-spec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated

set -euo pipefail

# Get script directory and repo root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# Download the Stellar Asset Contract spec XDR
REPO_URL="https://raw.githubusercontent.com/stellar/stellar-asset-contract-spec/main/stellar-asset-spec.xdr"
OUTPUT_FILE="$REPO_ROOT/stellar-asset-spec.xdr"
TS_FILE="$REPO_ROOT/src/bindings/sac-spec.ts"

echo "Downloading Stellar Asset Contract spec from GitHub..."

# Download with error handling
if ! curl -fsSL "$REPO_URL" -o "$OUTPUT_FILE"; then
echo "✗ Failed to download from $REPO_URL"
exit 1
fi

# Verify file was downloaded and has content
if [ ! -s "$OUTPUT_FILE" ]; then
echo "✗ Downloaded file is empty or doesn't exist"
rm -f "$OUTPUT_FILE"
exit 1
fi

echo "✓ Successfully downloaded $OUTPUT_FILE"
echo " Size: $(wc -c < "$OUTPUT_FILE") bytes"

# Convert to base64 (portable for both macOS and Linux)
echo "Converting to base64..."
if command -v base64 >/dev/null 2>&1; then
# Try macOS syntax first, fall back to Linux
BASE64_CONTENT=$(base64 -i "$OUTPUT_FILE" 2>/dev/null || base64 -w 0 "$OUTPUT_FILE" 2>/dev/null || base64 "$OUTPUT_FILE" | tr -d '\n')
else
echo "✗ base64 command not found"
rm -f "$OUTPUT_FILE"
exit 1
fi

# Validate base64 content
if [ -z "$BASE64_CONTENT" ]; then
echo "✗ Base64 conversion failed"
rm -f "$OUTPUT_FILE"
exit 1
fi

# Update TypeScript file using printf to avoid shell interpretation
echo "Updating $TS_FILE..."
{
echo "// Auto-generated by scripts/download-sac-spec.sh"
echo "// Do not edit manually - run the script to update"
printf 'export const SAC_SPEC = "%s";\n' "$BASE64_CONTENT"
} > "$TS_FILE"

echo "✓ Successfully updated $TS_FILE"

# Cleanup downloaded file
rm -f "$OUTPUT_FILE"
echo "✓ Cleaned up temporary file" d
255 changes: 255 additions & 0 deletions src/bindings/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { xdr } from "@stellar/stellar-base";
import { Spec } from "../contract";

/**
* Generates TypeScript client class for contract methods
*/
export class ClientGenerator {
private spec: Spec;
private interfaceImports: Set<string> = new Set<string>();

constructor(spec: Spec) {
this.spec = spec;
}

/**
* Generate client class
*/
generate(): string {
// Generate constructor method if it exists
let deployMethod = "";
try {
const constructorFunc = this.spec.getFunc("__constructor");
deployMethod = this.generateDeployMethod(constructorFunc);
} catch {
// For specs without a constructor, generate a deploy method without params
deployMethod = this.generateDeployMethod(undefined);
}
// Generate interface methods
const interfaceMethods = this.spec
.funcs()
.filter((func) => func.name().toString() !== "__constructor")
.map((func) => this.generateInterfaceMethod(func))
.join("\n");

// Build imports
const typeImports =
Array.from(this.interfaceImports).length > 0
? `import { ${Array.from(this.interfaceImports).join(", ")} } from './types.js';`
: "";

const specEntries = this.spec.entries.map(
(entry) => `"${entry.toXDR("base64")}"`,
);

const fromJSON = this.spec
.funcs()
.filter((func) => func.name().toString() !== "__constructor")
.map((func) => this.generateFromJSONMethod(func))
.join(",");

return `import {
AssembledTransaction,
Client as ContractClient,
ClientOptions as ContractClientOptions,
Result,
MethodOptions,
Spec,
} from '@stellar/stellar-sdk/contract';
${typeImports}
import { Buffer } from 'buffer';
export interface Client {
${interfaceMethods}
}

export class Client extends ContractClient {
constructor(public readonly options: ContractClientOptions) {
super(
new Spec([${specEntries.join(", ")}]),
options
);
}

${deployMethod}
public readonly fromJSON = {
${fromJSON}
};
}`;
}

/**
* Generate interface method signature
*/
private generateInterfaceMethod(func: any): string {
const name = func.name().toString();
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated
const inputs = func.inputs().map((input: any) => ({
name: input.name().toString(),
type: this.generateTypeFromTypeDef(input.type()),
}));
const outputType =
func.outputs().length > 0
? this.generateTypeFromTypeDef(func.outputs()[0])
: "void";

const params = this.formatMethodParameters(inputs);

return ` ${name}(${params}): Promise<AssembledTransaction<${outputType}>>;`;
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated
}

private generateFromJSONMethod(func: any): string {
const name = func.name().toString();
const outputType =
func.outputs().length > 0
? this.generateTypeFromTypeDef(func.outputs()[0])
: "void";

return ` ${name} : this.txFromJSON<${outputType}>`;
}
/**
* Generate deploy method
*/
private generateDeployMethod(
constructorFunc: xdr.ScSpecFunctionV0 | undefined,
): string {
// If no constructor, generate deploy with no params
if (!constructorFunc) {
const params = this.formatConstructorParameters([]);
return ` static deploy<T = Client>(${params}): Promise<AssembledTransaction<T>> {
return ContractClient.deploy(null, options);
}`;
}
const inputs = constructorFunc.inputs().map((input: any) => ({
name: input.name().toString(),
type: this.generateTypeFromTypeDef(input.type()),
}));

const params = this.formatConstructorParameters(inputs);
const inputsDestructure =
inputs.length > 0 ? `{ ${inputs.map((i) => i.name).join(", ")} }` : "";

return ` static deploy<T = Client>(${params}): Promise<AssembledTransaction<T>> {
return ContractClient.deploy(${inputs.length > 0 ? inputsDestructure + ", " : ""}options);
}`;
}

/**
* Format method parameters
*/
private formatMethodParameters(
inputs: Array<{ name: string; type: string }>,
): string {
const params: string[] = [];

if (inputs.length > 0) {
const inputsParam = `{ ${inputs.map((i) => `${i.name}: ${i.type}`).join("; ")} }`;
params.push(
`{ ${inputs.map((i) => i.name).join(", ")} }: ${inputsParam}`,
);
}

params.push(
"options?: { fee?: number; timeoutInSeconds?: number; simulate?: boolean; }",
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated
);

return params.join(", ");
}

/**
* Format constructor parameters
*/
private formatConstructorParameters(
inputs: Array<{ name: string; type: string }>,
): string {
const params: string[] = [];

if (inputs.length > 0) {
const inputsParam = `{ ${inputs.map((i) => `${i.name}: ${i.type}`).join("; ")} }`;
params.push(
`{ ${inputs.map((i) => i.name).join(", ")} }: ${inputsParam}`,
);
}

params.push(
'options: MethodOptions & Omit<ContractClientOptions, \'contractId\'> & { wasmHash: Buffer | string; salt?: Buffer | Uint8Array; format?: "hex" | "base64"; address?: string; }',
);

return params.join(", ");
}

/**
* Generate TypeScript type from XDR type definition
*/
private generateTypeFromTypeDef(typeDef: xdr.ScSpecTypeDef): string {
switch (typeDef.switch()) {
case xdr.ScSpecType.scSpecTypeVal():
return "xdr.ScVal";
case xdr.ScSpecType.scSpecTypeBool():
return "boolean";
case xdr.ScSpecType.scSpecTypeVoid():
return "void";
case xdr.ScSpecType.scSpecTypeError():
return "Error";
case xdr.ScSpecType.scSpecTypeU32():
case xdr.ScSpecType.scSpecTypeI32():
return "number";
case xdr.ScSpecType.scSpecTypeU64():
case xdr.ScSpecType.scSpecTypeI64():
case xdr.ScSpecType.scSpecTypeTimepoint():
case xdr.ScSpecType.scSpecTypeDuration():
case xdr.ScSpecType.scSpecTypeU128():
case xdr.ScSpecType.scSpecTypeI128():
case xdr.ScSpecType.scSpecTypeU256():
case xdr.ScSpecType.scSpecTypeI256():
return "bigint";
case xdr.ScSpecType.scSpecTypeBytes():
case xdr.ScSpecType.scSpecTypeBytesN():
return "Buffer";
case xdr.ScSpecType.scSpecTypeString():
return "string";
case xdr.ScSpecType.scSpecTypeSymbol():
return "symbol";
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated
case xdr.ScSpecType.scSpecTypeAddress():
case xdr.ScSpecType.scSpecTypeMuxedAddress():
return "string";
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated
case xdr.ScSpecType.scSpecTypeVec(): {
const vecType = this.generateTypeFromTypeDef(
typeDef.vec().elementType(),
);
return `Array<${vecType}>`;
}
case xdr.ScSpecType.scSpecTypeMap(): {
const keyType = this.generateTypeFromTypeDef(typeDef.map().keyType());
const valueType = this.generateTypeFromTypeDef(
typeDef.map().valueType(),
);
return `Map<${keyType}, ${valueType}>`;
}
case xdr.ScSpecType.scSpecTypeTuple(): {
const tupleTypes = typeDef
.tuple()
.valueTypes()
.map((t: xdr.ScSpecTypeDef) => this.generateTypeFromTypeDef(t));
return `[${tupleTypes.join(", ")}]`;
}
case xdr.ScSpecType.scSpecTypeOption(): {
const optionType = this.generateTypeFromTypeDef(
typeDef.option().valueType(),
);
return `${optionType} | undefined`;
Comment thread
Ryang-21 marked this conversation as resolved.
Outdated
}
case xdr.ScSpecType.scSpecTypeResult(): {
const okType = this.generateTypeFromTypeDef(typeDef.result().okType());
const errorType = this.generateTypeFromTypeDef(
typeDef.result().errorType(),
);
return `Result<${okType}, ${errorType}>`;
}
case xdr.ScSpecType.scSpecTypeUdt():
const udtName = typeDef.udt().name().toString();
this.interfaceImports.add(udtName);
return udtName;
default:
return "unknown";
}
}
}
Loading
Loading