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

# VaultTransactionBuilder

> Orchestrate multi-step Strategy Vault execution and mixed Loopscale flows

`VaultTransactionBuilder` is the default high-level execution API for manager flows. It sits on top of the low-level sync transaction helpers and is the best choice when you want to sequence multiple strategy steps, mix standard vault actions with Loopscale responses, or let the SDK manage setup instructions, price refreshes, lookup tables, and Loopscale co-signing for you.

<Note>
  For pre-flight state checks before sending a transaction, see [VaultAssertion](/developer-strategy-vaults/vault-assertion). It returns an `allow` / `warn` / `block` report you can use to gate `send()` calls.
</Note>

If you want low-level control instead of a builder:

* Use `createVaultSyncTransactions(...)` when you want wrapped sync transaction results but still want the SDK to fan a logical action out into multiple sequential transactions when needed.
* Use `createVaultSyncTransaction(...)` only when you specifically require exactly one sync transaction result.

For raw Loopscale response handling, see [Loopscale Instructions](/developer-strategy-vaults/loopscale-instructions).

## Create a builder

```typescript theme={null}
import {
  ExponentVault,
  VaultTransactionBuilder,
} from "@exponent-labs/exponent-sdk";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const manager = Keypair.fromSecretKey(/* your keypair */);
const vaultAddress = new PublicKey("...");

const vault = await ExponentVault.load({ connection, address: vaultAddress });

const builder = VaultTransactionBuilder.create({
  vault,
  connection,
  signer: manager.publicKey,
  autoManagePositions: true,
});
```

The builder resolves the Squads vault PDA from `vault.state.squadsVault`.

### Config fields

| Field                 | Type                                    | Description                                                                                                   |
| --------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `vault`               | `ExponentVault`                         | Loaded vault instance                                                                                         |
| `connection`          | `Connection`                            | RPC connection used for reads and sends                                                                       |
| `signer`              | `PublicKey`                             | Manager or allocator public key                                                                               |
| `pricesAccount`       | `ExponentPrices`                        | Optional prefetched prices snapshot                                                                           |
| `computeUnitLimit`    | `number`                                | Default compute unit limit for all steps. Defaults to `1_400_000`                                             |
| `heapFrameBytes`      | `number`                                | Default heap frame size for all steps. Defaults to `256 * 1024`                                               |
| `autoManagePositions` | `boolean`                               | Whether the builder should automatically manage tracked Kamino, CLMM, and yield positions. Defaults to `true` |
| `squadsProgram`       | `PublicKey`                             | Optional Squads program override                                                                              |
| `loopscale`           | `{ baseUrl?: string; debug?: boolean }` | Optional Loopscale client config for prepared and co-signed steps                                             |

## Add standard vault steps

Each `addActions()` call becomes a separate Squads sync transaction.

```typescript theme={null}
import {
  KaminoMarket,
  VaultTransactionBuilder,
  kaminoAction,
} from "@exponent-labs/exponent-sdk";
import BN from "bn.js";

const result = await VaultTransactionBuilder
  .create({
    vault,
    connection,
    signer: manager.publicKey,
  })
  .addActions([
    kaminoAction.deposit(KaminoMarket.MAIN, "USDC", new BN(100_000_000)),
  ], {
    label: "deposit-usdc",
    computeUnitLimit: 1_200_000,
  })
  .addActions([
    kaminoAction.borrow(KaminoMarket.MAIN, "SOL", new BN(10_000_000)),
  ], {
    label: "borrow-sol",
    priorityFee: 500,
  })
  .build();

await result.send({ signers: [manager] });
```

<Note>
  If you pass multiple actions in one `addActions([...])` array, the builder packs them into one sync transaction. Large combinations can exceed Solana's transaction size limit.
</Note>

<Tip>
  A single `addActions([...])` step can still expand into multiple sequential transaction sets when the SDK has to split a smart Kamino Vault withdraw across reserve-specific sync transactions. That same fan-out is available at the low level through `createVaultSyncTransactions(...)`.
</Tip>

### Step options

`StepOptions` applies to `addActions(...)`:

| Field              | Description                                |
| ------------------ | ------------------------------------------ |
| `label`            | Human-readable label for the step          |
| `computeUnitLimit` | Per-step compute unit limit override       |
| `heapFrameBytes`   | Per-step heap frame override               |
| `priorityFee`      | Optional microLamports-per-CU priority fee |

`LoopscaleStepOptions` applies to `addLoopscaleResponse(...)`:

| Field   | Description                                 |
| ------- | ------------------------------------------- |
| `label` | Human-readable label for the Loopscale step |

Loopscale steps only accept `label`. Compute overrides stay on the raw Loopscale response and are not reapplied by the builder.

## AMM swap (Market Two)

`marketTwoAction.tradePt`, `marketTwoAction.buyYt`, and `marketTwoAction.sellYt` plug into the builder like any other standard vault step. Each `addActions(...)` call becomes its own Squads sync transaction.

```typescript theme={null}
import {
  VaultTransactionBuilder,
  marketTwoAction,
} from "@exponent-labs/exponent-sdk";
import { PublicKey } from "@solana/web3.js";

const result = await VaultTransactionBuilder
  .create({
    vault,
    connection,
    signer: manager.publicKey,
  })
  .addActions([
    marketTwoAction.tradePt({
      market: new PublicKey("..."),
      netTraderPt: 1_000_000_000n,    // positive = buy PT, negative = sell PT
      syConstraint: 950_000_000n,
    }),
  ], {
    label: "amm-trade-pt",
  })
  .build();

await result.send({ signers: [manager] });
```

See [Exponent Core Instructions](/developer-strategy-vaults/exponent-core-instructions#amm-market-two-swaps) for the full Market Two surface and the matching `createVaultSyncTransaction` flow.

## Kamino Vault to Kamino Farm sequence

Use the builder when you want to deposit into a Kamino Vault and then stake the resulting shares into a Kamino Farm.

```typescript theme={null}
import BN from "bn.js";
import {
  ExponentVault,
  VaultTransactionBuilder,
  kaminoFarmAction,
  kaminoVaultAction,
} from "@exponent-labs/exponent-sdk";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const manager = Keypair.fromSecretKey(/* your keypair */);
const vaultAddress = new PublicKey("...");
const kaminoVault = new PublicKey("...");
const farmState = new PublicKey("...");

const vault = await ExponentVault.load({ connection, address: vaultAddress });

const depositResult = await VaultTransactionBuilder
  .create({
    vault,
    connection,
    signer: manager.publicKey,
  })
  .addActions([
    kaminoVaultAction.deposit({
      vault: kaminoVault,
      amount: new BN(100_000_000),
    }),
  ], {
    label: "kamino-vault-deposit",
  })
  .build();

await depositResult.send({ signers: [manager] });
await vault.reload(connection);

const stakeResult = await VaultTransactionBuilder
  .create({
    vault,
    connection,
    signer: manager.publicKey,
  })
  .addActions([
    kaminoFarmAction.stake({
      farmState,
      amount: "ALL",
    }),
  ], {
    label: "kamino-farm-stake",
  })
  .build();

await stakeResult.send({ signers: [manager] });
```

<Warning>
  This flow is sequential, not atomic. The deposit must land before the stake step runs, so the example sends one builder result, reloads the vault, and then sends the second builder result.
</Warning>

<Tip>
  The builder defaults `autoManagePositions` to `true`. For this flow, that means the vault can track the minted Kamino Vault shares and the resulting Kamino Farm position automatically. Make sure the matching Kamino Vault and Kamino Farm policies already exist, and make sure the matching Exponent `KaminoVault` price entry already exists before you run the deposit step.
</Tip>

## Mix standard steps with Loopscale

Use `addLoopscaleResponse(...)` when you already have a raw response from `LoopscaleClient`.

```typescript theme={null}
import {
  KaminoMarket,
  LoopscaleClient,
  VaultTransactionBuilder,
  kaminoAction,
} from "@exponent-labs/exponent-sdk";
import BN from "bn.js";

const client = new LoopscaleClient({
  connection,
  userWallet: vault.state.squadsVault,
});

const response = await client.depositStrategy({
  strategy: strategyAddress,
  amount: 500_000_000,
});

const result = await VaultTransactionBuilder
  .create({
    vault,
    connection,
    signer: manager.publicKey,
  })
  .addActions([
    kaminoAction.deposit(KaminoMarket.MAIN, "USDC", new BN(100_000_000)),
  ], {
    label: "kamino-deposit",
  })
  .addLoopscaleResponse(response, {
    label: "loopscale-deposit",
  })
  .build();

await result.send({ signers: [manager] });
```

`result.send(...)` only calls Loopscale MPC co-signing for transaction sets that require it.

## Manager: fill a withdrawal

`managerAction.fillWithdrawal({...})` fills one depositor withdrawal account from the vault's token entries. Use it to clear the [withdrawal queue](/developer-strategy-vaults/concepts#withdrawal-queue) without leaving the builder pipeline.

```typescript theme={null}
import {
  VaultTransactionBuilder,
  managerAction,
} from "@exponent-labs/exponent-sdk";
import { PublicKey } from "@solana/web3.js";

const result = await VaultTransactionBuilder
  .create({
    vault,
    connection,
    signer: manager.publicKey,
  })
  // Fill the entire requested amount from all positive token entries:
  .addActions([
    managerAction.fillWithdrawal({
      withdrawalAccount: new PublicKey("..."),
      amount: { kind: "full" },
    }),
  ], { label: "fill-withdrawal-full" })
  // Or cap the fill in base units and choose specific token entries:
  .addActions([
    managerAction.fillWithdrawal({
      withdrawalAccount: new PublicKey("..."),
      amount: { kind: "up-to-base", maxBaseAmountAtomic: 250_000_000n },
      tokenEntryIndices: [0, 2],
    }),
  ], { label: "fill-withdrawal-bounded" })
  .build();

await result.send({ signers: [manager] });
```

| Parameter           | Type                                                                                                | Description                                                                                         |
| ------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `withdrawalAccount` | `PublicKey`                                                                                         | Required. The withdrawal account to fill                                                            |
| `amount`            | `{ kind: "full" } \| { kind: "up-to-base"; maxBaseAmountAtomic: bigint \| number \| string \| BN }` | Required. Either fill in full, or cap by base-unit atomic amount                                    |
| `tokenEntryIndices` | `number[]`                                                                                          | Optional. Restrict source token entries by index. Omit to use every positive balance in vault order |

Add multiple `fillWithdrawal` actions in one builder call to fill multiple withdrawal accounts in the same flow.

## Build result

`build()` returns a `VaultTransactionBuildResult`:

| Field                            | Description                                               |
| -------------------------------- | --------------------------------------------------------- |
| `transactionSets`                | Ordered setup and sync transactions the builder assembled |
| `lookupTableAddresses`           | Deduplicated lookup tables referenced across the flow     |
| `labels`                         | Labels for the assembled transaction sets                 |
| `send({ signers, commitment? })` | Sends the transaction sets sequentially                   |

`send()` is sequential, not atomic. If a later transaction fails, earlier ones may already have landed.

## Ephemeral ALT handling

If the vault's existing lookup tables do not cover every account in the flow, `send()` can create an ephemeral address lookup table automatically. In that case, the returned `SendResult` includes:

```typescript theme={null}
{
  signatures: string[],
  ephemeralAlt: { address: PublicKey, authority: PublicKey } | null
}
```

When `ephemeralAlt` is non-null, you can deactivate and later close that lookup table to reclaim rent.
