> ## Documentation Index
> Fetch the complete documentation index at: https://docs.exponent.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Trading

> Trade PT and YT on the CLMM and Orderbook with real SDK code

Exponent offers two markets for trading PT and YT: the **CLMM** (Concentrated Liquidity Market Maker) for instant swaps, and the **Orderbook** for limit orders at specific APY levels. This page covers both, with production-ready SDK examples.

***

## CLMM Trading

The CLMM enables instant swaps between PT and SY using concentrated liquidity pools.

### Sell PT on the CLMM (go long yield)

Selling PT at a discount gives leveraged yield exposure through YT at low cost. If PT trades at 0.95, the net cost for 1,000 YT is \~50 SY.

```typescript theme={null}
import { Vault, MarketThree, LOCAL_ENV } from "@exponent-labs/exponent-sdk";
import { Connection, PublicKey, Transaction, sendAndConfirmTransaction, Keypair } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const wallet = Keypair.fromSecretKey(/* ... */);

const vault = await Vault.load(LOCAL_ENV, connection, new PublicKey("..."));
const market = await MarketThree.load(LOCAL_ENV, connection, new PublicKey("..."));

const { ixs, setupIxs } = market.ixSellPt({
  trader: wallet.publicKey,
  amountPt: 1_000_000_000n,     // 1,000 PT
  outConstraint: 940_000_000n,  // minimum 940 SY out
});

const tx = new Transaction().add(...setupIxs, ...ixs);
await sendAndConfirmTransaction(connection, tx, [wallet]);
```

### CLMM Liquidity Provision

Providing concentrated liquidity to the CLMM earns trading fees. This example shows how to deposit liquidity, monitor a position, collect fees, and withdraw.

