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
| 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.
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(...):
| 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.
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:
| 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:
{
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.