> For the complete documentation index, see [llms.txt](https://docs.fwog.fun/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fwog.fun/platform/fwogswap-integration-guide.md).

# FwogSwap: Integration Guide

*Download the IDL file below:*

{% file src="/files/4Ss5ztIVBupRxa4dESBj" %}

### Overview

FwogSwap is a **Constant Product Market Maker (CPMM)** DEX on Solana with support for **one-sided liquidity provisioning**. It uses a concentrated liquidity model with configurable price ranges (`sqrtMinPrice` / `sqrtMaxPrice`), allowing pool creators to define the exact price boundaries for their liquidity.

**Program ID:** `fwogV3pR5UgBUWTbg8UKYCQZUbuNKCSKC8CyBtxxU7J`

**Protocol fees are set to 0%.**

#### Key Features

* **Concentrated Liquidity CPMM** with customizable price range (`sqrtMinPrice` to `sqrtMaxPrice`)
* **One-Sided Liquidity**: create pools where all initial liquidity is in a single token (e.g., launch a token without needing to pair it with SOL/USDC upfront)
* **SPL Token & Token-2022 support** (including transfer fees, transfer hooks)
* **NFT-based positions**: each liquidity position is represented by a Token-2022 NFT
* **Farming rewards**: up to 2 reward tokens per pool
* **Position locking & vesting**: lock positions with customizable vesting schedules
* **Dynamic fees**: optional volatility-based fee adjustments
* **Fee schedulers**: time-based or market-cap-based fee curves for token launches

***

### Architecture

#### Core Accounts

| Account            | Description                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------------- |
| **Pool**           | Stores all pool state: reserves, price, fees, liquidity, reward info                         |
| **Config**         | Predefined pool configuration (fees, price range, activation type). Used by `initializePool` |
| **Position**       | Tracks a user's liquidity, pending fees, and pending rewards. Linked to an NFT mint          |
| **ProtocolConfig** | Singleton storing protocol-wide fee percentages                                              |

#### Price Model

Prices are stored as **sqrt prices in Q64.64 fixed-point format** (`u128`):

```
sqrtPrice = sqrt(price_token_b / price_token_a) * 2^64
```

Constants:

* `MIN_SQRT_PRICE = 4295048016`
* `MAX_SQRT_PRICE = 79226673521066979257578248091`

#### Fee Model

Fees use a numerator/denominator system:

* **Fee denominator**: `1,000,000,000` (1e9)
* **Fee numerator range**: `100,000` (0.01%) to `990,000,000` (99%)
* Trade fees go to LPs. Protocol and partner fees are configurable percentages split from the trade fee.

***

### Integration via IDL / ABI

The program is built with **Anchor**. You can integrate using the generated IDL at `target/idl/cp_amm.json`.

#### Setup

```typescript
import { Program, AnchorProvider, BN } from "@coral-xyz/anchor";
import { Connection, PublicKey, Keypair, SystemProgram, ComputeBudgetProgram } from "@solana/web3.js";
import { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync } from "@solana/spl-token";

// Import the IDL (JSON file from target/idl/cp_amm.json)
import CpAmmIDL from "./cp_amm.json";

const PROGRAM_ID = new PublicKey("fwogV3pR5UgBUWTbg8UKYCQZUbuNKCSKC8CyBtxxU7J");

const connection = new Connection("https://api.mainnet-beta.solana.com");
const provider = new AnchorProvider(connection, wallet, {});
const program = new Program(CpAmmIDL, provider);
```

#### PDA Derivations

All accounts are derived deterministically using PDAs:

```typescript
function derivePoolAuthority(): PublicKey {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("pool_authority")],
    PROGRAM_ID
  )[0];
}

// Pool PDA (config-based pool)
function derivePoolAddress(config: PublicKey, tokenAMint: PublicKey, tokenBMint: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [
      Buffer.from("pool"),
      config.toBuffer(),
      maxKey(tokenAMint, tokenBMint),   // larger pubkey first
      minKey(tokenAMint, tokenBMint),   // smaller pubkey second
    ],
    PROGRAM_ID
  )[0];
}

// Customizable pool PDA (no config, unique per token pair)
function deriveCustomizablePoolAddress(tokenAMint: PublicKey, tokenBMint: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [
      Buffer.from("cpool"),
      maxKey(tokenAMint, tokenBMint),
      minKey(tokenAMint, tokenBMint),
    ],
    PROGRAM_ID
  )[0];
}

function deriveTokenVaultAddress(tokenMint: PublicKey, pool: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("token_vault"), tokenMint.toBuffer(), pool.toBuffer()],
    PROGRAM_ID
  )[0];
}

function derivePositionAddress(positionNftMint: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("position"), positionNftMint.toBuffer()],
    PROGRAM_ID
  )[0];
}

function derivePositionNftAccount(positionNftMint: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("position_nft_account"), positionNftMint.toBuffer()],
    PROGRAM_ID
  )[0];
}

function deriveProtocolConfigAddress(): PublicKey {
  return PublicKey.findProgramAddressSync(
    [Buffer.from("protocol_config")],
    PROGRAM_ID
  )[0];
}

// Helper: sort keys for deterministic PDA seeds
function maxKey(a: PublicKey, b: PublicKey): Buffer {
  return Buffer.compare(a.toBuffer(), b.toBuffer()) === 1 ? a.toBuffer() : b.toBuffer();
}

function minKey(a: PublicKey, b: PublicKey): Buffer {
  return Buffer.compare(a.toBuffer(), b.toBuffer()) === 1 ? b.toBuffer() : a.toBuffer();
}
```

***

### Creating a Customizable Pool (Two-Sided)

The `initializeCustomizablePool` instruction creates a pool where the creator specifies all parameters: price range, initial price, fees, and initial liquidity. This pool is unique per token pair (only one customizable pool per pair).

#### Sqrt Price Calculation

To compute the `sqrtPrice` from a human-readable price:

```typescript
function priceToSqrtPrice(price: number, decimalsA: number, decimalsB: number): BN {
  // price = tokenB_amount / tokenA_amount (in human-readable terms)
  // Adjust for decimal difference
  const adjustedPrice = price * Math.pow(10, decimalsB - decimalsA);
  const sqrtPriceDecimal = Math.sqrt(adjustedPrice);
  // Convert to Q64.64
  const sqrtPrice = new BN(
    BigInt(Math.floor(sqrtPriceDecimal * 2 ** 64)).toString()
  );
  return sqrtPrice;
}
```

#### Encoding Base Fee Parameters

The base fee is encoded as a 30-byte `data` array. For a simple static fee, use the `BorshFeeTimeScheduler` codec:

```typescript
import { BN } from "@coral-xyz/anchor";

// Encode a static fee (no time decay)
function encodeStaticBaseFee(feeNumerator: BN): { data: number[] } {
  // For a static fee, numberOfPeriod = 0, periodFrequency = 0, reductionFactor = 0
  // baseFeeMode = 0 (FeeTimeSchedulerLinear with no periods = static)
  const buffer = program.coder.types.encode("borshFeeTimeScheduler", {
    cliffFeeNumerator: feeNumerator,
    numberOfPeriod: 0,
    periodFrequency: new BN(0),
    reductionFactor: new BN(0),
    baseFeeMode: 0,
    padding: [0, 0, 0],
  });
  return { data: Array.from(buffer) };
}
```

#### Full Example: Two-Sided Pool

```typescript
async function createTwoSidedPool(
  payer: Keypair,
  tokenAMint: PublicKey,  // e.g., your token
  tokenBMint: PublicKey,  // e.g., SOL or USDC
  initialPriceBPerA: number,  // price of tokenA in terms of tokenB
  decimalsA: number,
  decimalsB: number,
) {
  const poolAuthority = derivePoolAuthority();
  const pool = deriveCustomizablePoolAddress(tokenAMint, tokenBMint);
  const positionNftKP = Keypair.generate();
  const position = derivePositionAddress(positionNftKP.publicKey);
  const positionNftAccount = derivePositionNftAccount(positionNftKP.publicKey);
  const tokenAVault = deriveTokenVaultAddress(tokenAMint, pool);
  const tokenBVault = deriveTokenVaultAddress(tokenBMint, pool);
  const protocolConfig = deriveProtocolConfigAddress();

  const tokenAProgram = (await connection.getAccountInfo(tokenAMint)).owner;
  const tokenBProgram = (await connection.getAccountInfo(tokenBMint)).owner;

  const payerTokenA = getAssociatedTokenAddressSync(tokenAMint, payer.publicKey, true, tokenAProgram);
  const payerTokenB = getAssociatedTokenAddressSync(tokenBMint, payer.publicKey, true, tokenBProgram);

  // Compute sqrt prices
  const sqrtPrice = priceToSqrtPrice(initialPriceBPerA, decimalsA, decimalsB);
  const sqrtMinPrice = new BN("4295048016");       // MIN_SQRT_PRICE
  const sqrtMaxPrice = new BN("79226673521066979257578248091"); // MAX_SQRT_PRICE

  // Fee: 1% static fee (fee_numerator = 10_000_000, denominator = 1_000_000_000)
  const baseFee = encodeStaticBaseFee(new BN(10_000_000));

  const poolFees = {
    baseFee,
    padding: new Array(2).fill(0),
    dynamicFee: null,  // no dynamic fee
  };

  const liquidity = new BN("1000000000000000"); // adjust based on desired depth

  const tx = await program.methods
    .initializeCustomizablePool({
      poolFees,
      sqrtMinPrice,
      sqrtMaxPrice,
      hasAlphaVault: false,
      liquidity,
      sqrtPrice,
      activationType: 1,       // 0 = slot-based, 1 = timestamp-based
      collectFeeMode: 0,       // 0 = both tokens, 1 = only token B
      activationPoint: null,   // null = activate immediately
    })
    .accountsPartial({
      creator: payer.publicKey,
      positionNftMint: positionNftKP.publicKey,
      positionNftAccount,
      payer: payer.publicKey,
      poolAuthority,
      pool,
      position,
      tokenAMint,
      tokenBMint,
      tokenAVault,
      tokenBVault,
      payerTokenA,
      payerTokenB,
      tokenAProgram,
      tokenBProgram,
      token2022Program: TOKEN_2022_PROGRAM_ID,
      systemProgram: SystemProgram.programId,
      protocolConfig,
    })
    .preInstructions([
      ComputeBudgetProgram.setComputeUnitLimit({ units: 350_000 }),
    ])
    .signers([payer, positionNftKP])
    .rpc();

  console.log("Pool created:", pool.toBase58());
  console.log("Position:", position.toBase58());
  return { pool, position };
}
```

***

### Creating a One-Sided Pool

One-sided liquidity is achieved by setting the **initial price at one of the price boundaries**. This makes the pool hold 100% of a single token at creation:

* **`sqrtPrice = sqrtMinPrice`**: the pool contains **only token A** (selling token A as price goes up)
* **`sqrtPrice = sqrtMaxPrice`**: the pool contains **only token B**

This is ideal for **token launches** where you want to seed the pool with your new token only, without needing to provide a quote token (SOL, USDC, etc.).

#### Example: One-Sided Pool (Token A Only)

```typescript
async function createOneSidedPool(
  payer: Keypair,
  tokenAMint: PublicKey,  // your new token (100% of initial liquidity)
  tokenBMint: PublicKey,  // quote token (SOL/USDC) - 0 needed at creation
  maxPriceBPerA: number,  // the price ceiling
  decimalsA: number,
  decimalsB: number,
) {
  const poolAuthority = derivePoolAuthority();
  const pool = deriveCustomizablePoolAddress(tokenAMint, tokenBMint);
  const positionNftKP = Keypair.generate();
  const position = derivePositionAddress(positionNftKP.publicKey);
  const positionNftAccount = derivePositionNftAccount(positionNftKP.publicKey);
  const tokenAVault = deriveTokenVaultAddress(tokenAMint, pool);
  const tokenBVault = deriveTokenVaultAddress(tokenBMint, pool);
  const protocolConfig = deriveProtocolConfigAddress();

  const tokenAProgram = (await connection.getAccountInfo(tokenAMint)).owner;
  const tokenBProgram = (await connection.getAccountInfo(tokenBMint)).owner;

  const payerTokenA = getAssociatedTokenAddressSync(tokenAMint, payer.publicKey, true, tokenAProgram);
  const payerTokenB = getAssociatedTokenAddressSync(tokenBMint, payer.publicKey, true, tokenBProgram);

  // For one-sided (token A only): sqrtPrice = sqrtMinPrice
  // Set sqrtMinPrice to a very small value (or MIN_SQRT_PRICE)
  // Set sqrtMaxPrice to represent the max price you want
  const sqrtMinPrice = new BN("4295048016"); // MIN_SQRT_PRICE
  const sqrtMaxPrice = priceToSqrtPrice(maxPriceBPerA, decimalsA, decimalsB);

  // Key: sqrtPrice = sqrtMinPrice => 100% token A, 0% token B
  const sqrtPrice = sqrtMinPrice;

  // Fee: 1% static fee
  const baseFee = encodeStaticBaseFee(new BN(10_000_000));

  const poolFees = {
    baseFee,
    padding: new Array(2).fill(0),
    dynamicFee: null,
  };

  const liquidity = new BN("1000000000000000");

  const tx = await program.methods
    .initializeCustomizablePool({
      poolFees,
      sqrtMinPrice,
      sqrtMaxPrice,
      hasAlphaVault: false,
      liquidity,
      sqrtPrice,           // = sqrtMinPrice => one-sided (token A only)
      activationType: 1,
      collectFeeMode: 0,
      activationPoint: null,
    })
    .accountsPartial({
      creator: payer.publicKey,
      positionNftMint: positionNftKP.publicKey,
      positionNftAccount,
      payer: payer.publicKey,
      poolAuthority,
      pool,
      position,
      tokenAMint,
      tokenBMint,
      tokenAVault,
      tokenBVault,
      payerTokenA,
      payerTokenB,
      tokenAProgram,
      tokenBProgram,
      token2022Program: TOKEN_2022_PROGRAM_ID,
      systemProgram: SystemProgram.programId,
      protocolConfig,
    })
    .preInstructions([
      ComputeBudgetProgram.setComputeUnitLimit({ units: 350_000 }),
    ])
    .signers([payer, positionNftKP])
    .rpc();

  console.log("One-sided pool created:", pool.toBase58());
  console.log("Only token A deposited. Token B is added by buyers.");
  return { pool, position };
}
```

> **Note**: Even in a one-sided pool, the payer still needs to have at least **1 lamport** worth of the other token to prove ownership of the mint. The program enforces `total_amount = max(computed_amount, 1)` for both tokens.

***

### Swapping

The swap instruction routes through the pool. The input/output direction is determined by matching the `inputTokenAccount` mint against `tokenAMint` / `tokenBMint`.

```typescript
async function swap(
  payer: Keypair,
  pool: PublicKey,
  inputMint: PublicKey,
  outputMint: PublicKey,
  amountIn: BN,
  minAmountOut: BN,
) {
  const poolState = await program.account.pool.fetch(pool);
  const poolAuthority = derivePoolAuthority();

  const tokenAProgram = (await connection.getAccountInfo(poolState.tokenAMint)).owner;
  const tokenBProgram = (await connection.getAccountInfo(poolState.tokenBMint)).owner;

  const inputTokenAccount = getAssociatedTokenAddressSync(
    inputMint, payer.publicKey, true,
    inputMint.equals(poolState.tokenAMint) ? tokenAProgram : tokenBProgram
  );
  const outputTokenAccount = getAssociatedTokenAddressSync(
    outputMint, payer.publicKey, true,
    outputMint.equals(poolState.tokenAMint) ? tokenAProgram : tokenBProgram
  );

  const tx = await program.methods
    .swap2({
      amount0: amountIn,
      amount1: minAmountOut,
      swapMode: 0,  // 0 = ExactIn, 1 = PartialFill, 2 = ExactOut
    })
    .accountsPartial({
      poolAuthority,
      pool,
      payer: payer.publicKey,
      inputTokenAccount,
      outputTokenAccount,
      tokenAVault: poolState.tokenAVault,
      tokenBVault: poolState.tokenBVault,
      tokenAMint: poolState.tokenAMint,
      tokenBMint: poolState.tokenBMint,
      tokenAProgram,
      tokenBProgram,
      referralTokenAccount: null,
    })
    .signers([payer])
    .rpc();

  console.log("Swap tx:", tx);
}
```

#### Swap Modes

| Mode        | Value | Description                                                           |
| ----------- | ----- | --------------------------------------------------------------------- |
| ExactIn     | `0`   | Swap exact input amount, receive at least `amount1`                   |
| PartialFill | `1`   | Swap up to `amount0` input, allowing partial fills at pool boundaries |
| ExactOut    | `2`   | Receive exactly `amount0` output, spend at most `amount1` input       |

***

### Adding / Removing Liquidity

#### Add Liquidity

```typescript
async function addLiquidity(
  owner: Keypair,
  pool: PublicKey,
  position: PublicKey,
  liquidityDelta: BN,
  maxTokenA: BN,
  maxTokenB: BN,
) {
  const poolState = await program.account.pool.fetch(pool);
  const positionState = await program.account.position.fetch(position);
  const positionNftAccount = derivePositionNftAccount(positionState.nftMint);

  const tokenAProgram = (await connection.getAccountInfo(poolState.tokenAMint)).owner;
  const tokenBProgram = (await connection.getAccountInfo(poolState.tokenBMint)).owner;

  await program.methods
    .addLiquidity({
      liquidityDelta,
      tokenAAmountThreshold: maxTokenA,
      tokenBAmountThreshold: maxTokenB,
    })
    .accountsPartial({
      pool,
      position,
      positionNftAccount,
      owner: owner.publicKey,
      tokenAAccount: getAssociatedTokenAddressSync(poolState.tokenAMint, owner.publicKey, true, tokenAProgram),
      tokenBAccount: getAssociatedTokenAddressSync(poolState.tokenBMint, owner.publicKey, true, tokenBProgram),
      tokenAVault: poolState.tokenAVault,
      tokenBVault: poolState.tokenBVault,
      tokenAMint: poolState.tokenAMint,
      tokenBMint: poolState.tokenBMint,
      tokenAProgram,
      tokenBProgram,
    })
    .signers([owner])
    .rpc();
}
```

#### Remove Liquidity

```typescript
async function removeLiquidity(
  owner: Keypair,
  pool: PublicKey,
  position: PublicKey,
  liquidityDelta: BN,
  minTokenA: BN,
  minTokenB: BN,
) {
  const poolState = await program.account.pool.fetch(pool);
  const positionState = await program.account.position.fetch(position);
  const positionNftAccount = derivePositionNftAccount(positionState.nftMint);
  const poolAuthority = derivePoolAuthority();

  const tokenAProgram = (await connection.getAccountInfo(poolState.tokenAMint)).owner;
  const tokenBProgram = (await connection.getAccountInfo(poolState.tokenBMint)).owner;

  await program.methods
    .removeLiquidity({
      liquidityDelta,
      tokenAAmountThreshold: minTokenA,
      tokenBAmountThreshold: minTokenB,
    })
    .accountsPartial({
      poolAuthority,
      pool,
      position,
      positionNftAccount,
      owner: owner.publicKey,
      tokenAAccount: getAssociatedTokenAddressSync(poolState.tokenAMint, owner.publicKey, true, tokenAProgram),
      tokenBAccount: getAssociatedTokenAddressSync(poolState.tokenBMint, owner.publicKey, true, tokenBProgram),
      tokenAVault: poolState.tokenAVault,
      tokenBVault: poolState.tokenBVault,
      tokenAMint: poolState.tokenAMint,
      tokenBMint: poolState.tokenBMint,
      tokenAProgram,
      tokenBProgram,
    })
    .signers([owner])
    .rpc();
}
```

***

### Reading Pool State

```typescript
async function getPoolInfo(poolAddress: PublicKey) {
  const pool = await program.account.pool.fetch(poolAddress);

  console.log("Token A Mint:", pool.tokenAMint.toBase58());
  console.log("Token B Mint:", pool.tokenBMint.toBase58());
  console.log("Sqrt Price:", pool.sqrtPrice.toString());
  console.log("Liquidity:", pool.liquidity.toString());
  console.log("Sqrt Min Price:", pool.sqrtMinPrice.toString());
  console.log("Sqrt Max Price:", pool.sqrtMaxPrice.toString());
  console.log("Pool Status:", pool.poolStatus === 0 ? "Enabled" : "Disabled");
  console.log("Pool Type:", pool.poolType === 0 ? "Permissionless" : "Customizable");

  return pool;
}
```

***

### Constants Reference

| Constant               | Value                           | Description                |
| ---------------------- | ------------------------------- | -------------------------- |
| `MIN_SQRT_PRICE`       | `4295048016`                    | Minimum allowed sqrt price |
| `MAX_SQRT_PRICE`       | `79226673521066979257578248091` | Maximum allowed sqrt price |
| `FEE_DENOMINATOR`      | `1,000,000,000`                 | Fee denominator (1e9)      |
| `MIN_FEE_NUMERATOR`    | `100,000`                       | Min fee (0.01%)            |
| `MAX_FEE_NUMERATOR`    | `990,000,000`                   | Max fee (99%)              |
| `PROTOCOL_FEE_PERCENT` | `0`                             | Protocol fee percentage    |

#### Common Fee Numerator Values

| Fee (bps) | Fee (%) | Numerator    |
| --------- | ------- | ------------ |
| 1         | 0.01%   | `100,000`    |
| 25        | 0.25%   | `2,500,000`  |
| 30        | 0.30%   | `3,000,000`  |
| 100       | 1.00%   | `10,000,000` |
| 500       | 5.00%   | `50,000,000` |

***

### Pool Initialization Methods

| Method                            | Description                                                      | Pool PDA                             |
| --------------------------------- | ---------------------------------------------------------------- | ------------------------------------ |
| `initializePool`                  | Create pool from a pre-existing `Config` account (static config) | `["pool", config, maxMint, minMint]` |
| `initializeCustomizablePool`      | Create pool with fully custom parameters, unique per token pair  | `["cpool", maxMint, minMint]`        |
| `initializePoolWithDynamicConfig` | Create pool from a dynamic `Config` with custom fee/price params | `["pool", config, maxMint, minMint]` |

***

### Important Notes

* **Compute budget**: Pool initialization requires \~350,000 compute units. Always include `ComputeBudgetProgram.setComputeUnitLimit({ units: 350_000 })`.
* **Token ordering**: When deriving PDAs, the larger pubkey comes first, smaller second. This ensures deterministic addresses regardless of input order.
* **Position NFTs**: Positions are Token-2022 NFTs. The `positionNftMint` keypair must be a signer during pool initialization and position creation.
* **Activation**: Pools can have delayed activation (slot-based or timestamp-based). Set `activationPoint: null` for immediate activation.
* **One customizable pool per pair**: The `initializeCustomizablePool` instruction allows only one pool per token pair since the PDA doesn't include a config key.
