Skip to main content
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]);
PartWhat it containsWhy it’s separate
preInstructionsAny permissionless setup instructionsMust be top-level instructions
instructionSquads sync transaction wrapping all vault-signed instructionsExecutes with the vault’s smart account as signer
postInstructionsAny post-operation instructionsMust be top-level instructions
signersAdditional 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
ptInIntentbigintMaximum amount of PT to deposit
syInIntentbigintMaximum amount of SY to deposit
lowerTickKeynumberLower tick boundary (APY value)
upperTickKeynumberUpper 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
lpPositionPublicKeyThe existing LpPosition account public key
ptInIntentbigintMaximum amount of PT to add
syInIntentbigintMaximum 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
lpPositionPublicKeyThe LpPosition account to withdraw from
lpInbigintAmount of liquidity (LP units) to remove
minPtOutbigintMinimum PT to receive — slippage protection
minSyOutbigintMinimum 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
amountSybigintAmount of SY to spend
outConstraintbigintMinimum PT to receive — slippage protection
lnImpliedApyLimitnumberOptional. 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
amountPtbigintAmount of PT to sell
outConstraintbigintMinimum SY to receive — slippage protection
lnImpliedApyLimitnumberOptional. 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
ytOutbigintMinimum amount of YT to receive
maxSyInbigintMaximum amount of SY to spend
lnImpliedApyLimitnumberOptional. 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
ytInbigintAmount of YT to sell
minSyOutbigintMinimum SY to receive — slippage protection
lnImpliedApyLimitnumberOptional. 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
traderAmountbigintAmount of the input token
outConstraintbigintMinimum output amount — slippage protection
swapDirectionSwapDirectionSwapDirection.SyToPt or SwapDirection.PtToSy
lnImpliedApyLimitnumberOptional. 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

ParameterTypeDescription
marketPublicKeyThe CLMM MarketThree account address
lpPositionPublicKeyThe LpPosition account to claim from
farmIndexnumberIndex 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]);