> For the complete documentation index, see [llms.txt](https://docs.veda.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.veda.tech/api/transaction-builder/transaction-builder-solana.md).

# Solana

Building ready-to-sign Solana transactions for vault actions.

The Solana-flavored surface of the [Transaction Builder](/api/transaction-builder.md). Same three-step workflow — discover routes, build a transaction, submit — but the addresses, transaction format, simulation model, and response shape all differ from the EVM path.

{% hint style="danger" %}
**Always verify the transaction independently before signing.** Deserialize `serialized_transaction_base64` with `VersionedTransaction.deserialize`, walk the instruction list, and confirm every program invocation and account matches what you asked the API to build. **Never rely solely on the API response for correctness.**
{% endhint %}

## What's different on Solana

|                 | EVM                                                         | Solana                                                                |
| --------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
| Addresses       | Hex (`0x…`)                                                 | Base58 pubkeys                                                        |
| Path chain slug | `ethereum`, `base`, …                                       | `solana`                                                              |
| Returned tx     | Hex calldata (`encoded_tx_data`)                            | Base64 v0 versioned transaction (`serialized_transaction_base64`)     |
| Simulation      | Tenderly (`tenderly_simulation_url`, `simulation_gas_used`) | Native Solana RPC (`simulation.units_consumed`, `simulation.logs`)    |
| Caller          | `msg.sender` must equal `strategist_address`                | `strategist_address` is the `feePayer` and signer of the versioned tx |
| Batching        | `POST /execute-multiple` batches actions atomically         | Not currently supported — call `POST /execute` once per action        |

Examples on this page assume:

```bash
export VEDA_API_KEY=veda_user_live_...
BASE=https://api.veda.tech/v1/tx-builder/api
VAULT=4dTwdU6t92Fvf3dCQoiztbijX4Ncbu6zHKe9c8rYxmAp
STRATEGIST=GGPhD3awtUsk4SXuwTcGMsqYbCDu9uNfeBrpe4zGaAom
```

That vault is Balanced Yield USDC on Solana — a good starting point since it exposes ITB deposits/withdrawals, CCTP bridging, and Orca swaps under one strategist.

***

## 1. List available actions

```
GET /actions/solana/{vault_address}/{strategist_address}
```

Returns the manifest of routes the strategist can call on the vault — same shape as the EVM version. See [Transaction Builder → List available actions](https://docs.veda.tech/api/transaction-builder/pages/eHmNQRPepjE7D1Q5DbLI#id-1.-list-available-actions) for a full field-by-field walk of the manifest. Solana-specific note: the field on each route object is `params` (not `parameters`).

```bash
curl -sS \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  "$BASE/actions/solana/$VAULT/$STRATEGIST"
```

**Response** (abridged — one route shown):

```json
{
  "itb_position_manager/deposit": {
    "name":            "Deposit",
    "protocol":        "itb_position_manager",
    "has_constraints": false,
    "params": [
      { "name": "strategy_name", "type": "str",     "choices": ["kraken_earn_pyusd"] },
      { "name": "asset",         "type": "str",     "choices": ["PYUSD"] },
      { "name": "amount",        "type": "Decimal", "choices": [] }
    ]
  },
  "cctp/bridge": { "…": "…" },
  "orca/swap":   { "…": "…" }
}
```

***

## 2. Inspect parameter constraints

```
GET /actions/solana/{vault_address}/{strategist_address}/constraints/{route}
```

When a route reports `has_constraints: true`, call this endpoint for the valid parameter combinations. The route slug in the path is percent-encoded (e.g. `orca%2Fswap`).

```bash
curl -sS \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  "$BASE/actions/solana/$VAULT/$STRATEGIST/constraints/orca%2Fswap"
```

Response is a JSON object with `route` and a `constraints` array — each entry is one valid combination of the correlated params for that route.

***

## 3. Build a single action

```
POST /execute/solana/{vault_address}/{strategist_address}/{route}
```

**Body:**

```json
{
  "params": {
    "strategy_name": "kraken_earn_pyusd",
    "asset":         "PYUSD",
    "amount":        "0.1"
  },
  "skip_sim": false
}
```

| Field      | Default | Notes                                                                                  |
| ---------- | ------- | -------------------------------------------------------------------------------------- |
| `params`   | `{}`    | Key-value map matching the route's `params` from `/actions`.                           |
| `skip_sim` | `false` | Skip native Solana simulation. Some routes require simulation — see [Errors](#errors). |

### Example — deposit

Deposits `0.1` PYUSD into the vault's ITB Position Manager strategy:

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "params": {
      "strategy_name": "kraken_earn_pyusd",
      "asset":         "PYUSD",
      "amount":        "0.1"
    },
    "skip_sim": false
  }' \
  "$BASE/execute/solana/$VAULT/$STRATEGIST/itb_position_manager/deposit"
```

You get back a JSON body containing (among other fields) `serialized_transaction_base64` — that's the transaction to sign and broadcast. Its exact top-level shape depends on the route — see [Response shapes](#response-shapes) below.

{% hint style="info" %}
`/execute-multiple` is **not currently supported on Solana**. Call `/execute` once per action; batching may come in a later version.
{% endhint %}

***

## Response shapes

Solana `/execute` responses come in **two shapes**. There's no top-level discriminator — check whether a `transaction` key is present.

### Shape A — nested `transaction` object

Used by `itb_position_manager/*` and `itb_supervised_loan/*`:

```
{
  schema_version, network, protocol, action,
  strategy_name, position_manager_address,
  asset, amount, raw_atomic_amount,
  finalized_authorization_slot,
  active_authorization_digests[],
  already_complete,           // true = no tx needed; `transaction` absent
  transaction: {
    encoding, transaction_version,
    serialized_transaction_base64,
    message_base64,
    signature_placeholders,
    required_signers[], server_signed_signers[],
    payer, strategist,
    recent_blockhash, last_valid_block_height,
    address_lookup_tables[],
    permission_group,
    simulation: {
      successful,             // boolean
      units_consumed,
      logs[],
      error,
      observed_slot
    }
  }
}
```

When `already_complete` is `true`, the API is telling you the action is a no-op onchain (e.g. `accept_ownership` on a strategy that's already accepted). The `transaction` key is absent.

### Shape B — flat

Used by `orca/swap`, `cctp/bridge`, `xstocks_ccip/bridge`, `raydium/swap`:

```
{
  schema_version, network, protocol, action,

  serialized_transaction_base64,   // top-level, not nested
  message_base64,
  encoding, transaction_version,
  signature_placeholders,
  required_signers[], server_signed_signers[],
  payer, strategist,
  recent_blockhash, last_valid_block_height,
  address_lookup_tables[],
  permission_group,
  finalized_authorization_slot,
  active_authorization_digests[],
  simulation: { successful, units_consumed, logs[], error, observed_slot },

  // ...plus route-specific fields (pool, token_in, token_out, amount,
  //    max_price, min_price, quote_token, digest_hex, topology_sha256,
  //    ticket_id, event_pubkey, destination_vault, …)
}
```

### Field cheatsheet (both shapes)

| Field                            | Notes                                                                                                   |
| -------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `serialized_transaction_base64`  | Base64 v0 transaction. `VersionedTransaction.deserialize(Buffer.from(…, "base64"))`.                    |
| `required_signers[]`             | Every pubkey that must sign before broadcast. Usually just the strategist.                              |
| `server_signed_signers[]`        | Pubkeys the API has already signed for (e.g. the CCTP relay key on `cctp/bridge`). Don't re-sign these. |
| `raw_atomic_amount`              | Amount in smallest token unit (PYUSD is 6 decimals, so `0.1 → "100000"`).                               |
| `simulation.successful`          | Whether the native Solana RPC simulation passed.                                                        |
| `simulation.units_consumed`      | Compute units consumed — useful for sizing the `ComputeBudget` on your submit path.                     |
| `simulation.logs[]`              | Raw program logs from the simulation. Inspect on `transaction_simulation_failed` errors.                |
| `already_complete`               | Shape A only. `true` = no-op; no `transaction` present.                                                 |
| `finalized_authorization_slot`   | Slot at which the vault's authorization state was read. Internal.                                       |
| `active_authorization_digests[]` | On-chain authorization PDAs used to build this transaction. Internal.                                   |

***

## Submitting the transaction

Deserialize the returned base64 v0 transaction, sign with the strategist, and broadcast:

```javascript
import { Connection, VersionedTransaction } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");

// Deserialize
const txBytes = Buffer.from(response.serialized_transaction_base64, "base64");
const tx = VersionedTransaction.deserialize(txBytes);

// Sign with the strategist. Signers already listed in `server_signed_signers`
// are pre-signed by the API — don't re-sign those.
tx.sign([strategistKeypair]);

// Broadcast
const signature = await connection.sendTransaction(tx, { skipPreflight: true });
await connection.confirmTransaction({
  signature,
  blockhash:            response.recent_blockhash,
  lastValidBlockHeight: response.last_valid_block_height,
});
```

Address lookup tables referenced by the v0 message are embedded and don't need to be fetched separately for signing.

For non-EOA custody (the common case — strategists are rarely hot wallets):

* **Squads (Solana multisig)** — propose the transaction with the deserialized instructions; execute after threshold is met.
* **Fordefi (Solana workflow)** — hand the serialized transaction to your Fordefi policy pipeline.
* **Any custody** that can sign a `VersionedTransaction` and enforce that the fee-payer / signer equals the strategist pubkey.

***

## Errors

Non-2xx responses use a `detail` envelope:

```json
{ "detail": { "code": "…", "message": "…", "details": { "…": "…" } } }
```

| HTTP  | Code                            | Meaning                                       | Fix                                                                                 |
| ----- | ------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------- |
| `400` | `action_simulation_required`    | This route cannot skip simulation.            | Retry with `skip_sim: false`.                                                       |
| `400` | `invalid_request`               | Generic validation failure — check `message`. | e.g. `amount` exceeds the vault's finalized token balance.                          |
| `422` | `transaction_simulation_failed` | Native Solana simulation failed.              | Inspect `details.logs`. Common cause: vault doesn't hold enough of the input token. |
| `422` | `route_unavailable`             | Protocol route isn't currently serviceable.   | Usually transient (e.g. no active Raydium digest window server-side). Retry later.  |

Routes that always require `skip_sim: false`:

* `itb_position_manager/accept_ownership`, `/deposit`, `/withdraw`
* `itb_supervised_loan/accept_ownership`, `/deposit`, `/withdraw`
* `orca/swap`
