-
Notifications
You must be signed in to change notification settings - Fork 345
Port Binding Generation #1287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Port Binding Generation #1287
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 a802b40
add cli for binding generation
Ryang-21 b348428
change cli name to avoid naming conflict with stellar-cli
Ryang-21 ee062fe
fix scSymbol type conversion to string
Ryang-21 c03ae81
extract duplicated typedef parsing and create util for generating imp…
Ryang-21 2fcf367
include null in Option union type and Address for scVal Address
Ryang-21 e5ca345
fix parsing of ts type for scVoid
Ryang-21 d4c571e
simplify type generation
Ryang-21 b320cde
set option field for contract methods to be type MethodOptions
Ryang-21 7a0b1f5
sanitize names of functions,interfaces, and enums for js reserved key…
Ryang-21 f065fe7
add js docs to contract method interface
Ryang-21 095f164
replace sh -> js script for fetching sac spec
Ryang-21 33e8b6c
cleanup redundant code
Ryang-21 dcd94cf
Merge branch 'master' into binding-gen
Ryang-21 d38ddeb
fix wasm parser import
Ryang-21 98b0ba0
use --network option for setting passphrase in cli
Ryang-21 c4b6ad3
fix linter errors
Ryang-21 f0497dd
parse scVoid typedef as null
Ryang-21 08974d5
handle nested option types when parsing typedef
Ryang-21 cb59a14
fix export of types in index file
Ryang-21 3a8bad2
dedeplicate import formatting from client and type generator
Ryang-21 5e5b562
refactor import generation
Ryang-21 6709e01
fortify cleaning of jsdoc
Ryang-21 d8dcfab
fix formatting
Ryang-21 0f4bbd3
sanitize method names for dynamic method generation and sanitize meth…
Ryang-21 8fcf247
change method name from sanitizeName -> sanitizeIdentifier
Ryang-21 174278c
restrict binding exports to strictly BindingGenerator
Ryang-21 4e158ea
add testing for binding generation and cli functionalites
Ryang-21 0ccb076
fix changed method name
Ryang-21 c777522
improve jsdocs for BindingGeneration class
Ryang-21 6f6cb68
add cli documentation
Ryang-21 f11ea04
Merge branch 'master' into binding-gen
Ryang-21 0437123
allow RpcServer options to be passed into cli
Ryang-21 ff2bc97
sort functions and types by name for deterministic generation
Ryang-21 05c25bf
Revert "sort functions and types by name for deterministic generation"
Ryang-21 8a873ff
update stellar-cli for e2e CI testing
Ryang-21 d2741ad
replace snapshot testing for a e2e generation -> compile -> execution…
Ryang-21 695eb39
rename binding e2e test file name
Ryang-21 ecd3f41
update docs for CLI's optional args
Ryang-21 8eb6376
include all options in --network description
Ryang-21 8a09e56
log message from error cause
Ryang-21 b57ca2d
provide default rpc-urls for tesntet, futurenet, and localnet
Ryang-21 e6e1744
update test-contracts submodule hash
Ryang-21 a3bab12
add snapshot tests for generated bindings
Ryang-21 fc776f0
update quickstart container hash
Ryang-21 3bfb72e
run snapshot binding test on fixed wasm
Ryang-21 058a140
sanitize jsdoc escapes with a space
Ryang-21 58409a1
change Val type parsing from xdr.ScVal to any for drop in replacement
Ryang-21 0fe49c5
add values: void field to nonvalue unions for cli generation parity
Ryang-21 e99d97d
implement specific generation for tuple structs for cli generation pa…
Ryang-21 21d140f
port all cli binding gen method tests
Ryang-21 916abe3
update changelog
Ryang-21 07c3842
remove log
Ryang-21 55be278
cleanup interface instantiation
Ryang-21 9eb10e5
update submodule to 17190efb9833cb7e4a92059202dfdac41662d6e1
Ryang-21 d1e7b8d
Fix rpc name from Soroban -> Stellar
Ryang-21 cc697d8
set SAC spec download to a pinned hash
Ryang-21 1ff86bf
refactor: simplify WASM fetching logic and config generation
Ryang-21 6784ed0
refactor binding generator to take rpc server instance
Ryang-21 caf34bd
refactor: have cli utilize BindingGenerator for wasm fetching
Ryang-21 0ddb24f
fix jsdoc returns statement
Ryang-21 30e6181
Merge branch 'master' into binding-gen
Ryang-21 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
|
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}>>;`; | ||
|
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; }", | ||
|
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"; | ||
|
Ryang-21 marked this conversation as resolved.
Outdated
|
||
| case xdr.ScSpecType.scSpecTypeAddress(): | ||
| case xdr.ScSpecType.scSpecTypeMuxedAddress(): | ||
| return "string"; | ||
|
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`; | ||
|
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"; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.