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

# Zap-In & Zap-Out via API

> End-to-end guide: add liquidity (zap-in) and withdraw liquidity (zap-out) using the LP Agent Open API

## What You'll Build

By the end of this tutorial, you'll know how to programmatically:

* **Zap-In** — Find a pool and add liquidity with a single token (SOL)
* **Zap-Out** — Withdraw your liquidity back to your wallet

No smart contract knowledge required — LP Agent handles everything through simple REST APIs.

<Tip>
  LP Agent provides built-in transaction landing via Jito bundles for both zap-in and zap-out. This gives you a **significantly better landing rate** than submitting transactions directly via RPC — and it's free to use.
</Tip>

***

## How It Works

### The Zap-In Flow

Adding liquidity to a Meteora pool involves 4 steps:

```
1. DISCOVER       Find a pool that matches your criteria
   GET /pools/discover
        ↓
2. GET POOL INFO   Get the current active bin (price) to set your range
   GET /pools/{poolId}/info
        ↓
3. GENERATE TXS   API creates unsigned transactions for you
   POST /pools/{poolId}/add-tx
        ↓
4. SIGN & LAND     Sign with your wallet, submit via Jito
   POST /pools/landing-add-tx
```

**What is "zap-in"?** You provide just SOL, and the API automatically swaps it into the correct ratio of both tokens before adding liquidity. No need to manually buy tokens first.

### The Zap-Out Flow

Withdrawing liquidity follows a similar pattern:

```
1. GET POSITIONS   Find your open positions
   GET /lp-positions/opening
        ↓
2. GET QUOTES      (Optional) Preview what you'll receive
   POST /position/decrease-quotes
        ↓
3. GENERATE TXS   API creates unsigned withdrawal transactions
   POST /position/decrease-tx
        ↓
4. SIGN & LAND     Sign with your wallet, submit via Jito
   POST /position/landing-decrease-tx
```

### Understanding Key Concepts

Before diving into code, here are the key concepts:

| Concept        | What It Means                                                                                                                       |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Bin**        | A price point in a Meteora DLMM pool. Liquidity is placed across a range of bins.                                                   |
| **Active Bin** | The bin where the current price sits. Your position earns fees when the price is within your bin range.                             |
| **Strategy**   | How liquidity is distributed across bins — `Spot` (uniform), `Curve` (concentrated in middle), or `BidAsk` (concentrated at edges). |
| **BPS**        | Basis points. 10000 = 100%, 5000 = 50%, 500 = 5%. Used for slippage and withdrawal amounts.                                         |
| **Landing**    | Submitting signed transactions on-chain. LP Agent uses Jito bundles for better success rates.                                       |

***

## Prerequisites

