> ## Documentation Index
> Fetch the complete documentation index at: https://developers.stellar.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Work with contract specs in Java, Python, and PHP

> A guide to understanding and interacting with Soroban smart contracts in different programming languages

## Introduction

Soroban smart contracts are powerful tools for building decentralized applications on the Stellar network. To interact with these contracts effectively, it's crucial to understand their specifications and how to use them in your programming language of choice.

A typical contract specification (spec) includes:

1. Data types used by the contract
2. Function definitions with their inputs and outputs
3. Error types that the contract may return

These details guide how you interact with the contract, regardless of the programming language you're using.

## Prerequisites

Before diving into contract interactions, ensure you have the following:

* Stellar CLI ([`stellar`](../../smart-contracts/getting-started/setup#install-the-stellar-cli)) installed
* A Soroban-compatible SDK for your programming language. View the [list of available SDKs](https://developers.stellar.org/docs/tools/sdks) to find one that suits your needs
* Access to a Stellar [RPC server](https://developers.stellar.org/docs) (local or on a test network)

For this guide, we will focus on the [Java](https://developers.stellar.org/docs/tools/sdks/client-sdks.md#java-sdk), [Python](https://developers.stellar.org/docs/tools/sdks/client-sdks.md#python-sdk), and [PHP](https://developers.stellar.org/docs/tools/sdks/client-sdks.md#php-sdk) SDKs for reference, but the concepts can also be applied to other languages.

## What are contract specs?

A contract spec is just like an ABI (Application Binary Interface) in Ethereum. It is a standardized description of a smart contract's interface, typically in JSON format or XDR format. It defines the contract's functions, data structures, events, and errors in a way that external applications can understand and use.

This specification serves as a crucial bridge between the smart contract and client applications, enabling them to interact without needing to know the contract's internal implementation details.

## Stellar Contract Spec

When you compile a smart contract using the Rust SDK, the resulting Wasm file includes a special section containing a complete description of your contract's interface types. This is often referred to as the contract's `spec` or `contract spec`. Stellar smart contract specification, known as the `contract spec`, is a foundational element for interacting with contracts and for building dApps on the Stellar network. The contract Spec provides a robust and fully typed definitions for interacting with smart contracts, offering functionality equivalent to Ethereum's ABI while addressing its limitations.

The contract spec serves as a standardized interface for interacting with Stellar smart contracts. Similar to Ethereum's ABIs, but with key advantages:

* On-chain Availability: Every contract spec is stored on-chain
* Developer Comments: Comments from the contract author are preserved
* Seamless communication between contracts and external applications
* Ecosystem-wide compatibility with tools like wallets, explorers, and SDKs

This standardization simplifies integrations and accelerates the development process.

## Fully Typed Contract Definitions

The Contract Spec enforces fully typed definitions for all contract functions, inputs, and outputs. This ensures that:

* Developers can define contract behavior explicitly, reducing ambiguity.
* Type mismatches and runtime errors are minimized, leading to more reliable smart contracts.
* Tools can provide intelligent suggestions and validations during development.

By embedding type safety at the protocol level, Stellar’s Contract Spec creates a more predictable and robust development environment.

## Comparison to Ethereum ABI

Stellar’s Contract Spec shares many similarities with Ethereum’s ABI but also introduces enhancements:

| Feature | Ethereum ABI | Stellar Contract Spec |
| - | - | - |
| Fully typed contracts | Partial | Yes |
| Decoding and validation | Manual or external | Built-in |
| Security focus | Moderate | High (type safety enforced) |

## Generating contract specs

[The Stellar CLI](https://github.com/stellar/stellar-cli) provides a command to generate a contract spec from a contract's source code. This process is easy but requires you to have the Wasm binary of the contract.

Sometimes, you may not have access to the contract's source code or the ability to compile it. In such cases, you must use the [`stellar contract fetch`](../../../tools/cli/stellar-cli#stellar-contract-fetch) command to download the contract's Wasm binary and generate the spec.

Finally, we use the [`stellar bindings`](../../../tools/cli/stellar-cli#stellar-contract-bindings-json) command to generate the contract spec from the Wasm binary.

The Stellar Lab has a [Contract Explorer](https://lab.stellar.org/smart-contracts/contract-explorer) also provides the ability to view and to download a contract's contract spec.

### Fetching the contract binary

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract fetch --network-passphrase 'Test SDF Network ; September 2015' --rpc-url https://soroban-testnet.stellar.org --id CONTRACT_ID --out-file contract.wasm
```

### Generating the contract spec from Wasm

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract bindings json --wasm contract.wasm > abi.json
```

## Understanding the contract specification

The ABI (Application Binary Interface) specification for Stellar smart contracts includes several key components that define how to interact with the contract. Let's examine these in detail with examples:

1. **Functions:** Functions are defined with their name, inputs, and outputs. They represent the callable methods of the contract. They can be used for writing data to the contract and reading data from the contract.

   Example:

   ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
   {
     "type": "function",
     "name": "mint",
     "inputs": [
       {
         "name": "contract",
         "value": { "type": "address" }
       },
       {
         "name": "minter",
         "value": { "type": "address" }
       },
       {
         "name": "to",
         "value": { "type": "address" }
       },
       {
         "name": "amount",
         "value": { "type": "i128" }
       }
     ],
     "outputs": [
       {
         "type": "result",
         "value": { "type": "tuple", "elements": [] },
         "error": { "type": "error" }
       }
     ]
   }
   ```

   This defines a `mint` function that takes four parameters and returns either an empty tuple or an error. Notice the type of each parameter: `address` for Stellar account addresses, `i128` for 128-bit integers, etc.

2. **Structs:** Structs define complex data types with multiple fields.

   Example:

   ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
   {
     "type": "struct",
     "name": "ClaimableBalance",
     "fields": [
       {
         "name": "amount",
         "value": { "type": "i128" }
       },
       {
         "name": "claimants",
         "value": {
           "type": "vec",
           "element": { "type": "address" }
         }
       },
       {
         "name": "time_bound",
         "value": {
           "type": "custom",
           "name": "TimeBound"
         }
       },
       {
         "name": "token",
         "value": { "type": "address" }
       }
     ]
   }
   ```

   This defines a `ClaimableBalance` struct with four fields.

3. **Unions:** Unions represent variables that can be one of several types.

   Example:

   ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
   {
     "type": "union",
     "name": "DataKey",
     "cases": [
       {
         "name": "Init",
         "values": []
       },
       {
         "name": "Balance",
         "values": []
       }
     ]
   }
   ```

   This defines a `DataKey` union that can be either `Init` or `Balance`.

4. **Custom Types:** Custom types refer to other defined types in the ABI.

   Example:

   ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
   {
     "name": "time_bound",
     "value": {
       "type": "custom",
       "name": "TimeBound"
     }
   }
   ```

   This refers to a custom `TimeBound` type defined elsewhere in the ABI.

5. **Vector Types:** Vectors represent arrays of a specific type.

   Example:

   ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
   {
     "name": "claimants",
     "value": {
       "type": "vec",
       "element": { "type": "address" }
     }
   }
   ```

   This defines a vector of addresses.

6. **Primitive Types:** These include basic types like `i128` (128-bit integer), `u64` (64-bit unsigned integer), `address`, etc.

   Example:

   ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
   {
     "name": "amount",
     "value": { "type": "i128" }
   }
   ```

These specifications are crucial for encoding and decoding data when interacting with the contract. For example:

* When calling the `mint` function, you must provide four parameters: three addresses and a 128-bit integer.
* If a function returns a `ClaimableBalance`, you would expect to receive a struct with an amount (i128), a vector of addresses (claimants), a TimeBound object, and an address (token).
* If a function could return an `Error`, it will most likely fail at simulation and you won't need to decode the result.

## Soroban types

Before we dive into interacting with Stellar smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](https://developers.stellar.org/docs/learn/fundamentals/contract-development/types/built-in-types.md). Here are some of the common types:

* `u32`: Unsigned 32-bit integer
* `u64`: Unsigned 64-bit integer
* `i32`: Signed 32-bit integer
* `i64`: Signed 64-bit integer
* `u128`: Unsigned 128-bit integer
* `i128`: Signed 128-bit integer
* `bool`: Boolean
* `string`: UTF-8 encoded string
* `vec`: Variable-length array
* `address`: Stellar account address
* `map`: Key-value map
* `symbol`: A small string used mainly for function names and map keys

In this guide and the SDKs, these types are represented as `ScU32`, `ScU64`, `ScI32`, `ScI64`, `ScU128`, `ScI128`, `ScBool`, `ScString`, `ScVec`, `ScAddress`, `ScMap`, and `ScSymbol` respectively.

Every other complex type can be derived using these basic types but these types do not really map to values in the programming languages. The Stellar SDKs provide helper classes to work with these types.

## Working with native Soroban types

One of the most common tasks when working with Stellar smart contracts is converting between Stellar smart contract types and native types in your programming language. In this guide, we will go over some common conversions and show how they can be used to invoke contracts with the help of the contract spec.

In most SDKs, the `ScVal` class or function is used to convert between Soroban types and native types.

> [!NOTE]
>
> The JSON code block shows the contract spec, while RUST code blocks show the contract for each example.

### 1. Invoking a contract function with no parameters

We will be using the `increment` function of the sample [increment contract](https://github.com/stellar/soroban-examples/tree/main/increment) to exemplify this. The `increment` function takes no parameters and increments the counter by 1.

In this scenario, there is no need for conversions and passing the value `null` as contract arguments is sufficient in most SDKs.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contractimpl]
impl IncrementContract {
    /// Increment increments an internal counter, and returns the value.
    pub fn increment(env: Env) -> u32 {
        // Get the current count.
        let mut count: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0); // If no value set, assume 0.
        log!(&env, "count: {}", count);
        // Increment the count.
        count += 1;

        // Save the count.
        env.storage().instance().set(&COUNTER, &count);
        env.storage().instance().extend_ttl(50, 100);

        // Return the count to the caller.
        count
    }
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "function",
    "doc": "Increment increments an internal counter, and returns the value.",
    "name": "increment",
    "inputs": [],
    "outputs": [
      {
        "type": "u32"
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# pip install --upgrade stellar-sdk
from stellar_sdk import SorobanServer, soroban_rpc, Account, Asset, Keypair, Network, TransactionBuilder

def send_transaction() -> soroban_rpc.SendTransactionResponse:
    server = SorobanServer(server_url='https://soroban-testnet.stellar.org', client=None)

    root_keypair = Keypair.from_secret(
        "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    )
    root_account = Account(account=root_keypair.public_key, sequence=1)
    contract_id = "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE"
    transaction = (
        TransactionBuilder(
            source_account=root_account,
            network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
            base_fee=100,
        )
        .append_invoke_contract_function_op(
            contract_id=contract_id,
            function_name="increment",
            parameters=[],
            source=root_keypair.public_key,
        )
        # mark this transaction as valid only for the next 30 seconds
        .set_timeout(30)
        .build()
    )
    transaction.sign(root_keypair)
    response = server.send_transaction(transaction)
    return response

response = send_transaction()

print("status", response.status)
print("hash:", response.hash)
print("status:", response.status)
print("errorResultXdr:", response.error_result_xdr)
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
// implementation 'network.lightsail:stellar-sdk:3.1.0'
import org.stellar.sdk.KeyPair;
import org.stellar.sdk.Network;
import org.stellar.sdk.SorobanServer;
import org.stellar.sdk.Transaction;
import org.stellar.sdk.TransactionBuilder;
import org.stellar.sdk.TransactionBuilderAccount;
import org.stellar.sdk.xdr.InvokeHostFunctionOp;
import org.stellar.sdk.operations.InvokeHostFunctionOperation;
import org.stellar.sdk.exception.PrepareTransactionException;
import org.stellar.sdk.exception.NetworkException;
import org.stellar.sdk.responses.sorobanrpc.SendTransactionResponse;

public class SendTransactionExample {

    public static void main(String[] args) {
        SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org");
        try {
            // Replace with your source account's public key
            String sourcePublicKey = "GBSBL6FBPX5UHKL4AZCPUU6PXKUBYMKRUN3L4YQ4V2CCWSE7YMN2HYPB";

            // Replace with your source account's secret key
            String sourceSecretSeed = "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
            KeyPair sourceKeyPair = KeyPair.fromSecretSeed(sourceSecretSeed);

            // Decode the source account's public key to TransactionBuilderAccount
            TransactionBuilderAccount account = server.getAccount(sourceKeyPair.getAccountId());

            // Define the contract ID and function to invoke
            String contractId = "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE";
            String functionName = "increment";

            InvokeHostFunctionOperation operation =
                InvokeHostFunctionOperation.invokeContractFunctionOperationBuilder(
                  contractId,
                  functionName,
                  null
                )
                .build();

            Transaction unpreparedTransaction =
                new TransactionBuilder(account, Network.TESTNET)
                  .setBaseFee(Transaction.MIN_BASE_FEE)
                  .addOperation(operation)
                  .setTimeout(300)
                  .build();

            Transaction transaction;
            try {
                transaction = server.prepareTransaction(unpreparedTransaction);
            } catch (PrepareTransactionException e) {
                throw new RuntimeException("Prepare transaction failed", e);
            } catch (NetworkException e) {
                throw new RuntimeException("Network error", e);
            }

            // Sign the transaction
            transaction.sign(sourceKeyPair);

            // Send the transaction using the SorobanServer
            SendTransactionResponse response = server.sendTransaction(transaction);

            System.out.println("Status: " + response.getStatus());
            System.out.println("Transaction Hash: " + response.getHash());
            System.out.println("Latest Ledger: " + response.getLatestLedger());
            System.out.println("Ledger Close Time: " + response.getLatestLedgerCloseTime());
        } catch (Exception e) {
            System.err.println("An error has occurred:");
            e.printStackTrace();
        }
    }
}

```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php

namespace App\Models;

use Soneso\StellarSDK\TransactionBuilder;
use Soneso\StellarSDK\Soroban\SorobanServer;
use Soneso\StellarSDK\SEP\Derivation\Mnemonic;
use Soneso\StellarSDK\Crypto\KeyPair;
use Soneso\StellarSDK\Network;
use Soneso\StellarSDK\InvokeContractHostFunction;
use Soneso\StellarSDK\InvokeHostFunctionOperationBuilder;
use Soneso\StellarSDK\Soroban\Requests\SimulateTransactionRequest;

class Invoker
{
    public function invoker2()
    {
        $server = new SorobanServer("https://soroban-testnet.stellar.org");
        $accountASeed = "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        $accountAKeyPair = Keypair::fromSeed($accountASeed);
        $accountAId = "GBWIPGMM27M7O43NIHS6MXMVBDDI6WUYXWCJX7FL7DBA6B4ITXBC6JPN";
        $accountA = $server->getAccount($accountAId);
        $network = Network::testnet();
        $contractId = "CAMN24E6KNIXQBYPJJ4K7XRCUUJUMYSJRCSLZ2WOO6WJTSKIXAZWNYHK";

        $invokeContractHostFunction = new InvokeContractHostFunction($contractId, "increment", null);
        $builder = new InvokeHostFunctionOperationBuilder($invokeContractHostFunction);
        $op = $builder->build();

        $transaction = (new TransactionBuilder($accountA))
            ->addOperation($op)->build();

        $request = new SimulateTransactionRequest($transaction);
        $simulateResponse = $server->simulateTransaction($request);

        $transaction->setSorobanTransactionData($simulateResponse->getTransactionData());
        $transaction->addResourceFee($simulateResponse->minResourceFee);
        $transaction->sign($accountAKeyPair, $network);

        $server->sendTransaction($transaction);
    }
}
```

> [!NOTE]
>
> Subsequent examples will show code blocks for using the contract spec only to reduce redundancy.

### 2. Invoking a contract function with one or more parameters

Generally, this involves passing in a native `array` (not a `ScVec`) of parameters to the contract function.

We will be using the `hello` function of the sample [Hello World contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) to exemplify this.

We know from the spec that the `hello` function takes a string parameter and returns a vector of strings. In this scenario, we need to convert the string parameter to a `ScString` type before passing it to the contract.

This process is convenient using the `ScVal` class or function in most SDKs.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contract]
pub struct HelloContract;

#[contractimpl]
impl HelloContract {
    pub fn hello(env: Env, to: String) -> Vec<String> {
        vec![&env, String::from_str(&env, "Hello"), to]
    }
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "function",
    "doc": "",
    "name": "hello",
    "inputs": [
      {
        "doc": "",
        "name": "to",
        "value": {
          "type": "string"
        }
      }
    ],
    "outputs": [
      {
        "type": "vec",
        "element": {
          "type": "string"
        }
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import Keypair, Network, SorobanServer, TransactionBuilder, scval
.....
tx = (
  TransactionBuilder(source, network_passphrase, base_fee=100)
  .set_timeout(300)
  .append_invoke_contract_function_op(
      contract_id=contract_id,
      function_name="hello",
      parameters=[
          scval.to_string("John"),
      ]
).build())
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
import org.stellar.sdk.scval.Scv;
import org.stellar.sdk.xdr.SCVal;
// .....
List<SCVal> contractArgs = new ArrayList<SCVal>();
contractArgs.add(Scv.toString("John"));
InvokeHostFunctionOperation operation = InvokeHostFunctionOperation
                .invokeContractFunctionOperationBuilder(contractId, "hello", contractArgs).build();
TransactionBuilder transaction = new TransactionBuilder(source, Network.TESTNET);
Transaction tx = transaction.addOperation(operation).build();
```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
$arg = \Soneso\StellarSDK\Xdr\XdrSCVal::forString("John");
$invokeContractHostFunction = new InvokeContractHostFunction($contractId, "hello", [$arg]);
$builder = new InvokeHostFunctionOperationBuilder($invokeContractHostFunction);
$op = $builder->build();

$transaction = (new TransactionBuilder($accountA))
    ->addOperation($op)->build();
```

### 3. Getting responses from contracts

Data returned from contracts is also in `ScVal` format and need to be converted to native types in your programming language.

We will still be using the `hello` function of the sample [Hello World contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) to exemplify this.

We know from the Spec that the `hello` function takes a string parameter and returns a vec of strings. In this scenario, we need to convert the value returned from an `ScVec` of `ScString` type to `array` of `string` before making use of it.

Steps:

* Extract an `ScVec` from the return value
* Extract each `ScString` from the `ScVec`
* Convert each `ScString` to a native string

This process is convenient using the `ScVal` class or function in most SDKs.

Ideally, to retrieve this value, we need to use the [`getTransaction`](../../../data/apis/rpc/api-reference/methods/getTransaction) RPC method using the response hash of the transaction that invoked the contract function.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contract]
pub struct HelloContract;

#[contractimpl]
impl HelloContract {
    pub fn hello(env: Env, to: String) -> Vec<String> {
        vec![&env, String::from_str(&env, "Hello"), to]
    }
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "function",
    "doc": "",
    "name": "hello",
    "inputs": [
      {
        "doc": "",
        "name": "to",
        "value": {
          "type": "string"
        }
      }
    ],
    "outputs": [
      {
        "type": "vec",
        "element": {
          "type": "string"
        }
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import SorobanServer, soroban_rpc
from stellar_sdk import xdr as stellar_xdr
from stellar_sdk.soroban_rpc import GetTransactionStatus

def get_transaction(hash: str) -> soroban_rpc.GetTransactionResponse:
    server = SorobanServer(server_url='https://soroban-testnet.stellar.org', client=None)
    tx = server.get_transaction(hash)
    return tx

get_transaction_data = get_transaction("7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121")

if get_transaction_data.status == GetTransactionStatus.SUCCESS:
    transaction_meta = stellar_xdr.TransactionMeta.from_xdr(
        get_transaction_data.result_meta_xdr
    )
    result = transaction_meta.v4.soroban_meta.return_value
    output = []
    for x in result.vec.sc_vec:
        decoded_string = x.str.sc_string.decode()
        output.append(decoded_string)
    print(f"transaction result: {output}")
else:
    print(f"Transaction failed: {get_transaction_data.result_xdr}")
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
import org.stellar.sdk.responses.sorobanrpc.GetTransactionResponse;
import org.stellar.sdk.xdr.SCVal;
import org.stellar.sdk.xdr.TransactionMeta;
import org.stellar.sdk.SorobanServer;
import java.util.ArrayList;
import java.util.List;

public static void main(String[] args) {
  SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org");
  try {
    GetTransactionResponse tx = server
        .getTransaction("7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121");

    if (tx.getStatus() == GetTransactionResponse.GetTransactionStatus.SUCCESS) {
      List<String> output = new ArrayList<String>();
      String base64Xdr = tx.getResultMetaXdr();

      // convert the string to a result
      SCVal[] result = TransactionMeta.fromXdrBase64(base64Xdr).getV4()
          .getSorobanMeta().getReturnValue().getVec()
          .getSCVec();
      for (SCVal x : result) {
        output.add(x.getStr().getSCString().toString());
      }

      System.out.println("transaction result: " + output.toString());
    } else {
      System.out.println("Transaction failed: " + tx.getStatus());
    }
  } catch (Exception e) {
    System.err.println("An error has occurred:");
    e.printStackTrace();
  }
}
```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php
use Soneso\StellarSDK\Soroban\Responses\GetTransactionResponse;
use Soneso\StellarSDK\Soroban\SorobanServer;

class Test {
 public function getTx()
    {
        $txhash = "7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121";
        $server = new SorobanServer("https://soroban-testnet.stellar.org");
        $statusResponse = $server->getTransaction($txhash);
        $status = $statusResponse->status;
        $resultArr = [];

        if ($status == GetTransactionResponse::STATUS_FAILED) {
            print ("Transaction failed: " . $statusResponse->error . PHP_EOL);
        } else if ($status == GetTransactionResponse::STATUS_SUCCESS) {
            $resultValue = $statusResponse->getResultValue();
            $resVec = $resultValue->vec;
            foreach ($resVec as $strVal) {
                $resultArr[] = $strVal->str;
            }
            print_r($resultArr);
        }
        return $resultArr;
    }
}
```

## Working with complex data types

As described in [this guide](https://developers.stellar.org/docs/learn/fundamentals/contract-development/types/custom-types.md), there are some other variants of data structure supported by Soroban. They are

* `Struct` with named fields
* `Struct` with unnamed fields
* `Enum` (Unit and Tuple Variants)
* `Enum` (Integer Variants)

We would be looking at how these variants translate to the spec and how to construct them in the different SDKs.

### Struct with named fields

Structs with named values when converted to ABI or spec are represented as a `ScMap` where each value has the key in `ScSymbol` and the value in the underlying type.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct State {
    pub count: u32,
    pub last_incr: u32,
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "struct",
    "doc": "",
    "name": "State",
    "fields": [
      {
        "doc": "",
        "name": "count",
        "value": {
          "type": "u32"
        }
      },
      {
        "doc": "",
        "name": "last_incr",
        "value": {
          "type": "u32"
        }
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import scval
scval.to_map(
    {
        scval.to_symbol("count"): scval.to_u32(0),
        scval.to_symbol("last_incr"): scval.to_u32(0),
    }
)
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
import org.stellar.sdk.xdr.SCVal;
import org.stellar.sdk.scval.Scv;
import java.util.LinkedHashMap;

LinkedHashMap<SCVal, SCVal> map = new LinkedHashMap<SCVal, SCVal>();
map.put(Scv.toSymbol("count"), Scv.toUint32(0));
map.put(Scv.toSymbol("last_incr"), Scv.toUint32(0));
SCVal val = Scv.toMap(map);
```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php
use Soneso\StellarSDK\Xdr\XdrSCVal;

XdrSCVal::forMap(
    [
        XdrSCVal::forSymbol("count") => XdrSCVal::forU32(0),
        XdrSCVal::forSymbol("last_incr") => XdrSCVal::forU32(0),
    ]
);
```

### Struct with unnamed fields

Structs with unnamed values when converted to ABI or spec are represented as a `ScVal` where each value has the underlying type.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct State(pub u32, pub u32);
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "struct",
    "doc": "",
    "name": "State",
    "fields": [
      {
        "doc": "",
        "value": {
          "type": "u32"
        }
      },
      {
        "doc": "",
        "value": {
          "type": "u32"
        }
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import scval
scval.to_vec(
    [
        scval.to_uint32(0),
        scval.to_uint32(0),
    ]
)
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
import org.stellar.sdk.xdr.SCVal;
import org.stellar.sdk.scval.Scv;
import java.util.LinkedHashMap;

List<SCVal> vec = new ArrayList<SCVal>();
vec.add(Scv.toUint32(0));
vec.add(Scv.toUint32(0));
SCVal val = Scv.toVec(vec);
```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php
use Soneso\StellarSDK\Xdr\XdrSCVal;

XdrSCVal::forVec(
    [
        XdrSCVal::forU32(0),
        XdrSCVal::forU32(0),
    ]
);
```

### Enum (unit and tuple variants)

Enums are generally represented with `ScVec`, their unit types are represented as `ScSymbol` and their tuple variants are represented as the underlying types.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Enum {
    A,
    B(u32),
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "union",
    "doc": "",
    "name": "Enum",
    "cases": [
      {
        "doc": "",
        "name": "A",
        "values": []
      },
      {
        "doc": "",
        "name": "B",
        "values": [
          {
            "type": "u32"
          }
        ]
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import scval
scval.to_vec(
    [
        scval.to_symbol("A"),
        scval.to_map(
            {
                scval.to_symbol("B"): scval.to_uint32(0),
            }
        ),
    ]
)
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
import org.stellar.sdk.xdr.SCVal;
import org.stellar.sdk.scval.Scv;
import java.util.LinkedHashMap;

List<SCVal> vec = new ArrayList<SCVal>();
vec.add(Scv.toSymbol("A"));
LinkedHashMap<SCVal, SCVal> map = new LinkedHashMap<SCVal, SCVal>();
map.put(Scv.toSymbol("B"), Scv.toUint32(0));
vec.add(Scv.toMap(map));
SCVal val = Scv.toVec(vec);
```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php
use Soneso\StellarSDK\Xdr\XdrSCVal;

XdrSCVal::forVec(
    [
        XdrSCVal::forSymbol("A"),
        XdrSCVal::forMap(
            [
                XdrSCVal::forSymbol("B") => XdrSCVal::forU32(0),
            ]
        ),
    ]
);
```

### Enum (integer variants)

Enums are generally represented with `ScVec`, the integer variant has no keys so it's just a `ScVec` of the underlying type.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[contracttype]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum Enum {
    A = 1,
    B = 2,
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "struct",
    "doc": "",
    "name": "Enum",
    "fields": [
      {
        "doc": "",
        "name": "A",
        "type": "u32"
      },
      {
        "doc": "",
        "name": "B",
        "type": "u32"
      }
    ]
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import scval
scval.to_vec(
    [
        scval.to_uint32(0),
        scval.to_uint32(0),
    ]
)
```

```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
import org.stellar.sdk.xdr.SCVal;
import org.stellar.sdk.scval.Scv;
import java.util.LinkedHashMap;

List<SCVal> vec = new ArrayList<SCVal>();
vec.add(Scv.toUint32(0));
vec.add(Scv.toUint32(0));
SCVal val = Scv.toVec(vec);
```

```php PHP theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php
use Soneso\StellarSDK\Xdr\XdrSCVal;

XdrSCVal::forVec(
    [
        XdrSCVal::forU32(0),
        XdrSCVal::forU32(0),
    ]
);
```

### A complex example

Let's use the [timelock contract](https://github.com/stellar/soroban-examples/blob/main/timelock/src/lib.rs) example to show how to interact with a contract that has complex data types.

This example uses a `TimeBound` struct that has a `TimeBoundKind` enum as one of its fields, which are parameters to the `deposit` function. This example combines most of the concepts we have discussed so far.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
#[derive(Clone)]
#[contracttype]
pub enum TimeBoundKind {
    Before,
    After,
}

#[derive(Clone)]
#[contracttype]
pub struct TimeBound {
    pub kind: TimeBoundKind,
    pub timestamp: u64,
}

#[contracttype]
#[contractimpl]
impl ClaimableBalanceContract {
    pub fn deposit(
        env: Env,
        from: Address,
        token: Address,
        amount: i128,
        claimants: Vec<Address>,
        time_bound: TimeBound,
    )  {}
}
```

```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "type": "union",
    "doc": "",
    "name": "TimeBoundKind",
    "cases": [
      {
        "doc": "",
        "name": "Before",
        "values": []
      },
      {
        "doc": "",
        "name": "After",
        "values": []
      }
    ]
  },
  {
    "type": "struct",
    "doc": "",
    "name": "TimeBound",
    "fields": [
      {
        "doc": "",
        "name": "kind",
        "value": {
          "type": "custom",
          "name": "TimeBoundKind"
        }
      },
      {
        "doc": "",
        "name": "timestamp",
        "value": {
          "type": "u64"
        }
      }
    ]
  },
  {
    "type": "function",
    "doc": "",
    "name": "deposit",
    "inputs": [
      {
        "doc": "",
        "name": "from",
        "value": {
          "type": "address"
        }
      },
      {
        "doc": "",
        "name": "token",
        "value": {
          "type": "address"
        }
      },
      {
        "doc": "",
        "name": "amount",
        "value": {
          "type": "i128"
        }
      },
      {
        "doc": "",
        "name": "claimants",
        "value": {
          "type": "vec",
          "element": {
            "type": "address"
          }
        }
      },
      {
        "doc": "",
        "name": "time_bound",
        "value": {
          "type": "custom",
          "name": "TimeBound"
        }
      }
    ],
    "outputs": []
  }
]
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import scval, SorobanServer, Keypair, Network, TransactionBuilder

secret = "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
rpc_server_url = "https://soroban-testnet.stellar.org:443"
contract_id = "CAIKIZOT2LXM2WBEPGTZTPHHTGVHGLEOSI4WE6BOHWIBHJOKHPMCOPLO"

network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE

kp = Keypair.from_secret(secret)
soroban_server = SorobanServer(rpc_server_url)
source = soroban_server.load_account(kp.public_key)

# Let's build a transaction that invokes the `deposit` function.
tx = (
    TransactionBuilder(source, network_passphrase, base_fee=1000)
    .set_timeout(300)
    .append_invoke_contract_function_op(
        contract_id=contract_id,
        function_name="deposit",
        parameters=[
            scval.to_address(kp.public_key),
            scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
            scval.to_int128(1),
            scval.to_vec(
                [
                    scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
                ]
            ),
            scval.to_map(
                {
                    scval.to_symbol("kind"): scval.to_vec(
                        [
                            scval.to_symbol("Before"),
                        ]
                    ),
                    scval.to_symbol("timestamp"): scval.to_uint64(12346),
                }
            ),
        ],
    )
    .build()
)
```

## Reading contract events

Reading contract events is similar to reading transaction results. You can use the [`getEvents`](../../../data/apis/rpc/api-reference/methods/getEvents) RPC method to get the list of events associated with a contract.

One common convention is that small strings like function names, enum keys, and event topics are represented as `ScSymbol` in the contract spec.

However, event topics can be any `scval` type depending on the contract implementation.

In the example below, we will be encoding the mint to `ScSymbol` before querying it, and also encoding the addresses to `ScAddress`. Even after getting the event, we will need to parse the topics and value to get the actual values again from xdr base 64 to their corresponding types before then converting it to native types.

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
let address_1: &Address = "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".into();
let address_2: &Address = "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".into();
let count: i128 = 1;
env.events()
    .publish((symbol_short!("mint"), address_1, address_2), count);
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from stellar_sdk import SorobanServer, scval, xdr as stellar_xdr
from stellar_sdk.exceptions import NotFoundError, BadResponseError
from stellar_sdk.soroban_server import EventFilter, EventFilterType

def get_events():
    server = SorobanServer("https://soroban-testnet.stellar.org")
    # Define the request parameters
    start_ledger = 835020
    contract_id = "CDLYESZILKBHBRSPKQCQ3Q4K4N6MBI6UIQR3QXJ5L6WSYXC4EMTHSNNX"
    topic = [
            scval.to_symbol("mint").to_xdr(),
            scval.to_address("GALIALRZJ5EU2IJJSIQEA3D3ZIEHK5HPBHZJFUEPTGQU3MYEKKIUINTY").to_xdr(),
            scval.to_address("GC45QSBFYHGQUIWWQEOZ43INQGXX57CSSAABWRZ325H7MNFIFWZ56FD4").to_xdr(),
        ]

    try:
        # Use the get_events method directly
        events_response = server.get_events(
            start_ledger=start_ledger,
            filters=[
                EventFilter(
                    event_type=EventFilterType.CONTRACT,
                    contract_ids=[contract_id],
                    topics=[topic]
                )
            ],
            limit=20
        )

        # Process the response
        print(f"Latest ledger: {events_response.latest_ledger}")
        for event in events_response.events:
            print(f"Event ID: {event.id}")
            print(f"Contract ID: {event.contract_id}")
            for _topic in event.topic:
                if _topic is None:
                    continue
                sc_val = stellar_xdr.SCVal.from_xdr(_topic)
                if sc_val.sym is not None:
                    print(f"Topic: {scval.from_symbol(sc_val)}")
                if sc_val.address is not None:
                    print(f"Topic: {scval.from_address(sc_val).address}")
            if event.value is not None:
                value_sc_val = stellar_xdr.SCVal.from_xdr(event.value)
                if value_sc_val.i128 is not None:
                    print(f"Value: {scval.from_int128(value_sc_val)}")
            print("---")

    except NotFoundError:
        print("No events found for the given parameters.")
    except BadResponseError as e:
        print(f"Error occurred: {str(e)}")
```

```bash bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getEvents",
    "params": {
      "startLedger": 1190000,
      "filters": [
        {
          "type": "contract",
          "contractIds": [
            "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
          ],
          "topics": [
            [
              "AAAADwAAAARtaW50",
              "AAAAEgAAAAAAAAAAFoAuOU9JTSEpkiBAbHvKCHV07wnyktCPmaFNswRSkUQ=",
              "AAAAEgAAAAAAAAAAudhIJcHNCiLWgR2ebQ2Br378UpAAG0c710/2NKgts98=",
            ]
          ]
        }
      ],
      "pagination": {
        "limit": 20
      }
    }
  }' \
  https://soroban-testnet.stellar.org
```

## Wrapping Up

As we've seen, working with Soroban smart contracts across different programming languages isn't rocket science, but it does require some careful attention to detail. The key takeaways:

* Always start with a solid understanding of your contract's spec
* Get comfortable with converting between native types and Soroban's quirky data structures
* Don't be intimidated by complex data types - they're just puzzles waiting to be solved
* When in doubt, consult your SDK's documentation for language-specific nuances
