Skip to main content
This guide walks through a complete lifecycle with the Exponent SDK: strip a base asset into PT and YT, deposit YT to earn yield, collect accrued interest, and merge everything back. By the end, developers will have a working end-to-end flow they can adapt for production.

Prerequisites

Install the SDK:
npm install @exponent-labs/exponent-sdk @solana/web3.js bn.js

Basic Setup

import { Connection, PublicKey, Keypair } from "@solana/web3.js";
import { Vault, YtPosition, LOCAL_ENV } from "@exponent-labs/exponent-sdk";

// Connection setup
const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const wallet = Keypair.fromSecretKey(/* your secret key */);

// Load SDK instances
const vaultPubkey = new PublicKey("your_vault_address");

const vault = await Vault.load(LOCAL_ENV, connection, vaultPubkey);

Complete Lifecycle: Strip, Collect Yield, and Merge

This example demonstrates the core flow: strip a base asset into PT + YT, deposit YT to earn yield, collect accrued interest, and merge PT + YT back into the base asset.
import { sendAndConfirmTransaction, Transaction } from "@solana/web3.js";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";

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

  console.log("=== Quickstart: Strip, Collect Yield, and Merge ===");

  // -------------------------------------------------------------------
  // Step 1: Initialize yield position (one-time per vault)
  // -------------------------------------------------------------------

  console.log("Step 1: Initializing yield position...");

  // Check if yield position exists — ixStripFromBase auto-deposits YT,
  // so the position must exist before stripping.
  const yieldPositionPda = vault.pda.yieldPosition({ owner: user, vault: vault.selfAddress });
  const yieldPositionAccount = await connection.getAccountInfo(yieldPositionPda);

  if (!yieldPositionAccount) {
    const initYieldPosIx = vault.ixInitializeYieldPosition({
      owner: user,
    });

    let tx = new Transaction().add(initYieldPosIx);
    let sig = await sendAndConfirmTransaction(connection, tx, [wallet]);
    console.log(`Initialized yield position. Signature: ${sig}`);
  }

  // -------------------------------------------------------------------
  // Step 2: Wrap base asset (USDC) into SY, strip into PT + YT,
  //         and deposit YT into the yield position
  // -------------------------------------------------------------------

  console.log("\nStep 2: Wrapping 1000 USDC into SY and stripping into PT + YT...");

  const baseAmount = 1000 * 1_000000; // 1000 USDC (6 decimals)

  const stripFromBaseIx = await vault.ixStripFromBase({
    owner: user,
    amountBase: BigInt(baseAmount),
  });

  let tx = new Transaction().add(stripFromBaseIx);
  let sig = await sendAndConfirmTransaction(connection, tx, [wallet], {
    commitment: "confirmed",
  });

  console.log(`Stripped from base. Signature: ${sig}`);
  console.log(`  Received: ~${baseAmount / 1_000000} PT, YT auto-deposited into yield position`);

  // Load yield position for later use
  const ytPosition = await YtPosition.loadByOwner(LOCAL_ENV, connection, user, vault);

  // -------------------------------------------------------------------
  // Step 3: Wait for yield to accrue
  // -------------------------------------------------------------------

  console.log("\nStep 3: Waiting for yield to accrue...");
  console.log("  (In production, this would be days/weeks)");

  // -------------------------------------------------------------------
  // Step 4: Stage and collect YT yield
  // -------------------------------------------------------------------

  console.log("\nStep 4: Collecting YT yield...");

  await vault.reload();

  // Reload yield position to pick up latest state
  const ytPositionForYield = await YtPosition.loadByOwner(LOCAL_ENV, connection, user, vault);

  // Stage yield (calculates earned interest)
  const stageYieldIx = ytPositionForYield.ixStageYield({
    payer: user,
  });

  // Collect interest (transfers to user)
  const collectInterestIx = ytPositionForYield.ixCollectInterest({
    signer: user,
  });

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

  console.log(`Collected YT yield. Signature: ${sig}`);
  console.log(`  Received: ~X SY (depends on time elapsed and exchange rate growth)`);

  // -------------------------------------------------------------------
  // Step 5: Withdraw YT from position
  // -------------------------------------------------------------------

  console.log("\nStep 5: Withdrawing YT from yield position...");

  const withdrawYtIx = ytPositionForYield.ixWithdrawYt({
    amount: BigInt(baseAmount), // Withdraw all YT
  });

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

  console.log(`Withdrew 1000 YT. Signature: ${sig}`);

  // -------------------------------------------------------------------
  // Step 6: Merge PT + YT back into the base asset
  // -------------------------------------------------------------------

  console.log("\nStep 6: Merging PT + YT back into USDC...");

  await vault.reload();

  const pyToRedeem = BigInt(1000_000000); // Merge all PT + YT

  const { ixs: mergeIxs, setupIxs: mergeSetupIxs } = await vault.ixMergeToBase({
    owner: user,
    amountPy: pyToRedeem,
  });

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

  console.log(`Redeemed to base asset. Signature: ${sig}`);
  console.log(`  Received: ~X USDC (depends on final exchange rate)`);

  console.log("\n=== Quickstart Complete ===");
}
Key Takeaways:
  • ixStripFromBase wraps base asset into SY, splits into PT + YT, and auto-deposits YT into the yield position — all in one transaction
  • The YieldTokenPosition must be initialized before calling ixStripFromBase
  • ixStageYield + ixCollectInterest snapshots the exchange rate and transfers accrued SY
  • Withdraw YT from the yield position before merging — ixMergeToBase burns YT from your wallet
  • Always call reload() on SDK instances before querying state

Next Steps