* An LP Agent API key (get one from the [API Dashboard](https://app.lpagent.io/open-api))
* A Solana wallet keypair
* Node.js >= 18

```bash theme={null}
npm install @solana/web3.js bs58
```

***

## Step-by-Step: Zap-In

### Step 1: Setup

First, set up your API client and wallet:

```typescript theme={null}
import { Keypair, Transaction, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";

const API_BASE = "https://api.lpagent.io/open-api/v1";
const API_KEY = "your-api-key-here";

const wallet = Keypair.fromSecretKey(bs58.decode("your-base58-private-key"));
const OWNER = wallet.publicKey.toBase58();

// Helper for API calls
async function apiCall(method: string, path: string, body?: object) {
  const res = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      "x-api-key": API_KEY,
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  if (!res.ok) {
    const error = await res.json();
    throw new Error(`API error: ${JSON.stringify(error)}`);
  }

  return res.json();
}

// Sign a base64-encoded transaction (handles both legacy and versioned)
function signTransaction(base64Tx: string): string {
  const buffer = Buffer.from(base64Tx, "base64");

  try {
    const tx = VersionedTransaction.deserialize(buffer);
    tx.sign([wallet]);
    return Buffer.from(tx.serialize()).toString("base64");
  } catch {
    const tx = Transaction.from(buffer);
    tx.partialSign(wallet);
    return tx
      .serialize({ requireAllSignatures: false, verifySignatures: false })
      .toString("base64");
  }
}
```

### Step 2: Discover a Pool

Browse available pools and filter by your criteria. The API returns pools sorted by your chosen metric.

```typescript theme={null}
const discoverRes = await apiCall("GET",
  "/pools/discover?" + new URLSearchParams({
    chain: "SOL",
    sortBy: "vol_24h",        // sort by 24h volume
    sortOrder: "desc",
    pageSize: "5",
    min_market_cap: "5",       // min $5M market cap
    min_liquidity: "50",       // min $50K TVL
  }).toString()
);

const pool = discoverRes.data[0];
console.log(`Pool: ${pool.token0_symbol}/${pool.token1_symbol} (${pool.pool})`);
console.log(`Protocol: ${pool.protocol}`); // "meteora" or "meteora_damm_v2"
```

**What you get back:** A list of pools with their addresses, token pairs, TVL, volume, and protocol type. The API supports both Meteora DLMM and DAMM V2 pools — the same code works for both.

### Step 3: Get Pool Info & Set Your Range

Fetch the pool's current state to find the **active bin** (where the current price is). You'll place liquidity around this bin.

```typescript theme={null}
const poolInfoRes = await apiCall("GET", `/pools/${pool.pool}/info`);
const activeBin = poolInfoRes.data.liquidityViz?.activeBin;

// Place liquidity 34 bins on each side of the current price
const RANGE = 34;
const fromBinId = activeBin.binId - RANGE;
const toBinId = activeBin.binId + RANGE;

console.log(`Active bin: ${activeBin.binId}`);
console.log(`Your range: bin ${fromBinId} to ${toBinId} (${RANGE * 2 + 1} bins)`);
```

**How to choose your range:** Wider = less risk of going out of range, but lower fee APR. Tighter = higher fees but more frequent rebalancing needed. Start with 30-70 bins on each side.

### Step 4: Generate Zap-In Transactions

Tell the API how much SOL you want to deposit. It generates unsigned transactions that swap your SOL into the right token ratio and add liquidity.

```typescript theme={null}
const addTxRes = await apiCall("POST", `/pools/${pool.pool}/add-tx`, {
  stratergy: "Spot",           // distribution strategy
  inputSOL: 0.1,               // amount of SOL to deposit
  percentX: 0.5,               // 50/50 split between tokens
  fromBinId,
  toBinId,
  owner: OWNER,
  slippage_bps: 500,           // 5% slippage tolerance
  mode: "zap-in",              // auto-swap SOL to both tokens
});

console.log(`Position key: ${addTxRes.data.meta.positionPubKey}`);
console.log(`Swap txs: ${addTxRes.data.swapTxsWithJito.length}`);
console.log(`Add txs: ${addTxRes.data.addLiquidityTxsWithJito.length}`);
```

<Note>
  Already holding both tokens? Use `mode: "normal"` with `amountX` and `amountY` instead of `inputSOL` to skip the swap step.
</Note>

**What you get back:** Base64-encoded unsigned transactions, split into swap transactions (to convert SOL) and add-liquidity transactions. Plus metadata about your new position.

### Step 5: Sign & Land

Sign the transactions with your wallet and submit them via LP Agent's Jito landing endpoint.

```typescript theme={null}
const { lastValidBlockHeight, swapTxsWithJito, addLiquidityTxsWithJito, meta } = addTxRes.data;

// Sign all transactions with your wallet
const signedSwapTxs = swapTxsWithJito.map(signTransaction);
const signedAddTxs = addLiquidityTxsWithJito.map(signTransaction);

// Submit via LP Agent's Jito integration (better landing rate — free!)
const landRes = await apiCall("POST", "/pools/landing-add-tx", {
  lastValidBlockHeight,
  swapTxsWithJito: signedSwapTxs,
  addLiquidityTxsWithJito: signedAddTxs,
  meta,
});

console.log(`Zap-In Success! Tx: https://solscan.io/tx/${landRes.data.signature}`);
```

**That's it!** Your liquidity is now live and earning fees.

***

## Step-by-Step: Zap-Out

### Step 1: Find Your Open Positions

Query your wallet to see all open LP positions.

```typescript theme={null}
const positionsRes = await apiCall("GET",
  `/lp-positions/opening?owner=${OWNER}`
);

console.log(`Found ${positionsRes.count} open positions`);

for (const pos of positionsRes.data) {
  console.log(`- ${pos.pairName} | Value: $${pos.currentValue} | PnL: ${pos.pnl.percent.toFixed(2)}% | In Range: ${pos.inRange}`);
}
```

**What you get back:** Each position includes its encrypted ID (needed for withdrawal), current value, PnL, whether it's in range, and token details.

### Step 2: Get Quotes (Optional)

Preview what you'll receive before withdrawing. The API shows quotes for different output options.

```typescript theme={null}
const positionId = positionsRes.data[0].id; // encrypted position ID

const quotesRes = await apiCall("POST", "/position/decrease-quotes", {
  id: positionId,
  bps: 10000,    // 10000 = 100% withdrawal, 5000 = 50%, etc.
});

const quotes = quotesRes.data;
console.log(`Token prices: $${quotes.price.token0} / $${quotes.price.token1}`);
```

### Step 3: Generate Zap-Out Transactions

Choose how much to withdraw and what token(s) to receive.

```typescript theme={null}
const decreaseTxRes = await apiCall("POST", "/position/decrease-tx", {
  position_id: positionId,
  bps: 10000,                  // 100% withdrawal
  owner: OWNER,
  slippage_bps: 500,
  output: "allBaseToken",      // withdraw to SOL (see table below)
  provider: "JUPITER_ULTRA",
});

console.log(`Generated ${decreaseTxRes.data.closeTxsWithJito.length} close txs`);
```

**Output options:**

| Output         | Description                                            |
| -------------- | ------------------------------------------------------ |
| `allBaseToken` | Swap everything to SOL (recommended for easy re-entry) |
| `both`         | Receive both tokens as-is (no swap)                    |
| `allToken0`    | Swap everything to token X                             |
| `allToken1`    | Swap everything to token Y                             |

### Step 4: Sign & Land

```typescript theme={null}
const { lastValidBlockHeight: blockHeight, closeTxsWithJito, swapTxsWithJito } = decreaseTxRes.data;

// Sign all transactions
const signedCloseTxs = closeTxsWithJito.map(signTransaction);
const signedSwapTxs = swapTxsWithJito.map(signTransaction);

// Submit via LP Agent's Jito integration
const landRes = await apiCall("POST", "/position/landing-decrease-tx", {
  lastValidBlockHeight: blockHeight,
  closeTxs: [],
  swapTxs: [],
  closeTxsWithJito: signedCloseTxs,
  swapTxsWithJito: signedSwapTxs,
});

console.log(`Zap-Out Success! Tx: https://solscan.io/tx/${landRes.data.signature}`);
```

<Tip>
  Always use the `landing-decrease-tx` endpoint instead of submitting transactions directly via RPC. LP Agent's Jito integration provides a **much better landing rate** and it's completely **free**.
</Tip>

***

## Reference

### Strategy Types

| Strategy | Description                               | Best For                           |
| -------- | ----------------------------------------- | ---------------------------------- |
| `Spot`   | Uniform distribution across all bins      | Stable pairs (SOL/USDC)            |
| `Curve`  | Bell curve concentrated around active bin | Range-bound markets                |
| `BidAsk` | Heavier allocation at the edges           | Volatile pairs with mean reversion |

### Common Errors

| Error                       | Cause                                  | Fix                                                  |
| --------------------------- | -------------------------------------- | ---------------------------------------------------- |
| `Token X price unavailable` | Price feed missing                     | Try again later or use a different pool              |
| `Insufficient balance`      | Not enough tokens                      | Reduce `inputSOL` or `amountX`/`amountY`             |
| `Missing amount to ZapIn`   | `inputSOL` not provided in zap-in mode | Add `inputSOL` to the request body                   |
| Transaction expired         | `lastValidBlockHeight` has passed      | Re-generate and re-sign (don't wait more than \~60s) |

### Tips

* **Transaction landing**: Always use LP Agent's landing endpoints instead of direct RPC. Better success rates via Jito — for free.
* **DAMM V2 pools**: Both zap-in and zap-out auto-detect pool types. The same code works for DLMM and DAMM V2.
* **Partial zap-out**: Use `bps` less than 10000 to withdraw only a portion (e.g., `5000` = 50%).

***

## Complete Script

Here's everything above combined into a single copy-paste-ready script:

<Accordion title="Full zap-in + zap-out script">
  ```typescript theme={null}
  import { Keypair, Transaction, VersionedTransaction } from "@solana/web3.js";
  import bs58 from "bs58";

  const API_BASE = "https://api.lpagent.io/open-api/v1";
  const API_KEY = "your-api-key-here";
  const wallet = Keypair.fromSecretKey(bs58.decode("your-base58-private-key"));
  const OWNER = wallet.publicKey.toBase58();

  async function apiCall(method: string, path: string, body?: object) {
    const res = await fetch(`${API_BASE}${path}`, {
      method,
      headers: { "Content-Type": "application/json", "x-api-key": API_KEY },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (!res.ok) throw new Error(`API error: ${await res.text()}`);
    return res.json();
  }

  function signTx(base64Tx: string): string {
    const buffer = Buffer.from(base64Tx, "base64");
    try {
      const tx = VersionedTransaction.deserialize(buffer);
      tx.sign([wallet]);
      return Buffer.from(tx.serialize()).toString("base64");
    } catch {
      const tx = Transaction.from(buffer);
      tx.partialSign(wallet);
      return tx.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
    }
  }

  // ===================== ZAP-IN =====================
  async function zapIn(poolAddress: string, amountSOL: number) {
    const info = await apiCall("GET", `/pools/${poolAddress}/info`);
    const activeBin = info.data.liquidityViz?.activeBin;
    const fromBinId = activeBin.binId - 34;
    const toBinId = activeBin.binId + 34;

    const addTx = await apiCall("POST", `/pools/${poolAddress}/add-tx`, {
      stratergy: "Spot",
      inputSOL: amountSOL,
      percentX: 0.5,
      fromBinId,
      toBinId,
      owner: OWNER,
      slippage_bps: 500,
      mode: "zap-in",
    });

    const signedSwapTxs = addTx.data.swapTxsWithJito.map(signTx);
    const signedAddTxs = addTx.data.addLiquidityTxsWithJito.map(signTx);

    const result = await apiCall("POST", "/pools/landing-add-tx", {
      lastValidBlockHeight: addTx.data.lastValidBlockHeight,
      swapTxsWithJito: signedSwapTxs,
      addLiquidityTxsWithJito: signedAddTxs,
      meta: addTx.data.meta,
    });

    console.log(`Zap-In done: https://solscan.io/tx/${result.data.signature}`);
    return addTx.data.meta.positionPubKey;
  }

  // ===================== ZAP-OUT =====================
  async function zapOut(positionId: string, bps: number = 10000) {
    const decreaseTx = await apiCall("POST", "/position/decrease-tx", {
      position_id: positionId,
      bps,
      owner: OWNER,
      slippage_bps: 500,
      output: "allBaseToken",
    });

    const signedCloseTxs = decreaseTx.data.closeTxsWithJito.map(signTx);
    const signedSwapTxs = decreaseTx.data.swapTxsWithJito.map(signTx);

    const result = await apiCall("POST", "/position/landing-decrease-tx", {
      lastValidBlockHeight: decreaseTx.data.lastValidBlockHeight,
      closeTxs: [],
      swapTxs: [],
      closeTxsWithJito: signedCloseTxs,
      swapTxsWithJito: signedSwapTxs,
    });

    console.log(`Zap-Out done: https://solscan.io/tx/${result.data.signature}`);
  }

  // ===================== MAIN =====================
  async function main() {
    // Discover a pool
    const discover = await apiCall("GET", "/pools/discover?" + new URLSearchParams({
      chain: "SOL", sortBy: "vol_24h", sortOrder: "desc", pageSize: "1",
    }));
    const pool = discover.data[0];
    console.log(`Pool: ${pool.token0_symbol}/${pool.token1_symbol}`);

    // Zap-In: add 0.1 SOL of liquidity
    const positionKey = await zapIn(pool.pool, 0.1);
    console.log(`Position created: ${positionKey}`);

    // Check position status
    const positions = await apiCall("GET", `/lp-positions/opening?owner=${OWNER}`);
    const position = positions.data.find((p: any) => p.pool === pool.pool);
    if (position) {
      console.log(`Position value: $${position.currentValue}, PnL: ${position.pnl.percent}%`);

      // Zap-Out: withdraw 100% to SOL
      await zapOut(position.id, 10000);
    }
  }

  main().catch(console.error);
  ```
</Accordion>

## Next Steps

* [Auto-Rebalancing Bot](/tutorials/auto-rebalance-bot) — Build a bot that monitors and rebalances positions automatically
* Explore the full [API Reference](/api-reference/introduction) for all available endpoints
