AssembledTransaction

public final class AssembledTransaction : @unchecked Sendable

The main workhorse of SorobanClient. This class is used to wrap a transaction-under-construction and provide high-level interfaces to the most common workflows, while still providing access to low-level stellar-sdk transaction manipulation.

Most of the time, you will not construct an AssembledTransaction directly, but instead receive one as the return value of a SorobanClient.buildInvokeMethodTx method.

Let’s look at examples of how to use AssembledTransaction for a variety of use-cases:

1. Simple read call

Since these only require simulation, you can get the result of the call right after constructing your AssembledTransaction:


let clientOptions = ClientOptions(sourceAccountKeyPair: sourceAccountKeyPair,
                                  contractId: "C123…",
                                  network: Network.testnet,
                                  rpcUrl: "https://…")

let txOptions = AssembledTransactionOptions(clientOptions: clientOptions,
                                            methodOptions: MethodOptions(),
                                            method: "myReadMethod",
                                            arguments: args)
let tx = try await AssembledTransaction.build(options: txOptions)
let result = try tx.getSimulationData().returnedValue

While that looks pretty complicated, most of the time you will use this in conjunction with SorobanClient, which simplifies it to:

let result = try await client.invokeMethod(name: "myReadMethod", args: args)

2. Simple write call

For write calls that will be simulated and then sent to the network without further manipulation, only one more step is needed:

let tx = try await AssembledTransaction.build(options: txOptions)
let response = try await tx.signAndSend()
if response.status == GetTransactionResponse.STATUS_SUCCESS {
    let result = response.resultValue
}

If you are using it in conjunction with SorobanClient:

let result = try await client.invokeMethod(name: "myWriteMethod", args: args)

3. More fine-grained control over transaction construction

If you need more control over the transaction before simulating it, you can set various MethodOptions when constructing your AssembledTransaction. With a SorobanClient, this can be passed as an argument when calling invokeMethod or buildInvokeMethodTx :

let methodOptions = MethodOptions(fee: 10000,
                                  timeoutInSeconds: 20,
                                  simulate: false)

let tx = try await client.buildInvokeMethodTx(name: "myWriteMethod",
                                              args: args,
                                              methodOptions: methodOptions)

Since we’ve skipped simulation, we can now edit the raw transaction builder and then manually call simulate:

tx.raw?.setMemo(memo: Memo.text("Hello"))
try await tx.simulate()

If you need to inspect the simulation later, you can access it with let data = try tx.getSimulationData()

#### 4. Multi-auth workflows

Soroban, and Stellar in general, allows multiple parties to sign a transaction.

Let’s consider an Atomic Swap contract. Alice wants to give some of her Token A tokens to Bob for some of his Token B tokens.

 let swapMethodName = "swap"
 let amountA = SCValXDR.i128(Int128PartsXDR(hi: 0, lo: 1000))
 let minBForA = SCValXDR.i128(Int128PartsXDR(hi: 0, lo: 4500))

 let amountB = SCValXDR.i128(Int128PartsXDR(hi: 0, lo: 5000))
 let minAForB = SCValXDR.i128(Int128PartsXDR(hi: 0, lo: 950))
 let args:[SCValXDR] = [try SCValXDR.address(SCAddressXDR(accountId: aliceId)),
                        try SCValXDR.address(SCAddressXDR(accountId: bobId)),
                        try SCValXDR.address(SCAddressXDR(contractId: tokenAContractId)),
                        try SCValXDR.address(SCAddressXDR(contractId: tokenBContractId)),
                        amountA,
                        minBForA,
                        amountB,
                        minAForB]

Let’s say Alice is also going to be the one signing the final transaction envelope, meaning she is the invoker. So your app, she simulates the swap call:

let tx = try await atomicSwapClient.buildInvokeMethodTx(name: swapMethodName,
                                                        args: args)

But your app can’t signAndSend this right away, because Bob needs to sign it first. You can check this:

let whoElseNeedsToSign = try tx.needsNonInvokerSigningBy()

You can verify that whoElseNeedsToSign is an array of length 1, containing only Bob’s public key.

If you have Bob’s secret key, you can sign it right away with:

let bobsKeyPair = try KeyPair(secretSeed: "S...")
try await tx.signAuthEntries(signerKeyPair: bobsKeyPair)

But if you don’t have Bob’s private key, and e.g. need to send it to another server for signing, you can provide a callback function for signing the auth entry:

let bobPublicKeyKeypair = try KeyPair(accountId: bobsAccountId)
try await tx.signAuthEntries(signerKeyPair: bobPublicKeyKeypair, authorizeEntryCallback: { (entry, network) async throws in

       // You can send it to some other server for signing by encoding it as a base64xdr string
       let base64Entry = entry.xdrEncoded!

       // send for signing ...
       // and on the other server you can decode it:
       var entryToSign = try SorobanAuthorizationEntryXDR.init(fromBase64: base64Entry)

       // sign it
       try entryToSign.sign(signer: bobsSecretKeyPair, network: network)

       // encode as a base64xdr string and send it back
       let signedBase64Entry = entryToSign.xdrEncoded!

       // here you can now decode it and return it
       return try SorobanAuthorizationEntryXDR.init(fromBase64: signedBase64Entry)
})

