The core instructions module enables vault managers and allocators to strip base tokens (e.g., LSTs) into PT (Principal Tokens) and YT (Yield Tokens), and merge them back. All interactions are executed as Squads sync transactions validated against onchain policies.
For background on how stripping and merging works, see Core Concepts.
Transaction Structure
Every core interaction goes through createVaultSyncTransaction, which returns three parts:
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions, // Array of VaultInstruction descriptors
owner, // Vault PDA (the Squads smart account)
connection,
policyPda, // Policy authorizing this execution
vaultPda, // Squads vault PDA
signer, // The allocator or manager wallet
vaultAddress, // ExponentStrategyVault PDA (auto-resolves hook accounts)
});
// Send all three parts in order
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);
| Part | What it contains | Why it’s separate |
|---|
preInstructions | Any permissionless setup instructions | Must be top-level instructions |
instruction | Squads sync transaction wrapping all vault-signed instructions | Executes with the vault’s smart account as signer |
postInstructions | Any post-operation instructions | Must be top-level instructions |
Initialize Yield Position
Before the vault can strip or merge tokens, a yield position account must exist on the Exponent Core program. coreAction.initializeYieldPosition creates this account.
Orderbook and CLMM operations automatically initialize the yield position if it doesn’t exist. You only need to call this manually if strip or merge is your first Core interaction for a given vault.
import {
coreAction,
createVaultSyncTransaction,
} from "@exponent-labs/exponent-sdk";
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
coreAction.initializeYieldPosition({
vault: new PublicKey("..."), // Exponent Core vault address
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);
Parameters
| Parameter | Type | Description |
|---|
vault | PublicKey | The Exponent Core vault address (the yield-stripping vault, not the strategy vault) |
A matching policy must exist before executing. See Policy Builders.
Strip
coreAction.strip strips a base token (e.g., an LST like JitoSOL) into equal amounts of PT and YT through the Exponent Core program.
import {
coreAction,
createVaultSyncTransaction,
} from "@exponent-labs/exponent-sdk";
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
coreAction.strip({
vault: new PublicKey("..."), // Exponent Core vault address
amountBase: 1_000_000_000n, // 1 token in native units
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);
Parameters
| Parameter | Type | Description |
|---|
vault | PublicKey | The Exponent Core vault address (the yield-stripping vault, not the strategy vault) |
amountBase | bigint | Amount of base token to strip, in native units |
A matching policy must exist before executing. See Policy Builders.
Merge
coreAction.merge merges equal amounts of PT and YT back into the base token.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
coreAction.merge({
vault: new PublicKey("..."),
amountPy: 1_000_000_000n, // Must hold equal PT and YT
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);
Parameters
| Parameter | Type | Description |
|---|
vault | PublicKey | The Exponent Core vault address |
amountPy | bigint | Amount of PT/YT to merge — must hold equal amounts of both |
The merge operation requires the vault to hold equal amounts of PT and YT. If the vault holds 100 PT and 80 YT, you can merge at most 80.
A matching policy must exist before executing. See Policy Builders.
Full Flow Example
This example demonstrates stripping a base token into PT + YT, then merging them back.
Step 1: Strip Base Token into PT + YT
import {
coreAction,
createVaultSyncTransaction,
} from "@exponent-labs/exponent-sdk";
import { Connection, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const EXPONENT_VAULT = new PublicKey("..."); // Exponent Core vault
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
coreAction.strip({
vault: EXPONENT_VAULT,
amountBase: 2_000_000_000n, // 2 tokens
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);
Step 2: Merge PT + YT Back to Base Token
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
coreAction.merge({
vault: EXPONENT_VAULT,
amountPy: 2_000_000_000n,
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);