```typescript theme={null}
async function clmmLiquidityProvision() {
  const user = wallet.publicKey;

  console.log("=== CLMM Liquidity Provision ===");

  // -------------------------------------------------------------------
  // Step 1: Prepare assets (PT + SY)
  // -------------------------------------------------------------------

  console.log("Step 1: Preparing assets for liquidity provision...");

  // Strip to get PT
  const stripIx = vault.ixStrip({
    depositor: user,
    syIn: BigInt(10_000_000000), // 10,000 SY
  });

  let tx = new Transaction().add(stripIx);
  let sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Stripped 10,000 SY. Received 10,000 PT + 10,000 YT`);

  // Sell some YT to get SY back for LP pairing
  await market.reload();

  const { ixs: sellYtIxs, setupIxs: sellYtSetupIxs } = market.ixSellYt({
    trader: user,
    ytIn: BigInt(5_000_000000), // Sell 5000 YT
    minSyOut: BigInt(100_000000), // Minimum 100 SY
  });

  tx = new Transaction().add(...sellYtSetupIxs, ...sellYtIxs);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Sold 5000 YT for ~X SY`);

  // -------------------------------------------------------------------
  // Step 2: Determine tick range for concentrated liquidity
  // -------------------------------------------------------------------

  console.log("\nStep 2: Calculating optimal tick range...");

  await market.reload();

  const currentTick = market.state.currentTick;
  const tickSpacing = market.state.tickSpacing;

  console.log(`  Current Tick: ${currentTick}`);
  console.log(`  Tick Spacing: ${tickSpacing}`);

  // Concentrated range: +/-10% around current price
  // Tick formula: tick = log_1.0001(price)
  // For +/-10%: delta tick is approximately +/-1000

  const lowerTick = Math.floor((currentTick - 1000) / tickSpacing) * tickSpacing;
  const upperTick = Math.floor((currentTick + 1000) / tickSpacing) * tickSpacing;

  console.log(`  Range: [${lowerTick}, ${upperTick}]`);

  // -------------------------------------------------------------------
  // Step 3: Deposit liquidity
  // -------------------------------------------------------------------

  console.log("\nStep 3: Depositing liquidity to CLMM...");

  const depositResult = market.ixDepositLiquidity({
    depositor: user,
    ptInIntent: BigInt(5_000_000000), // Intent to deposit 5000 PT
    syInIntent: BigInt(5_000_000000), // Intent to deposit 5000 SY
    lowerTickKey: lowerTick,
    upperTickKey: upperTick,
  });

  tx = new Transaction().add(depositResult.ix);
  sig = await sendAndConfirmTransaction(
    connection,
    tx,
    [wallet, depositResult.signers] // signers is the generated LP position keypair
  );

  const lpPositionPubkey = depositResult.signers.publicKey;

  console.log(`Deposited liquidity. Signature: ${sig}`);
  console.log(`  LP Position: ${lpPositionPubkey.toString()}`);
  console.log(`  Range: ${lowerTick} to ${upperTick}`);

  // -------------------------------------------------------------------
  // Step 4: Monitor position
  // -------------------------------------------------------------------

  console.log("\nStep 4: Monitoring LP position...");

  await market.reload();

  const { lpPositions } = await market.getUserLpPositions(user, market.selfAddress);

  for (const positions of lpPositions) {
    for (const pos of positions) {
      console.log(`  Position: ${pos.publicKey.toBase58()}`);
      console.log(`  LP Balance: ${pos.account.lpBalance}`);
      console.log(`  Lower Tick: ${pos.account.lowerTickIdx}`);
      console.log(`  Upper Tick: ${pos.account.upperTickIdx}`);

      // Preview withdrawal amounts
      const result = market.getPtAndSyOnWithdrawLiquidity(pos.account);
      console.log(`  PT on withdraw: ${result.totalPtOut}`);
      console.log(`  SY on withdraw: ${result.totalSyOut}`);
    }
  }

  // -------------------------------------------------------------------
  // Step 5: Collect fees
  // -------------------------------------------------------------------

  console.log("\nStep 5: Collecting fees...");

  // Fees are collected automatically when withdrawing liquidity
  // Or can be collected separately via ixAddLiquidity with 0 amounts

  const { ixs: collectIxs, setupIxs: collectSetupIxs } = await market.ixAddLiquidity({
    owner: user,
    lpPosition: lpPositionPubkey,
    ptInIntent: 0n,
    syInIntent: 0n,
  });

  tx = new Transaction().add(...collectSetupIxs, ...collectIxs);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Collected fees. Signature: ${sig}`);

  // -------------------------------------------------------------------
  // Step 6: Withdraw liquidity
  // -------------------------------------------------------------------

  console.log("\nStep 6: Withdrawing liquidity...");

  const withdrawLiquidityIx = market.ixWithdrawLiquidity({
    withdrawer: user,
    lpPosition: lpPositionPubkey,
    lpIn: BigInt(5_000_000000), // LP tokens to burn
    minPtOut: 0n, // Set appropriate slippage limits in production
    minSyOut: 0n,
  });

  tx = new Transaction().add(withdrawLiquidityIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Withdrew liquidity. Signature: ${sig}`);
  console.log(`  Received: PT + SY + accrued fees`);

  console.log("\n=== CLMM Liquidity Provision Complete ===");
}
```

**Key Takeaways:**

* Concentrated liquidity requires choosing a tick range
* Tighter ranges mean higher capital efficiency but more rebalancing
* Fees accrue automatically when trades occur within the range
* Use `ixAddLiquidity` with 0 amounts to collect fees without depositing
* LP positions are NFT-like accounts (unique per range)

***

## Orderbook Trading

The Orderbook allows limit orders at specific APY levels, giving precise control over pricing. YT sitting in open SellYT offers continues to earn yield.

### Post a YT sell offer (lock in a fixed rate)

Selling YT leaves the trader with only PT — a fixed-rate position that converges to its SY backing at [maturity](/developers/learn/post-maturity).

```typescript theme={null}
import { Orderbook, OfferType, offerOptions } from "@exponent-labs/exponent-sdk";

const orderbook = await Orderbook.load(LOCAL_ENV, connection, new PublicKey("..."));

const postOfferIx = orderbook.ixPostOffer({
  trader: wallet.publicKey,
  price: 0.12,
  amount: 1_000_000_000n,
  offerType: OfferType.SellYt,
  offerOption: offerOptions("FillOrKill", [false]),
  virtualOffer: false,
  expirySeconds: 86400 * 7,
  mintSy: vault.mintSy,
});

const tx = new Transaction().add(postOfferIx);
await sendAndConfirmTransaction(connection, tx, [wallet]);
```

### Orderbook Limit Order with Interest Collection

This example posts a YT limit order and collects yield that accrues while the order sits in the book.

```typescript theme={null}
import { OfferType, offerOptions, amount } from "@exponent-labs/exponent-sdk";

async function orderbookLimitOrderWithInterest() {
  const user = wallet.publicKey;

  console.log("=== Orderbook Limit Order + Interest ===");

  // -------------------------------------------------------------------
  // Step 1: Strip SY into PT + YT
  // -------------------------------------------------------------------

  console.log("Step 1: Stripping 2000 SY into PT + YT...");

  const stripIx = vault.ixStrip({
    depositor: user,
    syIn: BigInt(2000_000000),
  });

  let tx = new Transaction().add(stripIx);
  let sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Stripped SY. Signature: ${sig}`);
  console.log(`  Received: 2000 PT + 2000 YT`);

  // -------------------------------------------------------------------
  // Step 2: Post limit order to sell 1500 YT at 15% implied APY
  // -------------------------------------------------------------------

  console.log("\nStep 2: Posting SellYT limit order at 15% APY...");

  await orderbook.reload();

  const postOfferIx = orderbook.ixPostOffer({
    trader: user,
    price: 0.15, // 15% implied APY
    amount: 1_500_000000n, // 1500 YT
    offerType: OfferType.SellYt,
    offerOption: offerOptions("FillOrKill", [false]), // Allow partial fills
    virtualOffer: false, // Real YT offer (not PT)
    expirySeconds: 86400 * 7, // 7 day expiry
    mintSy: vault.mintSy,
  });

  tx = new Transaction().add(postOfferIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Posted limit order. Signature: ${sig}`);
  console.log(`  Order: Sell 1500 YT at 15% APY`);
  console.log(`  Expiry: 7 days`);

  // -------------------------------------------------------------------
  // Step 3: Wait for order to fill OR time to pass
  // -------------------------------------------------------------------

  console.log("\nStep 3: Waiting for order to fill or yield to accrue...");
  console.log("  (Order sits in book, YT earns yield)");

  // In production: monitor order status
  // const openOrders = await orderbook.getOpenOrders({ user });
  // Check if order is filled, partially filled, or still open

  // Simulate time passing (in reality this would be days)
  await new Promise(resolve => setTimeout(resolve, 5000));

  // -------------------------------------------------------------------
  // Step 4: Collect interest on YT in open order
  // -------------------------------------------------------------------

  console.log("\nStep 4: Collecting interest on YT in order book...");

  await orderbook.reload();

  // Orderbook wrapper instruction handles SY conversion
  const collectInterestIx = await orderbook.ixWrapperCollectInterest({
    trader: user,
    mintSy: vault.mintSy,
  });

  tx = new Transaction().add(collectInterestIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Collected interest. Signature: ${sig}`);
  console.log(`  Received: ~X SY (yield accrued on YT in open order)`);

  // -------------------------------------------------------------------
  // Step 5: Check order status and balances
  // -------------------------------------------------------------------

  console.log("\nStep 5: Checking order status and escrow balances...");

  await orderbook.reload();

  const userBalances = orderbook.getUserBalances(user);

  console.log(`User Escrow Balances:`);
  console.log(`  PT: ${userBalances.pt}`);
  console.log(`  YT: ${userBalances.yt}`);
  console.log(`  SY: ${userBalances.sy}`);
  console.log(`  Staked YT: ${userBalances.stakedYt}`);
  console.log(`  Staged SY: ${userBalances.staged}`);

  const openOrders = orderbook.getUserOpenOrders(user);
  console.log(`\nOpen Orders: ${openOrders.length}`);

  openOrders.forEach((order, idx) => {
    console.log(`  Order ${idx + 1}:`);
    console.log(`    Type: ${order.type}`);
    console.log(`    Amount: ${order.amount}`);
    console.log(`    Price APY: ${order.priceApy}%`);
    console.log(`    Virtual: ${order.isVirtual}`);
    console.log(`    Expiry: ${new Date(order.expiryAt * 1000).toISOString()}`);
  });

  // -------------------------------------------------------------------
  // Step 6: Withdraw escrow balances
  // -------------------------------------------------------------------

  console.log("\nStep 6: Withdrawing escrow balances...");

  const withdrawIx = await orderbook.ixWrapperWithdrawFunds({
    trader: user,
    mintSy: vault.mintSy,
    ptAmount: amount("All"),
    ytAmount: amount("All"),
    syAmount: amount("All"),
  });

  tx = new Transaction().add(withdrawIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Withdrew escrow balances. Signature: ${sig}`);

  // -------------------------------------------------------------------
  // Step 7 (Optional): Remove unfilled order
  // -------------------------------------------------------------------

  if (openOrders.length > 0) {
    console.log("\nStep 7: Removing unfilled order...");

    const removeOfferIx = await orderbook.ixWrapperRemoveOffer({
      trader: user,
      offerIdx: openOrders[0].offerIndex, // Remove first order
      mintSy: vault.mintSy,
    });

    tx = new Transaction().add(removeOfferIx);
    sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

    console.log(`Removed order. Signature: ${sig}`);
  }

  console.log("\n=== Orderbook Limit Order + Interest Complete ===");
}
```

**Key Takeaways:**

* YT in orderbook SellYT offers continues to earn yield
* Use `ixWrapperCollectInterest` to claim yield from the orderbook
* `getUserBalances` shows escrow balances separate from wallet balances
* `getUserOpenOrders` returns all active orders for a user
* Always withdraw escrow balances or remove orders to recover funds

### Virtual Offers: Trade PT Directly on the Orderbook

Virtual offers enable PT trading on the Orderbook without handling YT directly. The system automatically strips or merges to settle virtual trades.

```typescript theme={null}
async function virtualOffers() {
  const user = wallet.publicKey;

  console.log("=== Virtual Offers (PT Trading) ===");

  // -------------------------------------------------------------------
  // Step 1: Post virtual SellYT offer (actually selling PT for SY)
  // -------------------------------------------------------------------

  console.log("Step 1: Posting virtual SellYT offer (sell PT for SY)...");

  await vault.reload();
  await orderbook.reload();

  // Virtual SellYT:
  // - User deposits SY
  // - When matched, system strips SY into PT + YT
  // - User receives PT, counter-party receives YT

  const baseAmount = 1000_000000; // 1000 USDC worth

  const { ix: virtualSellIx, setupIxs: virtualSellSetupIxs } = await orderbook.ixWrapperPostOffer({
    trader: user,
    amount: BigInt(baseAmount),
    price: 0.12, // 12% implied APY
    offerType: OfferType.SellYt,
    offerOption: offerOptions("FillOrKill", [false]),
    virtualOffer: true, // VIRTUAL = selling PT
    expirySeconds: 86400 * 3, // 3 day expiry
    mintSy: vault.mintSy,
  });

  let tx = new Transaction().add(...virtualSellSetupIxs, virtualSellIx);
  let sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Posted virtual SellYT offer. Signature: ${sig}`);
  console.log(`  Deposited: 1000 USDC worth of SY`);
  console.log(`  When filled: Receive PT, counter-party gets YT`);

  // -------------------------------------------------------------------
  // Step 2: Post virtual BuyYT offer (actually buying SY with PT)
  // -------------------------------------------------------------------

  console.log("\nStep 2: Posting virtual BuyYT offer (buy SY with PT)...");

  // First, get some PT (strip SY)
  const stripIx = vault.ixStrip({
    depositor: user,
    syIn: BigInt(500_000000),
  });

  tx = new Transaction().add(stripIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Stripped 500 SY. Now have PT to trade.`);

  await vault.reload();

  // Virtual BuyYT:
  // - User deposits PT
  // - When matched, system merges PT + YT into SY
  // - User receives SY, counter-party provides YT

  const virtualBuyIx = orderbook.ixPostOffer({
    trader: user,
    price: 0.10, // 10% implied APY
    amount: 500_000000n, // 500 PT
    offerType: OfferType.BuyYt,
    offerOption: offerOptions("FillOrKill", [false]),
    virtualOffer: true, // VIRTUAL = buying SY
    expirySeconds: 86400 * 3,
    mintSy: vault.mintSy,
  });

  tx = new Transaction().add(virtualBuyIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Posted virtual BuyYT offer. Signature: ${sig}`);
  console.log(`  Deposited: 500 PT`);
  console.log(`  When filled: Receive SY`);

  // -------------------------------------------------------------------
  // Step 3: Market order to fill virtual offer
  // -------------------------------------------------------------------

  console.log("\nStep 3: Executing market order to fill virtual offer...");

  // Take the virtual SellYT offer (receive PT)
  const { ix: marketOfferIx, setupIxs: marketSetupIxs } = await orderbook.ixWrapperMarketOffer({
    trader: user,
    amount: BigInt(300_000000), // Buy 300 USDC worth
    maxPriceApy: 0.13, // Max 13% APY
    minAmountOut: BigInt(290_000000), // Min output
    offerType: OfferType.BuyYt, // Buying YT (taking SellYT offers)
    virtualOffer: false, // This side is not virtual
    mintSy: vault.mintSy,
  });

  tx = new Transaction().add(...marketSetupIxs, marketOfferIx);
  sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

  console.log(`Market order executed. Signature: ${sig}`);
  console.log(`  Filled virtual SellYT offers`);
  console.log(`  Received: YT tokens (from stripped SY)`);

  // -------------------------------------------------------------------
  // Step 4: Check results
  // -------------------------------------------------------------------

  console.log("\nStep 4: Checking balances and open orders...");

  await orderbook.reload();

  const balances = orderbook.getUserBalances(user);
  const openOrders = orderbook.getUserOpenOrders(user);

  console.log(`Escrow Balances:`);
  console.log(`  PT: ${balances.pt}`);
  console.log(`  YT: ${balances.yt}`);
  console.log(`  SY: ${balances.sy}`);

  console.log(`\nOpen Virtual Offers: ${openOrders.filter(o => o.isVirtual).length}`);

  console.log("\n=== Virtual Offers Complete ===");
}
```

**Key Takeaways:**

* Virtual `SellYT` = Sell PT for SY (deposit SY, receive PT when filled)
* Virtual `BuyYT` = Buy SY with PT (deposit PT, receive SY when filled)
* Virtual offers enable PT traders to avoid YT entirely
* The system automatically strips/merges to settle virtual trades
* Virtual and non-virtual offers share the same orderbook liquidity

***

## Next Steps

<CardGroup cols={2}>
  <Card href="/developers/guides/strip-and-merge" title="Strip & Merge">
    Detailed walkthrough of stripping, yield collection, and merging
  </Card>

  <Card href="/developers/guides/debugging" title="Testing & Debugging">
    Inspect vault and market state and simulate transactions
  </Card>

  <Card href="/developer-clmm/sdk-quickstart" title="CLMM SDK">
    Complete CLMM integration guide
  </Card>

  <Card href="/developer-orderbook/sdk-quickstart" title="Orderbook SDK">
    Orderbook limit order documentation
  </Card>
</CardGroup>
