From ee4a767b9032e5ac632b5e7bdf6c70bc8c2f3332 Mon Sep 17 00:00:00 2001 From: Damilola Maria Ajibade Date: Sat, 30 May 2026 16:23:05 +0000 Subject: [PATCH] feat: generate TypeScript contract bindings from Soroban WASM (#174) - Add sdk/codegen/ tool that reads contractspecv0 section from WASM - Parse all funcs, structs, unions, enums, and error cases - Generate typed interfaces and client interface in generated-client.ts - Add codegen:build and codegen scripts to sdk/package.json - Add codegen CI job that builds WASM, runs codegen, uploads artifact - Fix pre-existing compile errors in token and admin contracts --- .github/workflows/ci.yml | 48 ++ contracts/admin/src/lib.rs | 1 - contracts/token/src/lib.rs | 66 +-- sdk/codegen/dist/generate.js | 149 ++++++ sdk/codegen/dist/index.js | 98 ++++ sdk/codegen/dist/parse-abi.js | 119 +++++ sdk/codegen/package-lock.json | 858 ++++++++++++++++++++++++++++++++++ sdk/codegen/package.json | 24 + sdk/codegen/src/generate.ts | 174 +++++++ sdk/codegen/src/index.ts | 76 +++ sdk/codegen/src/parse-abi.ts | 134 ++++++ sdk/codegen/tsconfig.json | 15 + sdk/package.json | 4 +- sdk/src/generated-client.ts | 150 ++++++ 14 files changed, 1863 insertions(+), 53 deletions(-) create mode 100644 sdk/codegen/dist/generate.js create mode 100644 sdk/codegen/dist/index.js create mode 100644 sdk/codegen/dist/parse-abi.js create mode 100644 sdk/codegen/package-lock.json create mode 100644 sdk/codegen/package.json create mode 100644 sdk/codegen/src/generate.ts create mode 100644 sdk/codegen/src/index.ts create mode 100644 sdk/codegen/src/parse-abi.ts create mode 100644 sdk/codegen/tsconfig.json create mode 100644 sdk/src/generated-client.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 320d8d1..20d7d0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,3 +93,51 @@ jobs: - name: Run tests run: npm test + + # ─── Codegen CI ───────────────────────────────────────────────────────── + codegen: + name: Contract Bindings Codegen + runs-on: ubuntu-latest + needs: [contracts, sdk] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Cache Cargo target + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-target-${{ hashFiles('Cargo.lock') }}-stable + + - name: Build WASM + run: cargo build --target wasm32-unknown-unknown --release + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: sdk/package-lock.json + + - name: Build codegen tool + working-directory: sdk + run: npm run codegen:build + + - name: Run codegen + working-directory: sdk + run: | + node codegen/dist/index.js \ + ../target/wasm32-unknown-unknown/release/bc_forge_token.wasm \ + src/generated-client.ts \ + --name BcForgeToken + + - name: Upload generated bindings + uses: actions/upload-artifact@v4 + with: + name: generated-client + path: sdk/src/generated-client.ts diff --git a/contracts/admin/src/lib.rs b/contracts/admin/src/lib.rs index e76d173..4eeecea 100644 --- a/contracts/admin/src/lib.rs +++ b/contracts/admin/src/lib.rs @@ -154,7 +154,6 @@ pub fn create_proposal(env: &Env, creator: Address, description: String) -> u64 let proposal = Proposal { creator: creator.clone(), - action_type, description, approvals: vec![env, creator], executed: false, diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 5faad34..a65629a 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -23,11 +23,8 @@ pub enum DataKey { /// The contract admin address (singular). Admin, PendingAdmin, - /// Spending allowance: (owner, spender) → amount and expiration. + /// Spending allowance: (owner, spender) → AllowanceInfo. Allowance(Address, Address), - /// Token balance for an address. - Allowance(Address, Address), - AllowanceExp(Address, Address), Balance(Address), Name, Symbol, @@ -133,54 +130,26 @@ impl BcForgeToken { .set(&DataKey::Balance(id.clone()), &balance); } - fn read_allowance(env: &Env, from: &Address, spender: &Address) -> i128 { - let allowance_info: AllowanceInfo = env.storage() - .persistent() - .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }); - - // Check if allowance has expired - if allowance_info.exp_ledger > 0 { - let current_ledger = env.ledger().sequence(); - if current_ledger > allowance_info.exp_ledger as u64 { - return 0; // Allowance expired - } - } - - allowance_info.amount - if let Some(exp_ledger) = env - .storage() - .persistent() - .get::<_, u32>(&DataKey::AllowanceExp(from.clone(), spender.clone())) - { - if exp_ledger > 0 && env.ledger().sequence() > exp_ledger { - return 0; - } - } - + fn read_allowance_info(env: &Env, from: &Address, spender: &Address) -> AllowanceInfo { env.storage() .persistent() .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(0) + .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }) } - fn write_allowance(env: &Env, from: &Address, spender: &Address, amount: i128, exp: u32) { - let allowance_info = AllowanceInfo { amount, exp_ledger: exp }; - env.storage() - .persistent() - .set(&DataKey::Allowance(from.clone(), spender.clone()), &allowance_info); + fn read_allowance(env: &Env, from: &Address, spender: &Address) -> i128 { + let info = Self::read_allowance_info(env, from, spender); + if info.exp_ledger > 0 && env.ledger().sequence() > info.exp_ledger { + return 0; + } + info.amount } - /// Reads the full allowance info for (owner → spender), defaulting to zero allowance with no expiration. - fn read_allowance_info(env: &Env, from: &Address, spender: &Address) -> AllowanceInfo { - env.storage() - .persistent() - .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }) - .set(&DataKey::Allowance(from.clone(), spender.clone()), &amount); - env.storage() - .persistent() - .set(&DataKey::AllowanceExp(from.clone(), spender.clone()), &exp); + fn write_allowance(env: &Env, from: &Address, spender: &Address, amount: i128, exp: u32) { + env.storage().persistent().set( + &DataKey::Allowance(from.clone(), spender.clone()), + &AllowanceInfo { amount, exp_ledger: exp }, + ); } fn move_balance( @@ -615,12 +584,9 @@ impl TokenInterface for BcForgeToken { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientAllowance); } - Self::move_balance(&env, &from, &to, amount); - // Preserve the original expiration let allowance_info = Self::read_allowance_info(&env, &from, &spender); - Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); let _ = Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); - Self::write_allowance(&env, &from, &spender, allowance - amount, 0); + Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); events::emit_transfer_from(&env, &spender, &from, &to, amount, allowance - amount); } @@ -664,10 +630,8 @@ impl TokenInterface for BcForgeToken { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientBalance); } - // Preserve the original expiration let allowance_info = Self::read_allowance_info(&env, &from, &spender); Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); - Self::write_allowance(&env, &from, &spender, allowance - amount, 0); Self::write_balance(&env, &from, balance - amount); let supply = Self::read_supply(&env) - amount; Self::write_supply(&env, supply); diff --git a/sdk/codegen/dist/generate.js b/sdk/codegen/dist/generate.js new file mode 100644 index 0000000..9ee6b3c --- /dev/null +++ b/sdk/codegen/dist/generate.js @@ -0,0 +1,149 @@ +"use strict"; +/** + * generate.ts + * + * Generates type-safe TypeScript client code from a parsed Soroban contract ABI. + * Produces: + * - TypeScript interfaces for all struct/union/enum types + * - A typed client class with one method per contract function + * - An error enum for all contract error cases + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateBindings = generateBindings; +// ─── Type Mapping ───────────────────────────────────────────────────────────── +function scSpecTypeToTs(typeDef) { + const kind = typeDef.switch().name; + switch (kind) { + case 'scSpecTypeVoid': return 'void'; + case 'scSpecTypeBool': return 'boolean'; + case 'scSpecTypeU32': return 'number'; + case 'scSpecTypeI32': return 'number'; + case 'scSpecTypeU64': return 'bigint'; + case 'scSpecTypeI64': return 'bigint'; + case 'scSpecTypeU128': return 'bigint'; + case 'scSpecTypeI128': return 'bigint'; + case 'scSpecTypeU256': return 'bigint'; + case 'scSpecTypeI256': return 'bigint'; + case 'scSpecTypeString': return 'string'; + case 'scSpecTypeSymbol': return 'string'; + case 'scSpecTypeAddress': return 'string'; + case 'scSpecTypeBytes': return 'Buffer'; + case 'scSpecTypeBytesN': return 'Buffer'; + case 'scSpecTypeTimepoint': return 'bigint'; + case 'scSpecTypeDuration': return 'bigint'; + case 'scSpecTypeVal': return 'unknown'; + case 'scSpecTypeError': return 'number'; + case 'scSpecTypeOption': { + const inner = scSpecTypeToTs(typeDef.option().valueType()); + return `${inner} | null`; + } + case 'scSpecTypeResult': { + const ok = scSpecTypeToTs(typeDef.result().okType()); + const err = scSpecTypeToTs(typeDef.result().errorType()); + return `{ ok: ${ok} } | { error: ${err} }`; + } + case 'scSpecTypeVec': { + const elem = scSpecTypeToTs(typeDef.vec().elementType()); + return `${elem}[]`; + } + case 'scSpecTypeMap': { + const k = scSpecTypeToTs(typeDef.map().keyType()); + const v = scSpecTypeToTs(typeDef.map().valueType()); + return `Map<${k}, ${v}>`; + } + case 'scSpecTypeTuple': { + const elems = typeDef.tuple().valueTypes().map(scSpecTypeToTs); + return `[${elems.join(', ')}]`; + } + case 'scSpecTypeUdt': { + return typeDef.udt().name().toString(); + } + default: + return 'unknown'; + } +} +// ─── Struct / Union / Enum Interfaces ───────────────────────────────────────── +function generateStructInterface(s) { + const doc = s.doc ? `/** ${s.doc} */\n` : ''; + const fields = s.fields + .map((f) => ` ${f.name}: ${scSpecTypeToTs(f.type)};`) + .join('\n'); + return `${doc}export interface ${s.name} {\n${fields}\n}`; +} +function generateUnionType(u) { + const doc = u.doc ? `/** ${u.doc} */\n` : ''; + const cases = u.cases.map((c) => { + if (c.types.length === 0) { + return ` | { tag: '${c.name}' }`; + } + const vals = c.types.map((t, i) => `value${i}: ${scSpecTypeToTs(t)}`).join('; '); + return ` | { tag: '${c.name}'; ${vals} }`; + }); + return `${doc}export type ${u.name} =\n${cases.join('\n')};`; +} +function generateEnumType(e) { + const doc = e.doc ? `/** ${e.doc} */\n` : ''; + const cases = e.cases.map((c) => ` ${c.name} = ${c.value},`).join('\n'); + return `${doc}export enum ${e.name} {\n${cases}\n}`; +} +// ─── Error Enum ─────────────────────────────────────────────────────────────── +function generateErrorEnum(errors, contractName) { + if (errors.length === 0) + return ''; + const cases = errors.map((e) => { + const doc = e.doc ? ` /** ${e.doc} */\n` : ''; + return `${doc} ${e.name} = ${e.value},`; + }).join('\n'); + return `export enum ${contractName}Error {\n${cases}\n}`; +} +// ─── Method Signatures ──────────────────────────────────────────────────────── +function generateMethod(fn) { + const doc = fn.doc + ? ` /**\n * ${fn.doc}\n */\n` + : ''; + const params = fn.inputs + .map((inp) => `${inp.name}: ${scSpecTypeToTs(inp.type)}`) + .join(', '); + const returnType = fn.outputs.length === 0 + ? 'void' + : fn.outputs.length === 1 + ? scSpecTypeToTs(fn.outputs[0]) + : `[${fn.outputs.map(scSpecTypeToTs).join(', ')}]`; + return `${doc} ${fn.name}(${params}): Promise<${returnType}>;`; +} +// ─── Client Interface ───────────────────────────────────────────────────────── +function generateClientInterface(funcs, contractName) { + const methods = funcs.map(generateMethod).join('\n\n'); + return `export interface ${contractName}Client {\n${methods}\n}`; +} +// ─── Main Generator ─────────────────────────────────────────────────────────── +function generateBindings(abi, contractName) { + const banner = [ + '// ─────────────────────────────────────────────────────────────────────────────', + `// Generated by @bc-forge/codegen — DO NOT EDIT`, + `// Contract: ${contractName}`, + `// Generated: ${new Date().toISOString()}`, + '// ─────────────────────────────────────────────────────────────────────────────', + '', + ].join('\n'); + const parts = [banner]; + // Enums + for (const e of abi.enums) { + parts.push(generateEnumType(e)); + } + // Structs + for (const s of abi.structs) { + parts.push(generateStructInterface(s)); + } + // Unions + for (const u of abi.unions) { + parts.push(generateUnionType(u)); + } + // Error enum + const errorEnum = generateErrorEnum(abi.errors, contractName); + if (errorEnum) + parts.push(errorEnum); + // Client interface + parts.push(generateClientInterface(abi.funcs, contractName)); + return parts.join('\n\n') + '\n'; +} diff --git a/sdk/codegen/dist/index.js b/sdk/codegen/dist/index.js new file mode 100644 index 0000000..658aa1f --- /dev/null +++ b/sdk/codegen/dist/index.js @@ -0,0 +1,98 @@ +#!/usr/bin/env node +"use strict"; +/** + * index.ts — CLI entry point for @bc-forge/codegen + * + * Usage: + * bc-forge-codegen [output-path] [--name ] + * + * Defaults: + * output-path → sdk/src/generated-client.ts + * name → derived from wasm filename (snake_case → PascalCase) + */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +const parse_abi_1 = require("./parse-abi"); +const generate_1 = require("./generate"); +function toPascalCase(str) { + return str + .replace(/[-_](.)/g, (_, c) => c.toUpperCase()) + .replace(/^(.)/, (_, c) => c.toUpperCase()); +} +async function main() { + const args = process.argv.slice(2); + if (args.length === 0 || args.includes('--help') || args.includes('-h')) { + console.log(` +Usage: bc-forge-codegen [output-path] [--name ] + + wasm-path Path to the compiled .wasm file + output-path Output .ts file (default: sdk/src/generated-client.ts) + --name Contract name for generated types (default: derived from filename) + +Example: + bc-forge-codegen target/wasm32-unknown-unknown/release/bc_forge_token.wasm +`); + process.exit(0); + } + const wasmPath = args[0]; + if (!fs.existsSync(wasmPath)) { + console.error(`Error: WASM file not found: ${wasmPath}`); + process.exit(1); + } + // Determine output path + let outputPath = args[1] && !args[1].startsWith('--') + ? args[1] + : path.resolve(__dirname, '../../../src/generated-client.ts'); + // Determine contract name + const nameIdx = args.indexOf('--name'); + const contractName = nameIdx !== -1 && args[nameIdx + 1] + ? args[nameIdx + 1] + : toPascalCase(path.basename(wasmPath, '.wasm')); + console.log(`Parsing WASM: ${wasmPath}`); + const abi = await (0, parse_abi_1.parseWasm)(wasmPath); + console.log(` Found ${abi.funcs.length} function(s), ${abi.structs.length} struct(s), ` + + `${abi.unions.length} union(s), ${abi.enums.length} enum(s), ${abi.errors.length} error(s)`); + const code = (0, generate_1.generateBindings)(abi, contractName); + // Ensure output directory exists + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, code, 'utf8'); + console.log(`Generated: ${outputPath}`); +} +main().catch((err) => { + console.error('codegen failed:', err.message); + process.exit(1); +}); diff --git a/sdk/codegen/dist/parse-abi.js b/sdk/codegen/dist/parse-abi.js new file mode 100644 index 0000000..aea906a --- /dev/null +++ b/sdk/codegen/dist/parse-abi.js @@ -0,0 +1,119 @@ +"use strict"; +/** + * parse-abi.ts + * + * Reads a compiled Soroban contract WASM file and extracts the contract spec + * (ABI) using the stellar-sdk contract.Client.fromWasm API. + */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseWasm = parseWasm; +const fs = __importStar(require("fs")); +const stellar_sdk_1 = require("@stellar/stellar-sdk"); +/** + * Parses a Soroban contract WASM file and returns the structured ABI. + */ +async function parseWasm(wasmPath) { + const wasmBuffer = fs.readFileSync(wasmPath); + // Use Client.fromWasm which handles the contractspecv0 section extraction + const client = await stellar_sdk_1.contract.Client.fromWasm(wasmBuffer, { + contractId: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4', + networkPassphrase: 'Test SDF Network ; September 2015', + rpcUrl: 'http://localhost:8000/rpc', + }); + return extractAbi(client.spec); +} +function extractAbi(spec) { + const funcs = spec.funcs().map((fn) => ({ + name: fn.name().toString(), + doc: fn.doc().toString(), + inputs: fn.inputs().map((inp) => ({ + name: inp.name().toString(), + type: inp.type(), + })), + outputs: fn.outputs(), + })); + const errors = spec.errorCases().map((e) => ({ + name: e.name().toString(), + value: e.value(), + doc: e.doc().toString(), + })); + const structs = []; + const unions = []; + const enums = []; + for (const entry of spec.entries) { + const kind = entry.switch().name; + if (kind === 'scSpecEntryUdtStructV0') { + const s = entry.udtStructV0(); + structs.push({ + name: s.name().toString(), + doc: s.doc().toString(), + fields: s.fields().map((f) => ({ + name: f.name().toString(), + type: f.type(), + })), + }); + } + else if (kind === 'scSpecEntryUdtUnionV0') { + const u = entry.udtUnionV0(); + unions.push({ + name: u.name().toString(), + doc: u.doc().toString(), + cases: u.cases().map((c) => { + const caseName = c.switch().name; + if (caseName === 'scSpecUdtUnionCaseVoidV0') { + return { name: c.voidCase().name().toString(), types: [] }; + } + else { + const tc = c.tupleCase(); + return { name: tc.name().toString(), types: tc.type() }; + } + }), + }); + } + else if (kind === 'scSpecEntryUdtEnumV0') { + const e = entry.udtEnumV0(); + enums.push({ + name: e.name().toString(), + doc: e.doc().toString(), + cases: e.cases().map((c) => ({ + name: c.name().toString(), + value: c.value(), + })), + }); + } + } + return { funcs, errors, structs, unions, enums }; +} diff --git a/sdk/codegen/package-lock.json b/sdk/codegen/package-lock.json new file mode 100644 index 0000000..76fa7f2 --- /dev/null +++ b/sdk/codegen/package-lock.json @@ -0,0 +1,858 @@ +{ + "name": "@bc-forge/codegen", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@bc-forge/codegen", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@stellar/stellar-sdk": "^12.0.0" + }, + "bin": { + "bc-forge-codegen": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@stellar/js-xdr": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", + "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", + "license": "Apache-2.0" + }, + "node_modules/@stellar/stellar-base": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.1.1.tgz", + "integrity": "sha512-gOBSOFDepihslcInlqnxKZdIW9dMUO1tpOm3AtJR33K2OvpXG6SaVHCzAmCFArcCqI9zXTEiSoh70T48TmiHJA==", + "license": "Apache-2.0", + "dependencies": { + "@stellar/js-xdr": "^3.1.2", + "base32.js": "^0.1.0", + "bignumber.js": "^9.1.2", + "buffer": "^6.0.3", + "sha.js": "^2.3.6", + "tweetnacl": "^1.0.3" + }, + "optionalDependencies": { + "sodium-native": "^4.1.1" + } + }, + "node_modules/@stellar/stellar-sdk": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.3.0.tgz", + "integrity": "sha512-F2DYFop/M5ffXF0lvV5Ezjk+VWNKg0QDX8gNhwehVU3y5LYA3WAY6VcCarMGPaG9Wdgoeh1IXXzOautpqpsltw==", + "license": "Apache-2.0", + "dependencies": { + "@stellar/stellar-base": "^12.1.1", + "axios": "^1.7.7", + "bignumber.js": "^9.1.2", + "eventsource": "^2.0.2", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + } + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bare-addon-resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", + "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-module-resolve": "^1.10.0", + "bare-semver": "^1.0.0" + }, + "peerDependencies": { + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-module-resolve": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.2.tgz", + "integrity": "sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-semver": "^1.0.0" + }, + "peerDependencies": { + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-semver": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.3.tgz", + "integrity": "sha512-HS/A30bi2+PiRJfU6R4+Kp+6KeLSCSByjYM2iiobOKzLAvtu1CT+S8xWfiU7wz0erknjkUoC+yXy108tzIuP5Q==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/base32.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/require-addon": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", + "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-addon-resolve": "^1.3.0" + }, + "engines": { + "bare": ">=1.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sodium-native": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", + "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "require-addon": "^1.1.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "license": "MIT" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "license": "MIT" + }, + "node_modules/which-typed-array": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + } +} diff --git a/sdk/codegen/package.json b/sdk/codegen/package.json new file mode 100644 index 0000000..b7de1cf --- /dev/null +++ b/sdk/codegen/package.json @@ -0,0 +1,24 @@ +{ + "name": "@bc-forge/codegen", + "version": "0.1.0", + "description": "Code generation tool for bc-forge Soroban contract TypeScript bindings", + "main": "dist/index.js", + "bin": { + "bc-forge-codegen": "dist/index.js" + }, + "scripts": { + "build": "tsc", + "codegen": "node dist/index.js" + }, + "license": "MIT", + "dependencies": { + "@stellar/stellar-sdk": "^12.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.4.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/sdk/codegen/src/generate.ts b/sdk/codegen/src/generate.ts new file mode 100644 index 0000000..a3ddffa --- /dev/null +++ b/sdk/codegen/src/generate.ts @@ -0,0 +1,174 @@ +/** + * generate.ts + * + * Generates type-safe TypeScript client code from a parsed Soroban contract ABI. + * Produces: + * - TypeScript interfaces for all struct/union/enum types + * - A typed client class with one method per contract function + * - An error enum for all contract error cases + */ + +import { xdr } from '@stellar/stellar-sdk'; +import type { ContractAbi, ParsedFunc, ParsedStruct, ParsedUnion, ParsedEnum } from './parse-abi'; + +// ─── Type Mapping ───────────────────────────────────────────────────────────── + +function scSpecTypeToTs(typeDef: xdr.ScSpecTypeDef): string { + const kind = typeDef.switch().name; + + switch (kind) { + case 'scSpecTypeVoid': return 'void'; + case 'scSpecTypeBool': return 'boolean'; + case 'scSpecTypeU32': return 'number'; + case 'scSpecTypeI32': return 'number'; + case 'scSpecTypeU64': return 'bigint'; + case 'scSpecTypeI64': return 'bigint'; + case 'scSpecTypeU128': return 'bigint'; + case 'scSpecTypeI128': return 'bigint'; + case 'scSpecTypeU256': return 'bigint'; + case 'scSpecTypeI256': return 'bigint'; + case 'scSpecTypeString': return 'string'; + case 'scSpecTypeSymbol': return 'string'; + case 'scSpecTypeAddress': return 'string'; + case 'scSpecTypeBytes': return 'Buffer'; + case 'scSpecTypeBytesN': return 'Buffer'; + case 'scSpecTypeTimepoint': return 'bigint'; + case 'scSpecTypeDuration': return 'bigint'; + case 'scSpecTypeVal': return 'unknown'; + case 'scSpecTypeError': return 'number'; + + case 'scSpecTypeOption': { + const inner = scSpecTypeToTs(typeDef.option().valueType()); + return `${inner} | null`; + } + case 'scSpecTypeResult': { + const ok = scSpecTypeToTs(typeDef.result().okType()); + const err = scSpecTypeToTs(typeDef.result().errorType()); + return `{ ok: ${ok} } | { error: ${err} }`; + } + case 'scSpecTypeVec': { + const elem = scSpecTypeToTs(typeDef.vec().elementType()); + return `${elem}[]`; + } + case 'scSpecTypeMap': { + const k = scSpecTypeToTs(typeDef.map().keyType()); + const v = scSpecTypeToTs(typeDef.map().valueType()); + return `Map<${k}, ${v}>`; + } + case 'scSpecTypeTuple': { + const elems = typeDef.tuple().valueTypes().map(scSpecTypeToTs); + return `[${elems.join(', ')}]`; + } + case 'scSpecTypeUdt': { + return typeDef.udt().name().toString(); + } + + default: + return 'unknown'; + } +} + +// ─── Struct / Union / Enum Interfaces ───────────────────────────────────────── + +function generateStructInterface(s: ParsedStruct): string { + const doc = s.doc ? `/** ${s.doc} */\n` : ''; + const fields = s.fields + .map((f) => ` ${f.name}: ${scSpecTypeToTs(f.type)};`) + .join('\n'); + return `${doc}export interface ${s.name} {\n${fields}\n}`; +} + +function generateUnionType(u: ParsedUnion): string { + const doc = u.doc ? `/** ${u.doc} */\n` : ''; + const cases = u.cases.map((c) => { + if (c.types.length === 0) { + return ` | { tag: '${c.name}' }`; + } + const vals = c.types.map((t, i) => `value${i}: ${scSpecTypeToTs(t)}`).join('; '); + return ` | { tag: '${c.name}'; ${vals} }`; + }); + return `${doc}export type ${u.name} =\n${cases.join('\n')};`; +} + +function generateEnumType(e: ParsedEnum): string { + const doc = e.doc ? `/** ${e.doc} */\n` : ''; + const cases = e.cases.map((c) => ` ${c.name} = ${c.value},`).join('\n'); + return `${doc}export enum ${e.name} {\n${cases}\n}`; +} + +// ─── Error Enum ─────────────────────────────────────────────────────────────── + +function generateErrorEnum(errors: ContractAbi['errors'], contractName: string): string { + if (errors.length === 0) return ''; + const cases = errors.map((e) => { + const doc = e.doc ? ` /** ${e.doc} */\n` : ''; + return `${doc} ${e.name} = ${e.value},`; + }).join('\n'); + return `export enum ${contractName}Error {\n${cases}\n}`; +} + +// ─── Method Signatures ──────────────────────────────────────────────────────── + +function generateMethod(fn: ParsedFunc): string { + const doc = fn.doc + ? ` /**\n * ${fn.doc}\n */\n` + : ''; + + const params = fn.inputs + .map((inp) => `${inp.name}: ${scSpecTypeToTs(inp.type)}`) + .join(', '); + + const returnType = fn.outputs.length === 0 + ? 'void' + : fn.outputs.length === 1 + ? scSpecTypeToTs(fn.outputs[0]) + : `[${fn.outputs.map(scSpecTypeToTs).join(', ')}]`; + + return `${doc} ${fn.name}(${params}): Promise<${returnType}>;`; +} + +// ─── Client Interface ───────────────────────────────────────────────────────── + +function generateClientInterface(funcs: ParsedFunc[], contractName: string): string { + const methods = funcs.map(generateMethod).join('\n\n'); + return `export interface ${contractName}Client {\n${methods}\n}`; +} + +// ─── Main Generator ─────────────────────────────────────────────────────────── + +export function generateBindings(abi: ContractAbi, contractName: string): string { + const banner = [ + '// ─────────────────────────────────────────────────────────────────────────────', + `// Generated by @bc-forge/codegen — DO NOT EDIT`, + `// Contract: ${contractName}`, + `// Generated: ${new Date().toISOString()}`, + '// ─────────────────────────────────────────────────────────────────────────────', + '', + ].join('\n'); + + const parts: string[] = [banner]; + + // Enums + for (const e of abi.enums) { + parts.push(generateEnumType(e)); + } + + // Structs + for (const s of abi.structs) { + parts.push(generateStructInterface(s)); + } + + // Unions + for (const u of abi.unions) { + parts.push(generateUnionType(u)); + } + + // Error enum + const errorEnum = generateErrorEnum(abi.errors, contractName); + if (errorEnum) parts.push(errorEnum); + + // Client interface + parts.push(generateClientInterface(abi.funcs, contractName)); + + return parts.join('\n\n') + '\n'; +} diff --git a/sdk/codegen/src/index.ts b/sdk/codegen/src/index.ts new file mode 100644 index 0000000..40ecc48 --- /dev/null +++ b/sdk/codegen/src/index.ts @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/** + * index.ts — CLI entry point for @bc-forge/codegen + * + * Usage: + * bc-forge-codegen [output-path] [--name ] + * + * Defaults: + * output-path → sdk/src/generated-client.ts + * name → derived from wasm filename (snake_case → PascalCase) + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { parseWasm } from './parse-abi'; +import { generateBindings } from './generate'; + +function toPascalCase(str: string): string { + return str + .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase()) + .replace(/^(.)/, (_, c: string) => c.toUpperCase()); +} + +async function main(): Promise { + const args = process.argv.slice(2); + + if (args.length === 0 || args.includes('--help') || args.includes('-h')) { + console.log(` +Usage: bc-forge-codegen [output-path] [--name ] + + wasm-path Path to the compiled .wasm file + output-path Output .ts file (default: sdk/src/generated-client.ts) + --name Contract name for generated types (default: derived from filename) + +Example: + bc-forge-codegen target/wasm32-unknown-unknown/release/bc_forge_token.wasm +`); + process.exit(0); + } + + const wasmPath = args[0]; + if (!fs.existsSync(wasmPath)) { + console.error(`Error: WASM file not found: ${wasmPath}`); + process.exit(1); + } + + // Determine output path + let outputPath = args[1] && !args[1].startsWith('--') + ? args[1] + : path.resolve(__dirname, '../../../src/generated-client.ts'); + + // Determine contract name + const nameIdx = args.indexOf('--name'); + const contractName = nameIdx !== -1 && args[nameIdx + 1] + ? args[nameIdx + 1] + : toPascalCase(path.basename(wasmPath, '.wasm')); + + console.log(`Parsing WASM: ${wasmPath}`); + const abi = await parseWasm(wasmPath); + + console.log(` Found ${abi.funcs.length} function(s), ${abi.structs.length} struct(s), ` + + `${abi.unions.length} union(s), ${abi.enums.length} enum(s), ${abi.errors.length} error(s)`); + + const code = generateBindings(abi, contractName); + + // Ensure output directory exists + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, code, 'utf8'); + + console.log(`Generated: ${outputPath}`); +} + +main().catch((err) => { + console.error('codegen failed:', err.message); + process.exit(1); +}); diff --git a/sdk/codegen/src/parse-abi.ts b/sdk/codegen/src/parse-abi.ts new file mode 100644 index 0000000..0eea188 --- /dev/null +++ b/sdk/codegen/src/parse-abi.ts @@ -0,0 +1,134 @@ +/** + * parse-abi.ts + * + * Reads a compiled Soroban contract WASM file and extracts the contract spec + * (ABI) using the stellar-sdk contract.Client.fromWasm API. + */ + +import * as fs from 'fs'; +import { contract, xdr } from '@stellar/stellar-sdk'; + +export interface ParsedArg { + name: string; + type: xdr.ScSpecTypeDef; +} + +export interface ParsedFunc { + name: string; + doc: string; + inputs: ParsedArg[]; + outputs: xdr.ScSpecTypeDef[]; +} + +export interface ParsedError { + name: string; + value: number; + doc: string; +} + +export interface ParsedStruct { + name: string; + doc: string; + fields: ParsedArg[]; +} + +export interface ParsedUnion { + name: string; + doc: string; + cases: Array<{ name: string; types: xdr.ScSpecTypeDef[] }>; +} + +export interface ParsedEnum { + name: string; + doc: string; + cases: Array<{ name: string; value: number }>; +} + +export interface ContractAbi { + funcs: ParsedFunc[]; + errors: ParsedError[]; + structs: ParsedStruct[]; + unions: ParsedUnion[]; + enums: ParsedEnum[]; +} + +/** + * Parses a Soroban contract WASM file and returns the structured ABI. + */ +export async function parseWasm(wasmPath: string): Promise { + const wasmBuffer = fs.readFileSync(wasmPath); + + // Use Client.fromWasm which handles the contractspecv0 section extraction + const client = await contract.Client.fromWasm(wasmBuffer, { + contractId: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4', + networkPassphrase: 'Test SDF Network ; September 2015', + rpcUrl: 'http://localhost:8000/rpc', + }); + + return extractAbi(client.spec); +} + +function extractAbi(spec: contract.Spec): ContractAbi { + const funcs: ParsedFunc[] = spec.funcs().map((fn) => ({ + name: fn.name().toString(), + doc: fn.doc().toString(), + inputs: fn.inputs().map((inp) => ({ + name: inp.name().toString(), + type: inp.type(), + })), + outputs: fn.outputs(), + })); + + const errors: ParsedError[] = spec.errorCases().map((e) => ({ + name: e.name().toString(), + value: e.value(), + doc: e.doc().toString(), + })); + + const structs: ParsedStruct[] = []; + const unions: ParsedUnion[] = []; + const enums: ParsedEnum[] = []; + + for (const entry of spec.entries) { + const kind = entry.switch().name; + + if (kind === 'scSpecEntryUdtStructV0') { + const s = entry.udtStructV0(); + structs.push({ + name: s.name().toString(), + doc: s.doc().toString(), + fields: s.fields().map((f) => ({ + name: f.name().toString(), + type: f.type(), + })), + }); + } else if (kind === 'scSpecEntryUdtUnionV0') { + const u = entry.udtUnionV0(); + unions.push({ + name: u.name().toString(), + doc: u.doc().toString(), + cases: u.cases().map((c) => { + const caseName = c.switch().name; + if (caseName === 'scSpecUdtUnionCaseVoidV0') { + return { name: c.voidCase().name().toString(), types: [] }; + } else { + const tc = c.tupleCase(); + return { name: tc.name().toString(), types: tc.type() }; + } + }), + }); + } else if (kind === 'scSpecEntryUdtEnumV0') { + const e = entry.udtEnumV0(); + enums.push({ + name: e.name().toString(), + doc: e.doc().toString(), + cases: e.cases().map((c) => ({ + name: c.name().toString(), + value: c.value(), + })), + }); + } + } + + return { funcs, errors, structs, unions, enums }; +} diff --git a/sdk/codegen/tsconfig.json b/sdk/codegen/tsconfig.json new file mode 100644 index 0000000..3538526 --- /dev/null +++ b/sdk/codegen/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020", "WebWorker"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/sdk/package.json b/sdk/package.json index 72def70..525db9d 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -10,7 +10,9 @@ "test": "jest --passWithNoTests", "lint": "eslint 'src/**/*.ts'", "format": "prettier --write 'src/**/*.ts'", - "clean": "rm -rf dist" + "clean": "rm -rf dist", + "codegen:build": "cd codegen && npm ci && npm run build", + "codegen": "node codegen/dist/index.js" }, "keywords": [ "stellar", diff --git a/sdk/src/generated-client.ts b/sdk/src/generated-client.ts new file mode 100644 index 0000000..890a70e --- /dev/null +++ b/sdk/src/generated-client.ts @@ -0,0 +1,150 @@ +// ───────────────────────────────────────────────────────────────────────────── +// Generated by @bc-forge/codegen — DO NOT EDIT +// Contract: BcForgeToken +// Generated: 2026-05-30T16:16:57.010Z +// ───────────────────────────────────────────────────────────────────────────── + + +export interface Recipient { + address: string; + amount: bigint; +} + +export interface LockupInfo { + amount: bigint; + unlock_time: bigint; +} + +/** Information about an allowance, including amount and expiration. */ +export interface AllowanceInfo { + amount: bigint; + exp_ledger: number; +} + +export interface Proposal { + approvals: string[]; + creator: string; + description: string; + executed: boolean; +} + +export type DataKey = + | { tag: 'Admin' } + | { tag: 'PendingAdmin' } + | { tag: 'Allowance'; value0: string; value1: string } + | { tag: 'Balance'; value0: string } + | { tag: 'Name' } + | { tag: 'Symbol' } + | { tag: 'Decimals' } + | { tag: 'Supply' } + | { tag: 'ClawbackAdmin' } + | { tag: 'Lockup'; value0: string } + | { tag: 'ProposalAction'; value0: bigint }; + +/** Possible actions that can be proposed via multi-sig. */ +export type TokenAction = + | { tag: 'Mint'; value0: string; value1: bigint } + | { tag: 'Pause' } + | { tag: 'Unpause' }; + +/** Storage keys for lifecycle state. */ +export type LifecycleKey = + | { tag: 'Paused' }; + +/** Enumeration of available roles. */ +export type Role = + | { tag: 'Admin' } + | { tag: 'Minter' }; + +export type AdminKey = + | { tag: 'Admin' } + | { tag: 'Role'; value0: Role; value1: string } + | { tag: 'AdminPool' } + | { tag: 'Threshold' } + | { tag: 'Proposal'; value0: bigint } + | { tag: 'ProposalIdCounter' }; + +export enum BcForgeTokenError { + AlreadyInitialized = 1, + NotInitialized = 2, + InvalidAmount = 3, + InsufficientBalance = 4, + InsufficientAllowance = 5, + ContractPaused = 6, +} + +export interface BcForgeTokenClient { + burn(from: string, amount: bigint): Promise; + + mint(to: string, amount: bigint): Promise<{ ok: [] } | { error: TokenError }>; + + name(): Promise; + + pause(): Promise<{ ok: [] } | { error: TokenError }>; + + supply(): Promise; + + symbol(): Promise; + + approve(from: string, spender: string, amount: bigint, exp: number): Promise; + + balance(id: string): Promise; + + unpause(): Promise<{ ok: [] } | { error: TokenError }>; + + upgrade(new_wasm_hash: Buffer): Promise<{ ok: [] } | { error: TokenError }>; + + version(): Promise; + + clawback(from: string, to: string, amount: bigint): Promise<{ ok: [] } | { error: TokenError }>; + + decimals(): Promise; + + has_role(role: Role, address: string): Promise; + + transfer(from: string, to: string, amount: bigint): Promise; + + allowance(from: string, spender: string): Promise; + + burn_from(spender: string, from: string, amount: bigint): Promise; + + batch_mint(recipients: Recipient[]): Promise<{ ok: [] } | { error: TokenError }>; + + grant_role(role: Role, address: string): Promise; + + initialize(admin: string, decimal: number, name: string, symbol: string): Promise<{ ok: [] } | { error: TokenError }>; + + lock_tokens(user: string, amount: bigint, unlock_time: bigint): Promise<{ ok: [] } | { error: TokenError }>; + + revoke_role(role: Role, address: string): Promise; + + update_name(new_name: string): Promise<{ ok: [] } | { error: TokenError }>; + + pending_owner(): Promise; + + propose_owner(new_admin: string): Promise<{ ok: [] } | { error: TokenError }>; + + transfer_from(spender: string, from: string, to: string, amount: bigint): Promise; + + update_symbol(new_symbol: string): Promise<{ ok: [] } | { error: TokenError }>; + + batch_transfer(from: string, recipients: [string, bigint][]): Promise; + + propose_action(signer: string, action: TokenAction, description: string): Promise; + + set_admin_pool(pool: string[], threshold: number): Promise; + + cancel_transfer(): Promise<{ ok: [] } | { error: TokenError }>; + + withdraw_locked(user: string): Promise; + + accept_ownership(): Promise; + + approve_proposal(signer: string, proposal_id: bigint): Promise; + + execute_proposal(proposal_id: bigint): Promise; + + set_clawback_admin(clawback_admin: string): Promise; + + transfer_ownership(new_admin: string): Promise<{ ok: [] } | { error: TokenError }>; +}