To see an even more complicated example, where Alice swaps with Bob but the transaction is invoked by yet another party, check out in the SorobanClientTest.atomicSwapTest()

  • raw

    Unconstructed transaction envelope that can be modified before simulation.

    Declaration

    Swift

    public var raw: Transaction?
  • Whether the transaction has been signed by the source account.

    Declaration

    Swift

    public var signed: Transaction?
  • Soroban RPC simulation result containing resource costs, events, and return values.

    Declaration

    Swift

    public var simulationResponse: SimulateTransactionResponse?
  • tx

    Built transaction ready for submission with simulation results and resource limits applied.

    Declaration

    Swift

    public var tx: Transaction?
  • Configuration options for transaction assembly including network, contract, and method parameters.

    Declaration

    Swift

    public let options: AssembledTransactionOptions
  • Creates a new assembled transaction with specified configuration options.

    Declaration

    Swift

    public init(options: AssembledTransactionOptions)
  • build(options:) Asynchronous

    Builds an assembled transaction for invoking a contract method with automatic simulation.

    Declaration

    Swift

    public static func build(options: AssembledTransactionOptions) async throws -> AssembledTransaction
  • Builds an assembled transaction from a custom invoke host function operation.

    Declaration

    Swift

    public static func buildWithOp(operation: InvokeHostFunctionOperation, options: AssembledTransactionOptions) async throws -> AssembledTransaction
  • simulate(restore:) Asynchronous

    Simulates the transaction to calculate resource requirements and validate execution.

    Declaration

    Swift

    public func simulate(restore: Bool? = nil) async throws
  • Restores expired contract state using the restore preamble from simulation.

    Declaration

    Swift

    public func restoreFootprint(restorePreamble: RestorePreamble) async throws -> GetTransactionResponse
  • Signs the transaction with source account and submits it to the network.

    Declaration

    Swift

    public func signAndSend(sourceAccountKeyPair: KeyPair? = nil, force: Bool = false) async throws -> GetTransactionResponse
  • Signs the transaction envelope with the source account keypair.

    Declaration

    Swift

    public func sign(sourceAccountKeyPair: KeyPair? = nil, force: Bool = false) throws
  • send() Asynchronous

    Submits the signed transaction to the network and polls until completion. Returns the final transaction status including success/failure and return value.

    Declaration

    Swift

    public func send() async throws -> GetTransactionResponse
  • Returns the addresses of every node whose signature is void, across all authorization entries.

    All three credential arms are supported (ADDRESS, ADDRESS_V2, ADDRESS_WITH_DELEGATES). For WITH_DELEGATES entries, the top-level address and every unsigned delegate node (depth-first) are reported individually. A top-level signature alongside delegates is a legal pattern, so the top-level address is always included when its signature is void, even when delegates are present.

    WITH_DELEGATES send precheck: sign() and signAndSend() treat a WITH_DELEGATES entry as satisfied when every delegate node in the tree carries a non-void signature, regardless of the void top-level. Do not block the send flow solely because this method reports the top-level address of a fully-delegate-signed entry.

    .sourceAccount entries are ignored (they carry no explicit signer address).

    Declaration

    Swift

    public func needsNonInvokerSigningBy(includeAlreadySigned: Bool = false) throws -> [String]

    Parameters

    includeAlreadySigned

    When true, includes addresses whose signature is non-void. Defaults to false.

  • Determines if this is a read-only call requiring no signatures or network submission. Returns true if the call has no auth entries and writes no ledger data.

    Declaration

    Swift

    public func isReadCall() throws -> Bool
  • Retrieves parsed simulation data including auth entries and return value. Throws if transaction has not been simulated or simulation failed.

    Declaration

    Swift

    public func getSimulationData() throws -> SimulateHostFunctionResult
  • Signs authorization entries for multi-party transactions using a keypair or callback.

    Handles all three credential arms (ADDRESS, ADDRESS_V2, ADDRESS_WITH_DELEGATES). The credential arm is preserved on write-back; no arm coercion is performed.

    Delegate routing: For WITH_DELEGATES entries the signer address is matched against the top-level address AND every delegate node (depth-first) via the forAddress routing in SorobanAuthorizationEntryXDR.sign. When the signer matches a delegate node, the signature lands in that node; the top-level is left untouched unless the signer’s address also matches the top-level.

    Skipping policy: .sourceAccount entries are skipped silently; they are authorized by the transaction-envelope signer, not by auth-entry signing. An entry whose top-level address and delegate tree both differ from the signer is also silently skipped (multi-party transactions carry entries for different signers).

    Declaration

    Swift

    public func signAuthEntries(signerKeyPair: KeyPair, authorizeEntryCallback: ((_ entry: SorobanAuthorizationEntryXDR, _ network: Network) async throws -> SorobanAuthorizationEntryXDR)? = nil, validUntilLedgerSeq: UInt32? = nil) async throws