Skip to content

About

CLI tool designed to generate language bindings for Stellar Soroban smart contracts.

Topics

Resources

Stars

11 stars

Watchers

3 watching

Forks

Repository files navigation

stellar-contract-bindings

stellar-contract-bindings is a CLI tool designed to generate language bindings for Stellar Soroban smart contracts.

This tool simplifies the process of interacting with Soroban contracts by generating the necessary code to call contract methods directly from your preferred programming language. Currently, it supports Python, Java, Flutter/Dart, PHP, Swift/iOS, and Kotlin Multiplatform. stellar-cli provides support for TypeScript and Rust.

Web Interface

We have a web interface for generating bindings. You can access via https://stellar-contract-bindings.fly.dev/.

Installation

You can install stellar-contract-bindings using pip:

pip install stellar-contract-bindings

Usage

Please check the help message for the most up-to-date usage information:

stellar-contract-bindings --help

The contract id may name any contract instance: one carrying its own Wasm, a Stellar Asset Contract, or a CAP-85 external executable reference, which is resolved through its owner contract automatically.

Examples

Python

stellar-contract-bindings python --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF --rpc-url https://mainnet.sorobanrpc.com --output ./bindings

Java

stellar-contract-bindings java --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF --rpc-url https://mainnet.sorobanrpc.com --output ./bindings --package com.example --class-name Client

Flutter/Dart

stellar-contract-bindings flutter --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF --rpc-url https://mainnet.sorobanrpc.com --output ./lib --class-name MyContract

PHP

stellar-contract-bindings php --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF --rpc-url https://mainnet.sorobanrpc.com --output ./generated --namespace MyApp\\Contracts --class-name MyContractClient

Swift/iOS

stellar-contract-bindings swift --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF --rpc-url https://mainnet.sorobanrpc.com --output ./Sources --class-name MyContract

Kotlin Multiplatform

stellar-contract-bindings kmp --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF --rpc-url https://mainnet.sorobanrpc.com --output ./src/commonMain/kotlin --package com.example.bindings --class-name MyContract

These commands will generate language-specific bindings for the specified contract and save them in the respective directories.

Using the Generated Binding

After generating the binding, you can use it to interact with your Soroban contract. Here's an example:

Python

from stellar_sdk import Network
from bindings import Client  # Import the generated bindings

contract_id = "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF"
rpc_url = "https://mainnet.sorobanrpc.com"
network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE

client = Client(contract_id, rpc_url, network_passphrase)
assembled_tx = client.hello(b"world")
print(assembled_tx.result())
# assembled_tx.sign_and_submit()

If the contract declares events in its SEP-48 spec (supported since Protocol 23), the generated Python bindings also include a typed class per event, a topic_filter() builder for getEvents, and a parse_event dispatcher that accepts xdr.ContractEvent, RPC EventInfo, or raw (topics, data) values.

Java

The generated file holds one class, Client by default, with a method per contract function and a nested class per contract type. It compiles against java-stellar-sdk 4.0.0 or newer and needs Lombok on the compile classpath.

import com.example.Client;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.stellar.sdk.KeyPair;
import org.stellar.sdk.Network;

public class Example {
    public static void main(String[] args) throws Exception {
        KeyPair signer = KeyPair.fromSecretSeed("S...");
        try (Client client = new Client(
                "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF",
                "https://mainnet.sorobanrpc.com",
                Network.PUBLIC,
                Client.MethodOptions.signedBy(signer))) {
            // Read a simulated result.
            byte[] name = "World".getBytes(StandardCharsets.UTF_8);
            List<byte[]> greeting = client.hello(name).result();

            // Sign and submit a transaction; the signer comes from the options.
            client.hello(name).signAndSubmit(null, true);

            // Options given to a call replace the client's defaults.
            client.hello(name, Client.MethodOptions.builder().baseFee(200).build());
        }
    }
}

A call that fails because the contract returned an error raises the SDK's SimulationFailedException. If the contract declares an error enum, say Error, the generated enum reads the case back out of that failure:

try {
    client.withdraw(amount).result();
} catch (SimulationFailedException e) {
    Client.Error.fromSimulationError(e).ifPresent(error -> log.warn("rejected: {}", error));
}

Every function has two overloads: one that takes only the contract arguments and uses the client's default options (a read-only simulation unless the client was created with other defaults), and one that also takes a MethodOptions (source account, signer, fee, timeouts, and whether to simulate and restore). The types map as follows:

Spec type Java type
bool, u32, i32, i64 boolean, long, int, long; boxed inside List, Map, Option and tuples
u64, u128, i128, u256, i256, timepoint, duration BigInteger
Symbol String
String, Bytes, BytesN<N> byte[]: a Soroban string is bytes with no encoding of its own, so the bindings pass it through untouched; the length is checked for BytesN
Address, MuxedAddress org.stellar.sdk.Address
Vec<T>, Map<K, V> List<T>, Map<K, V>
Option<T> T, with null for None
(A, B, ...) a generated Tuple2<A, B> and so on, built with Tuple2.of(a, b)
Result<T, E> a generated Result<T, E> holding either arm
struct a class with a builder, a constructor and getters
enum a Java enum with getValue()
error enum a Java enum with getValue() and fromSimulationError(...)
union an abstract class with one nested class per case; handle them with match(...), or getKind() / instanceof

