The CLMM instructions module enables vault managers and allocators to provide concentrated liquidity, trade PT and YT, and claim farm emissions on the Exponent CLMM. All interactions are executed as Squads sync transactions validated against onchain policies.
Transaction Structure
Every CLMM interaction goes through createVaultSyncTransaction, which returns three parts:
const { preInstructions, instruction, postInstructions, signers } =
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, ...signers]);
| 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 |
signers | Additional signers (e.g., LP position keypair from depositLiquidity) | Must be included when signing the transaction |
Deposit Liquidity
clmmAction.depositLiquidity creates a new LP position on the CLMM with a specified tick range. The LP position keypair is generated internally and returned in result.signers.
import {
clmmAction,
createVaultSyncTransaction,
} from "@exponent-labs/exponent-sdk";
const { preInstructions, instruction, postInstructions, signers } =
await createVaultSyncTransaction({
instructions: [
clmmAction.depositLiquidity({
market: new PublicKey("..."), // CLMM MarketThree address
ptInIntent: 1_000_000_000n, // Maximum PT to deposit
syInIntent: 1_000_000_000n, // Maximum SY to deposit
lowerTickKey: 500, // Lower tick boundary (APY value)
upperTickKey: 1500, // Upper tick boundary (APY value)
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet, ...signers]);
// signers[0] is the generated LP position keypair — save its public key
const lpPositionAddress = signers[0].publicKey;
Parameters
| Parameter | Type | Description |
|---|
market | PublicKey | The CLMM MarketThree account address |
ptInIntent | bigint | Maximum amount of PT to deposit |
syInIntent | bigint | Maximum amount of SY to deposit |
lowerTickKey | number | Lower tick boundary (APY value) |
upperTickKey | number | Upper tick boundary (APY value) |
The LP position keypair is generated internally by the SDK. After calling createVaultSyncTransaction, the keypair is available in result.signers[0]. Save the public key to reference this position in future operations (add liquidity, withdraw, claim emissions).
A matching policy must exist before executing. See Policy Builders.
Add Liquidity
clmmAction.addLiquidity adds more liquidity to an existing LP position.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.addLiquidity({
market: new PublicKey("..."),
lpPosition: new PublicKey("..."), // Existing LP position address
ptInIntent: 500_000_000n,
syInIntent: 500_000_000n,
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
lpPosition | PublicKey | The existing LpPosition account public key |
ptInIntent | bigint | Maximum amount of PT to add |
syInIntent | bigint | Maximum amount of SY to add |
A matching policy must exist before executing. See Policy Builders.
Withdraw Liquidity
clmmAction.withdrawLiquidity removes liquidity from an LP position, receiving PT and SY back.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.withdrawLiquidity({
market: new PublicKey("..."),
lpPosition: new PublicKey("..."),
lpIn: 1_000_000_000n, // Amount of LP units to remove
minPtOut: 900_000_000n, // Minimum PT to receive
minSyOut: 900_000_000n, // Minimum SY to receive
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
lpPosition | PublicKey | The LpPosition account to withdraw from |
lpIn | bigint | Amount of liquidity (LP units) to remove |
minPtOut | bigint | Minimum PT to receive — slippage protection |
minSyOut | bigint | Minimum SY to receive — slippage protection |
A matching policy must exist before executing. See Policy Builders.
Buy PT
clmmAction.buyPt buys PT with SY on the CLMM.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.buyPt({
market: new PublicKey("..."),
amountSy: 1_000_000_000n, // Amount of SY to spend
outConstraint: 900_000_000n, // Minimum PT to receive
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
amountSy | bigint | Amount of SY to spend |
outConstraint | bigint | Minimum PT to receive — slippage protection |
lnImpliedApyLimit | number | Optional. Price limit expressed as ln(implied APY) |
A matching policy must exist before executing. See Policy Builders.
Sell PT
clmmAction.sellPt sells PT for SY on the CLMM.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.sellPt({
market: new PublicKey("..."),
amountPt: 1_000_000_000n, // Amount of PT to sell
outConstraint: 900_000_000n, // Minimum SY to receive
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
amountPt | bigint | Amount of PT to sell |
outConstraint | bigint | Minimum SY to receive — slippage protection |
lnImpliedApyLimit | number | Optional. Price limit expressed as ln(implied APY) |
A matching policy must exist before executing. See Policy Builders.
Buy YT
clmmAction.buyYt buys YT with SY on the CLMM.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.buyYt({
market: new PublicKey("..."),
ytOut: 1_000_000_000n, // Minimum YT to receive
maxSyIn: 1_100_000_000n, // Maximum SY to spend
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
ytOut | bigint | Minimum amount of YT to receive |
maxSyIn | bigint | Maximum amount of SY to spend |
lnImpliedApyLimit | number | Optional. Price limit expressed as ln(implied APY) |
A matching policy must exist before executing. See Policy Builders.
Sell YT
clmmAction.sellYt sells YT for SY on the CLMM.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.sellYt({
market: new PublicKey("..."),
ytIn: 1_000_000_000n, // Amount of YT to sell
minSyOut: 900_000_000n, // Minimum SY to receive
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
ytIn | bigint | Amount of YT to sell |
minSyOut | bigint | Minimum SY to receive — slippage protection |
lnImpliedApyLimit | number | Optional. Price limit expressed as ln(implied APY) |
A matching policy must exist before executing. See Policy Builders.
Trade PT (Low-Level)
clmmAction.tradePt is a low-level PT/SY swap that accepts a SwapDirection. For most use cases, prefer buyPt or sellPt.
import {
clmmAction,
createVaultSyncTransaction,
SwapDirection,
} from "@exponent-labs/exponent-sdk";
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.tradePt({
market: new PublicKey("..."),
traderAmount: 1_000_000_000n,
outConstraint: 900_000_000n,
swapDirection: SwapDirection.SyToPt,
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
traderAmount | bigint | Amount of the input token |
outConstraint | bigint | Minimum output amount — slippage protection |
swapDirection | SwapDirection | SwapDirection.SyToPt or SwapDirection.PtToSy |
lnImpliedApyLimit | number | Optional. Price limit expressed as ln(implied APY) |
A matching policy must exist before executing. See Policy Builders.
Claim Farm Emission
clmmAction.claimFarmEmission claims farm emissions from an LP position.
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.claimFarmEmission({
market: new PublicKey("..."),
lpPosition: new PublicKey("..."),
farmIndex: 0,
}),
],
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 |
|---|
market | PublicKey | The CLMM MarketThree account address |
lpPosition | PublicKey | The LpPosition account to claim from |
farmIndex | number | Index of the farm emission to claim |
A matching policy must exist before executing. See Policy Builders.
Full Flow Example
This example demonstrates providing concentrated liquidity on the CLMM, buying PT, and then withdrawing the liquidity position.
Step 1: Deposit Liquidity (Create LP Position)
import {
clmmAction,
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 CLMM_MARKET = new PublicKey("...");
const { preInstructions, instruction, postInstructions, signers } =
await createVaultSyncTransaction({
instructions: [
clmmAction.depositLiquidity({
market: CLMM_MARKET,
ptInIntent: 2_000_000_000n,
syInIntent: 2_000_000_000n,
lowerTickKey: 500,
upperTickKey: 1500,
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet, ...signers]);
// Save the LP position address for later use
const lpPositionAddress = signers[0].publicKey;
Step 2: Buy PT with SY
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.buyPt({
market: CLMM_MARKET,
amountSy: 1_000_000_000n,
outConstraint: 900_000_000n,
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);
Step 3: Withdraw Liquidity
const { preInstructions, instruction, postInstructions } =
await createVaultSyncTransaction({
instructions: [
clmmAction.withdrawLiquidity({
market: CLMM_MARKET,
lpPosition: lpPositionAddress,
lpIn: 2_000_000_000n,
minPtOut: 0n,
minSyOut: 0n,
}),
],
owner: vaultPda,
connection,
policyPda,
vaultPda,
signer: wallet.publicKey,
vaultAddress,
});
const tx = new Transaction().add(...preInstructions, instruction, ...postInstructions);
await sendAndConfirmTransaction(connection, tx, [wallet]);