# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.fwog.fun/platform/fwogswap-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