Names that are Java keywords or collide with another name are renamed, and every rename is printed when the bindings are generated.

If the contract declares events in its SEP-48 spec, the bindings also include a class per event with matches, parse, getEventName() and a topicFilter() builder for getEvents, plus a parseEvent dispatcher accepting an XDR ContractEvent or an RPC EventInfo.

Flutter/Dart

import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart';
import 'lib/my_contract_client.dart'; // Import the generated bindings

void main() async {
  final sourceKeyPair = KeyPair.fromAccountId("GD5KKP3LHUDXLDCGKP55NLEOEHMS3Z4BS6IDDZFCYU3BDXUZTBWL7JNF");
  // or: final sourceKeyPair = KeyPair.fromSecretSeed("S...");
  
  // Create client instance
  final client = await MyContract.forContractId(
    sourceAccountKeyPair: sourceKeyPair,
    contractId: "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF",
    network: Network.PUBLIC,
    rpcUrl: "https://mainnet.sorobanrpc.com",
  );
  
  // Call contract method directly
  try {
    final result = await client.hello(to: "World");
    print("Contract response: $result");
  } catch (e) {
    print("Error calling contract: $e");
  }
  
  // Or build an assembled transaction for more control
  final assembledTx = await client.buildHelloTx(
    to: "World",
    methodOptions: MethodOptions(),
  );
}

PHP

<?php

use Soneso\StellarSDK\Crypto\KeyPair;
use Soneso\StellarSDK\Network;
use Soneso\StellarSDK\Soroban\Contract\ClientOptions;
use Soneso\StellarSDK\Soroban\Contract\MethodOptions;
use MyApp\Contracts\MyContractClient; // Import the generated bindings

// Initialize
$sourceKeyPair = KeyPair::fromAccountId("GD5KKP3LHUDXLDCGKP55NLEOEHMS3Z4BS6IDDZFCYU3BDXUZTBWL7JNF");
// or: $sourceKeyPair = KeyPair::fromSeed("S...")

// Create client instance
$options = new ClientOptions(
    sourceAccountKeyPair: $sourceKeyPair,
    contractId: "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF",
    network: Network::public(),
    rpcUrl: "https://mainnet.sorobanrpc.com"
);

$client = MyContractClient::forClientOptions($options);

// Call contract method directly
try {
    $result = $client->hello("World");
    echo "Contract response: " . $result . "\n";
} catch (Exception $e) {
    echo "Error calling contract: " . $e->getMessage() . "\n";
}

// Or build an assembled transaction for more control
$methodOptions = new MethodOptions();
$assembledTx = $client->buildHelloTx("World", $methodOptions);

Swift/iOS

import stellarsdk
import Foundation

// Import the generated bindings
// Assuming the generated file is named MyContract.swift

// Initialize
let sourceKeyPair = try! KeyPair.init(accountId: "GD5KKP3LHUDXLDCGKP55NLEOEHMS3Z4BS6IDDZFCYU3BDXUZTBWL7JNF")
// or: let sourceKeyPair = try! KeyPair.init(secretSeed: "S...")

// Create client instance
let options = ClientOptions(
    sourceAccountKeyPair: sourceKeyPair,
    contractId: "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF",
    network: .public,
    rpcUrl: "https://mainnet.sorobanrpc.com"
)

Task {
    do {
        let client = try await MyContract.forClientOptions(options: options)
        
        // Call contract method directly
        let result = try await client.hello(
            to: "World",
            methodOptions: nil,
            force: false
        )
        print("Contract response: \(result)")
        
        // Or build an assembled transaction for more control
        let methodOptions = MethodOptions()
        let assembledTx = try await client.buildHelloTx(
            to: "World",
            methodOptions: methodOptions
        )
        
    } catch {
        print("Error calling contract: \(error)")
    }
}

Kotlin Multiplatform

import com.soneso.stellar.sdk.KeyPair
import com.soneso.stellar.sdk.Network
import com.example.bindings.MyContract // Import the generated bindings

suspend fun example() {
    val sourceKeyPair = KeyPair.fromAccountId("GD5KKP3LHUDXLDCGKP55NLEOEHMS3Z4BS6IDDZFCYU3BDXUZTBWL7JNF")
    // or: val sourceKeyPair = KeyPair.fromSecretSeed("S...")

    // Create a client instance (no network round-trip; the binding encodes and decodes
    // arguments directly, so the contract spec is not fetched)
    val client = MyContract.forContract(
        contractId = "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF",
        rpcUrl = "https://mainnet.sorobanrpc.com",
        network = Network.PUBLIC,
    )

    // Call a contract method directly (read-only calls pass signer = null)
    val result = client.hello(
        to = "World",
        source = sourceKeyPair.getAccountId(),
        signer = null,
    )
    println("Contract response: $result")

    // Or build an assembled transaction for more control before signing and submitting
    val assembledTx = client.buildHelloTx(
        to = "World",
        source = sourceKeyPair.getAccountId(),
        signer = sourceKeyPair,
    )
    assembledTx.signAndSubmit(sourceKeyPair)
}

License

This project is licensed under the Apache-2.0 License. See the LICENSE file for details.

Contributing

Contributions are welcome! The project is designed to be easy to add support for other languages, please open an issue or submit a pull request for any improvements or bug fixes.

About

CLI tool designed to generate language bindings for Stellar Soroban smart contracts.

Topics

Resources

Stars

11 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages