Skip to main content

Documentation Index

Fetch the complete documentation index at: https://v2-docs.exponent.finance/llms.txt

Use this file to discover all available pages before exploring further.

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. 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.

Create a builder

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

FieldTypeDescription
vaultExponentVaultLoaded vault instance
connectionConnectionRPC connection used for reads and sends
signerPublicKeyManager or allocator public key
pricesAccountExponentPricesOptional prefetched prices snapshot
computeUnitLimitnumberDefault compute unit limit for all steps. Defaults to 1_400_000
heapFrameBytesnumberDefault heap frame size for all steps. Defaults to 256 * 1024
autoManagePositionsbooleanWhether the builder should automatically manage tracked Kamino, CLMM, and yield positions. Defaults to true
squadsProgramPublicKeyOptional 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.
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] });
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.
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(...).

Step options

StepOptions applies to addActions(...):
FieldDescription
labelHuman-readable label for the step
computeUnitLimitPer-step compute unit limit override
heapFrameBytesPer-step heap frame override
priorityFeeOptional microLamports-per-CU priority fee
LoopscaleStepOptions applies to addLoopscaleResponse(...):
FieldDescription
labelHuman-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.

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.
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] });
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.
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.

Mix standard steps with Loopscale

Use addLoopscaleResponse(...) when you already have a raw response from LoopscaleClient.
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.

Build result

build() returns a VaultTransactionBuildResult:
FieldDescription
transactionSetsOrdered setup and sync transactions the builder assembled
lookupTableAddressesDeduplicated lookup tables referenced across the flow
labelsLabels 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:
{
  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.