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

# Interact with Stellar Soroban Contracts

> Invoke Soroban smart contracts on Stellar from a Fireblocks vault by submitting an unsigned transaction envelope with the SOROBAN_CALL operation.

> [!NOTE]
>
> Soroban contract calls are in Early Availability. Contact Fireblocks Support to enable them for your workspace.

Soroban is the smart contract platform on the Stellar network. To call a Soroban contract from a Fireblocks vault, you build an unsigned Stellar transaction envelope that invokes the contract, then submit it with the [Create a new transaction](https://developers.fireblocks.com/api-reference/transactions/create-a-new-transaction.md) endpoint using the `SOROBAN_CALL` operation.

The envelope you submit is the same artifact a Soroban dApp hands a wallet. Fireblocks decodes it, determines the footprint, resource fee, sequence number, and authorization entries from its own simulation, and signs a transaction it rebuilt from your invocation. Only the contract invocation itself is carried through unchanged.

## Before you start

Make sure the following are in place:

* **Soroban contract calls enabled for your workspace.** If they are not, the request fails with HTTP `403` and error code `16001` (`SOROBAN_CALL_FORBIDDEN`).
* **XLM in the source vault.** Fees are paid in XLM from the source vault's Stellar account. The vault needs enough XLM to cover the resource fee and the inclusion fee.
* **A whitelisted contract wallet** for the contract you want to call, unless your workspace has one-time addresses enabled. See [Whitelist the contract](#whitelist-the-contract).
* **A Policy rule for Soroban contract calls.** Without a matching rule, the call is blocked. See [Policy configuration](#policy-configuration).

## How Soroban contract calls work on Fireblocks

You build and submit the envelope:

1. **Build the envelope.** Use a Stellar SDK to build a `TransactionEnvelope` that carries one `invokeHostFunction` operation invoking your contract. Set the source account to the vault account's Stellar address and set a time bound. See [Envelope requirements](#envelope-requirements).
2. **Encode it.** Serialize the unsigned envelope to XDR and encode it in base64. Do not sign it.
3. **Submit it.** Call Create a new transaction with `operation` set to `SOROBAN_CALL` and the base64 envelope in `extraParameters.sorobanCallData.envelopeXdr`.

Fireblocks then processes the call in this order:

1. Validates the envelope.
2. Screens the transaction and checks that the vault holds XLM for fees.
3. Simulates the call to determine its footprint, resource fee, and authorization entries.
4. Checks that the call needs authorization from the source vault only.
5. Checks that the vault can pay the simulated resource fee plus the inclusion fee.
6. Evaluates the call against your Policy.
7. After approval, assigns the sequence number, assembles the final transaction, signs it, and broadcasts it.

Because simulation runs before Policy evaluation, a call that fails simulation never reaches your approvers.

For building Soroban transactions, see the [Stellar documentation on contract invocations](https://developers.stellar.org/docs/learn/fundamentals/contract-development/contract-interactions/stellar-transaction).

## Envelope requirements

The envelope must meet all of these conditions:

* It carries exactly one `invokeHostFunction` operation, and that operation invokes a contract.
* It is unsigned and carries no authorization entries.
* It has no memo and no fee-bump wrapper.
* Its source account is the Stellar address of the vault account you use as `source`. Multiplexed (`M...`) addresses are not accepted.
* It has a time bound with a non-zero `maxTime` that is still in the future.
* It carries no other preconditions: `ledgerBounds`, `minAccountSequence`, `minAccountSequenceAge`, `minAccountSequenceLedgerGap`, and `extraSigners` must be unset.
* Its declared fee does not exceed 1,000,000 stroops.
* The invoked contract is the contract of your `destination`. See [Whitelist the contract](#whitelist-the-contract).

Fireblocks sets the footprint, resource fee, sequence number, and authorization entries itself. Values you set for these in the envelope are not used. The inclusion fee of the signed transaction is also set by Fireblocks: your declared fee is only checked against the 1,000,000 stroop limit and against the vault's balance.

A Stellar transaction cannot exceed 132,096 bytes, which is about 176,128 base64 characters. The API rejects a request whose `envelopeXdr` is longer than 176,128 characters.

> [!WARNING]
>
> The API checks the request shape and that `envelopeXdr` is present. Fireblocks decodes and validates the envelope after the transaction is created, so an invalid envelope still returns a transaction ID. A transaction whose envelope fails validation is not signed and ends with status `FAILED` and sub-status `INVALID_CONTRACT_CALL_DATA`. Check the transaction status rather than treating a successful create response as acceptance.

## Whitelist the contract

Soroban contracts use the same contract wallet whitelisting as other contracts on Fireblocks:

1. Create a contract wallet with `POST /v1/contracts`, giving it a display name.
2. Add the contract's Stellar address (`C...`) to the wallet with `POST /v1/contracts/{contractId}/{assetId}`, using `XLM` on mainnet or `XLM_TEST` on testnet as the `assetId`.
3. Pass the contract wallet's ID as the `destination` of your `SOROBAN_CALL` transaction, with type `CONTRACT`.

Adding the address to the contract wallet returns the status `WAITING_FOR_APPROVAL`. Until the address is approved and active, a transaction to that wallet is rejected with a `400` error (code `1412`, destination contract suspended).

The contract your envelope invokes must be the address registered on that wallet. If the wallet does not exist or has no address for the asset, the request is rejected with a `400` error (code `1411`, destination contract not found). If the wallet holds a different address, the transaction is created and ends `FAILED` with sub-status `INVALID_CONTRACT_CALL_DATA`.

If one-time addresses are enabled for your workspace, you can instead pass the contract's `C...` address as a `ONE_TIME_ADDRESS` destination. The envelope must invoke that same address. If one-time addresses are not enabled, Policy blocks the call.

## Policy configuration

Soroban contract calls have their own Policy rule type, `SOROBAN_CALL`. Rules for other operation types, including `CONTRACT_CALL`, `MINT`, and `BURN`, do not apply to Soroban contract calls. If no `SOROBAN_CALL` rule matches a call, the call is blocked.

A `SOROBAN_CALL` rule matches on three conditions, and all three are required:

* **Initiator:** who submitted the call.
* **Source:** the vault account the call is made from.
* **Destination:** the contract being called.

The rule's action allows the call, blocks it, or sends it for two-tier approval. An allow rule must name the designated signers. `SOROBAN_CALL` rules do not support conditions on asset or amount. To restrict which contracts a vault can call, set the rule's destination.

## Example request

The SDK and CLI examples assume an initialized `fireblocks` client, as described in [Choose an interface](https://developers.fireblocks.com/transactions/create-transactions.md#choose-an-interface).

> [tab] SDK
>
> ```typescript TypeScript theme={"system"}
> import { TransferPeerPathType, TransactionOperation } from "@fireblocks/ts-sdk";
>
> const response = await fireblocks.transactions.createTransaction({
>   transactionRequest: {
>     operation: TransactionOperation.SorobanCall,
>     assetId: "XLM_TEST",
>     source: {
>       type: TransferPeerPathType.VaultAccount,
>       id: "<SOURCE_VAULT_ACCOUNT_ID>",
>     },
>     destination: {
>       type: TransferPeerPathType.Contract,
>       id: "<CONTRACT_WALLET_ID>",
>     },
>     extraParameters: {
>       sorobanCallData: {
>         envelopeXdr: "<BASE64_UNSIGNED_ENVELOPE_XDR>", // Required
>       },
>     },
>     externalTxId: "<YOUR_UNIQUE_ID>", // Optional
>   },
> });
> console.log(response.data);
> ```
>
> ```python Python theme={"system"}
> from fireblocks.models.transaction_request import TransactionRequest
> from fireblocks.models.transaction_operation import TransactionOperation
> from fireblocks.models.source_transfer_peer_path import SourceTransferPeerPath
> from fireblocks.models.destination_transfer_peer_path import DestinationTransferPeerPath
> from fireblocks.models.transfer_peer_path_type import TransferPeerPathType
> from fireblocks.models.extra_parameters import ExtraParameters
> from fireblocks.models.soroban_call_data import SorobanCallData
>
> transaction_request = TransactionRequest(
>     operation=TransactionOperation.SOROBAN_CALL,
>     asset_id="XLM_TEST",
>     source=SourceTransferPeerPath(
>         type=TransferPeerPathType.VAULT_ACCOUNT,
>         id="<SOURCE_VAULT_ACCOUNT_ID>",
>     ),
>     destination=DestinationTransferPeerPath(
>         type=TransferPeerPathType.CONTRACT,
>         id="<CONTRACT_WALLET_ID>",
>     ),
>     extra_parameters=ExtraParameters(
>         soroban_call_data=SorobanCallData(
>             envelope_xdr="<BASE64_UNSIGNED_ENVELOPE_XDR>",  # Required, pass the base64 string
>         ),
>     ),
>     external_tx_id="<YOUR_UNIQUE_ID>",  # Optional
> )
>
> future = fireblocks.transactions.create_transaction(
>     transaction_request=transaction_request,
> )
> print(future.result().data)  # create_transaction returns a Future
> ```
>
> ```java Java theme={"system"}
> import com.fireblocks.sdk.model.*;
> import java.util.Base64;
>
> // envelopeXdr is a byte[] in the Java SDK, which base64-encodes it on the wire.
> // Pass the decoded XDR bytes, not the bytes of the base64 string.
> byte[] envelope = Base64.getDecoder().decode("<BASE64_UNSIGNED_ENVELOPE_XDR>");
>
> TransactionRequest transactionRequest = new TransactionRequest()
>     .operation(TransactionOperation.SOROBAN_CALL)
>     .assetId("XLM_TEST")
>     .source(new SourceTransferPeerPath()
>         .type(TransferPeerPathType.VAULT_ACCOUNT)
>         .id("<SOURCE_VAULT_ACCOUNT_ID>"))
>     .destination(new DestinationTransferPeerPath()
>         .type(TransferPeerPathType.CONTRACT)
>         .id("<CONTRACT_WALLET_ID>"))
>     .extraParameters(new ExtraParameters()
>         .sorobanCallData(new SorobanCallData().envelopeXdr(envelope))) // Required
>     .externalTxId("<YOUR_UNIQUE_ID>"); // Optional
>
> CreateTransactionResponse transaction = fireblocks.transactions()
>     .createTransaction(transactionRequest, null, null)
>     .get()
>     .getData();
> System.out.println(transaction);
> ```

> [tab] API
>
> ```http theme={"system"}
> POST /v1/transactions
> ```
>
> ```json theme={"system"}
> {
>   "operation": "SOROBAN_CALL",
>   "assetId": "XLM_TEST",
>   "source": { "type": "VAULT_ACCOUNT", "id": "<SOURCE_VAULT_ACCOUNT_ID>" },
>   "destination": { "type": "CONTRACT", "id": "<CONTRACT_WALLET_ID>" },
>   "extraParameters": {
>     "sorobanCallData": { "envelopeXdr": "<BASE64_UNSIGNED_ENVELOPE_XDR>" }
>   },
>   "externalTxId": "<YOUR_UNIQUE_ID>"
> }
> ```
>
> Sign the request with a JWT. See [Signing a request](https://developers.fireblocks.com/reference/signing-a-request-jwt-structure.md).

> [tab] CLI
>
> ```bash theme={"system"}
> # Preview the request without sending it
> fireblocks transactions create-transaction \
>   --data '{"operation":"SOROBAN_CALL","assetId":"XLM_TEST","source":{"type":"VAULT_ACCOUNT","id":"<SOURCE_VAULT_ACCOUNT_ID>"},"destination":{"type":"CONTRACT","id":"<CONTRACT_WALLET_ID>"},"extraParameters":{"sorobanCallData":{"envelopeXdr":"<BASE64_UNSIGNED_ENVELOPE_XDR>"}},"externalTxId":"<YOUR_UNIQUE_ID>"}' \
>   --dry-run
>
> # Execute
> fireblocks transactions create-transaction \
>   --data '{"operation":"SOROBAN_CALL","assetId":"XLM_TEST","source":{"type":"VAULT_ACCOUNT","id":"<SOURCE_VAULT_ACCOUNT_ID>"},"destination":{"type":"CONTRACT","id":"<CONTRACT_WALLET_ID>"},"extraParameters":{"sorobanCallData":{"envelopeXdr":"<BASE64_UNSIGNED_ENVELOPE_XDR>"}},"externalTxId":"<YOUR_UNIQUE_ID>"}' \
>   --no-confirm
> ```

### Parameters

| Parameter | Required | Description |
| - | - | - |
| `operation` | Yes | Must be `SOROBAN_CALL`. |
| `assetId` | Yes | The fee asset: `XLM` on mainnet or `XLM_TEST` on testnet. This is not the token the call moves. Other assets are rejected. |
| `source` | Yes | A vault account. It signs the transaction and pays its fees. Its Stellar address must be the envelope's source account. |
| `destination` | Yes | The contract wallet for the contract you are calling, with type `CONTRACT`. When enabled for your workspace, you can instead use a `ONE_TIME_ADDRESS` holding a Stellar contract address (`C...`), without a memo or tag. |
| `amount` | No | Omit it or set it to `0`. A non-zero value is rejected. Any value the call moves is defined inside the contract invocation. |
| `extraParameters.sorobanCallData.envelopeXdr` | Yes | The base64-encoded, unsigned Stellar `TransactionEnvelope`. Maximum 176,128 characters. |
| `externalTxId` | No | Your unique ID for the request. A later request with the same value returns the original transaction instead of creating a new one. |

A Soroban contract call takes exactly one destination: `destinations` is not supported. `replaceTxByHash` is not supported.

## Limits and constraints

### Sequence numbers

Fireblocks assigns the sequence number of each Soroban contract call from the vault's Stellar account, after Policy approval. You do not set or manage it.

### Transaction validity window

The signed transaction is valid on the Stellar network for three hours from the moment Fireblocks assigns its sequence number, which happens after Policy approval. If it is not included in that window, it expires and cannot be broadcast. Submit a new request to try again. The `maxTime` you set in your envelope is checked only when the request is validated, and is not carried into the signed transaction.

### Cancelling

You can cancel a Soroban contract call until it is signed. A call rejected by an approver ends `CANCELLED`.

### Policy approval window

Policy must return a verdict within 10 minutes, including the time your approvers take on a two-tier rule. If it does not, the transaction ends `FAILED` with sub-status `TIMEOUT`.

### Authorization

Fireblocks supports calls that need authorization from the source vault only. A call whose simulation shows it needs authorization from any other address, such as a contract account or a second signer, fails before Policy evaluation. A call whose simulation shows it needs no authorization at all is not supported either: it passes Policy but fails afterwards with sub-status `INVALID_CONTRACT_CALL_DATA`.

### Fee estimation

The [Estimate transaction fee](https://developers.fireblocks.com/api-reference/transactions/estimate-transaction-fee.md) endpoint does not support the `SOROBAN_CALL` operation. Fireblocks determines the resource fee from its own simulation.

## Transaction outcomes

A Soroban contract call can end early at several points. The transaction's sub-status identifies the reason.

| When | What happened | Status |
| - | - | - |
| Before Policy | The envelope failed validation, or its contract does not match the destination. See [Envelope requirements](#envelope-requirements). | `FAILED` |
| Before Policy | The transaction did not pass screening. | `FAILED` or `REJECTED` |
| Before Policy | The vault does not hold enough XLM for the fees. | `FAILED`, sub-status `INSUFFICIENT_FUNDS_FOR_FEE` |
| Before Policy | Simulation failed, so the call cannot succeed as built. | `FAILED`, sub-status `SMART_CONTRACT_SIMULATION_FAILED` |
| Before Policy | The call needs authorization from an address other than the source vault. | `FAILED`, sub-status `UNSUPPORTED_TRANSACTION_TYPE` |
| Policy | A `SOROBAN_CALL` rule blocked the call, or no rule matched it. | `BLOCKED`, sub-status `BLOCKED_BY_POLICY` |
| Policy | An approver rejected the call. | `CANCELLED` |
| Policy | No verdict arrived within 10 minutes. | `FAILED`, sub-status `TIMEOUT` |
| After Policy | The call needs no authorization, so Fireblocks cannot sign it. | `FAILED`, sub-status `INVALID_CONTRACT_CALL_DATA` |
| After broadcast | The transaction expired before inclusion. | `FAILED`, sub-status `TX_OUTDATED` |
| After broadcast | The network rejected the transaction, for example because of a stale sequence number or too low a fee. | `FAILED`, sub-status `INVALID_NONCE_TOO_LOW` or `FAIL_ON_LOW_FEE` |
| After broadcast | The contract call failed on-chain. | `FAILED`, sub-status `SMART_CONTRACT_EXECUTION_FAILED` |
| After broadcast | The call executed successfully on-chain. | `COMPLETED` |

Fireblocks never rebuilds or resubmits a failed or expired call on your behalf. Fix the cause and submit a new request.

## Webhooks

Status changes for Soroban contract calls arrive through the standard Fireblocks transaction webhooks, with `SOROBAN_CALL` as the transaction's `operation`. If your webhook handler branches on the operation type, add `SOROBAN_CALL` so these transactions are not skipped.
