# Concentrated Liquidity Pool Integration

## Purpose

Guide on how to Build Transaction Swap Concentrated Pool Token:

* Example transaction on preprod: <https://preprod.cardanoscan.io/transaction/77a2a7786629f9280a9e0653e07d8bf153376555307efd3d066fd6ebae881140> &#x20;
* Example transaction on mainnet: <https://cardanoscan.io/transaction/402a6320fe48781cf103d42951c22fc0aeb782a35c9306db1ba258397b02d65b>

### **Constants**

**Preprod**:

```
POOL_SCRIPT_HASH="04041c3c6ba87b33f2c9eb7f7dbeae3b26003c3e199d438bb99932a2"
POOL_SCRIPT_OUT_REF="2e19cca74e3badcab26aef7574aa1885ba97228a254ca227ba2f79f2b75fd136#0"
PROTOCOL_SCRIPT_HASH="26ec271e96420bd548932f350e76ea38590da68e12f0f55bd5473f67"
PROTOCOL_CONFIG_OUT_REF="3775af36f485f9c97101ee5b9b360c34f0f8e12186bc9060f358b7fc8ce468a4#0"
```

**Mainnet**:

```
POOL_SCRIPT_HASH="d8b69fc53637bcfadbc4469083f706bc293f4d9d2296646c5ca167bb"
POOL_SCRIPT_OUT_REF="64d111b957e7d7848ffdde5149aa77fa4090a7fa1ad0ac108067900614848501#0"
PROTOCOL_SCRIPT_HASH="fa991bc2f9c4206e72d713bc3487a72e7901057cabb8d364bebeef8f"
PROTOCOL_CONFIG_OUT_REF="2cafd7c92f7093e5229af274be83dea660b0590b4174bbed79ba662b44fbd1ee#0"
```

## Steps to Build a Transaction Swap Token

### Step 1: Retrieve Necessary Parameters

* Call the API [GetListConcentratedPool](https://dapp-indexers.api.danogo.io/api/v1/concentrated/pools) to get list pools. For example, to get all pools with given token pair:

Mainnet API:

```bash
curl 'https://dapp-indexers.api.danogo.io/api/v1/concentrated/pools?limit=20&offset=&tokenA=&tokenB=1f3aec8bfe7ea4fe14c5f121e2a92e301afe414147860d557cac7e34.5553444378'
```

Preprod API:

```bash
curl 'https://liquidity-pair-preprod.dev.tekoapis.net/api/v1/concentrated/pools?limit=20&offset=&tokenA=&tokenB=9a614be30284aa88eb845da7657b5d0a235f1b95628b23c08050d502.6655534441'
```

* Use `lpToken` from each pool to call the API [GetConcentratedPoolParams](https://dapp-indexers.api.danogo.io/api/v1/concentrated/pool-params) to retrieve the necessary parameters from a pool:

Mainnet API:

```bash
curl 'https://dapp-indexers.api.danogo.io/api/v1/concentrated/pool-params?lpToken=d8b69fc53637bcfadbc4469083f706bc293f4d9d2296646c5ca167bb.d602bf16c7710f7453bd18fcb156beb2aa422ef57c8f4e780d243712'
```

Preprod API:

```bash
curl 'https://liquidity-pair-preprod.dev.tekoapis.net/api/v1/concentrated/pool-params?lpToken=9a614be30284aa88eb845da7657b5d0a235f1b95628b23c08050d502.6655534441'
```

### Step 2: **Build the Redeemer Structure**

#### Concentrated ExchangeActionRedeemer Structure:

To optimize data size and allow one transaction (txn) to swap across multiple pools simultaneously, the Redeemer is a ByteArray, where each field has a defined index and byte size.

* ExchangeActionRedeemer structure for Token Swap:

```rust
pub type ExchangeActionRedeemer {
    in_idx: Int, // 1 byte: protocol_config_idx for withdraw-zero pool_skh, pool_in_idx for others
    action: ExchangeAction // 1 byte
}

pub type ExchangeAction {
    CreatePool(CreatePoolParams)
    // platform_fee_out_idx > 0 if platform_fee_X/Y > 0
    ModifyLiquidity(Int, List<ModifyLiquidityParams>)
    // (UTxOType, LicenseIdx)
    WithdrawPlatformFee((Int, Int) ,List<WithdrawPlatformFeeParams>)
    Swap(List<SwapParams>)
    // platfrom_fee_out_idx
    ClaimUndefined((Int, Int) ,List<ClaimUndefinedParams>)
    DelegatePool
}

type SwapParams {
    pool_in_idx: Int, // 1 byte
    pool_out_idx: Int, // 1 byte
    delta_amount: Int, // 32 byte (Int256)
}
```

* Diagnostic Notation format:

```
h'0103...' // Swap Action = 03
```

* CBOR format (single pool):

```
  58240103... // 36 bytes (<in_idx><action><pool_in_idx><pool_out_idx><delta_amount>)
```

### Step 3: Create the Transaction

Use the retrieved parameters to create the token swap transaction.

* Note: To minimize DDoS attacks in multi-hop pools, each pool, when swapped, will collect ADA fees according to protocol\_config.swap\_fee and add them to the Pool Datum:

```
pool_out_datum.total_swap_fee=pool_in_datum.total_swap_fee+protocol_config.swap_fee
```

* Amount of `swapFee` to be charged and current `totalSwapFee` in Pool Datum is returned by API.

{% stepper %}
{% step %}

### Validity range

Set the validity range for the transaction.

* Ensure the time-to-live (TTL) <=6 minutes.
  {% endstep %}

{% step %}

### Input

* Note: The `outRef`, `address`, `coin`, and `multiAssets` information is fully returned by API Get Params.
  * PoolInUtxo: The pool you intend to swap tokens with.
    * Spend Redeemer: Build the redeemer according to the **ExchangeActionRedeemer** Swap structure for Concentrated Pool, ensuring the specified indices are correct.
    * The input Pool UTxO must have the valid PoolNFT:
      * pool\_in\_asset\[PoolNFT] = 1
        {% endstep %}

{% step %}

### Output

* PoolOutUtxo: The output of the pool after the swap.
  * delta\_amount > 0 -> Trader sells X, Pool receives X and pays Y. Simultaneously increases platform\_fee\_X accumulation.
    * poolChangeX = redeemer.delta\_amount
  * delta\_amount < 0 -> Trader sells Y, Pool receives Y and pays X. Simultaneously increases platform\_fee\_Y accumulation.
    * poolChangeY = -redeemer.delta\_amount
  * The pool datum structure must adhere to the correct order:

```rust
type PoolDatum {
    // Pool liquidity pair used for trading
    token_X: TupleAsset,
    token_Y: TupleAsset,
    //
    lp_fee_rate: Basis, // LP fee decided by pool creator
    platform_fee_X: Int, // platform fee accumulated on each swap transaction collected in token X
    platform_fee_Y: Int, // platform fee accumulated on each swap transaction collected in token Y
    total_swap_fee: Int, // total swap_fee (ADA) accumulated
    // to save costs, onchain will let offchain calculate sqrt price
    sqrt_lower_price: PRational,
    sqrt_upper_price: PRational,
    // min X, min Y to avoid DDOS when ModifyLiquidity and Swap
    min_x_change: Int,
    min_y_change: Int,
    circulating_lp_token: Int, // current lp token in circulation, changed when supply/withdraw liquidity
    last_withdraw_epoch: Int // the last epoch when a withdrawal was made from the pool
}

type ProtocolConfigDatum {
    platform_fee_rate: Basis, //% of LP Fee that the platform takes on each swap. Default = 1000
    swap_fee: Int //In Lovelace. The fee collected by the platform, to avoid DDoS by chainning multiple pools
}
```

* Temporary variables:

  * **basis = 10000**
  * **pool\_utxo\_min\_ada** = 3\_000\_000 (lovelace)
  * **poolinX**: `tokenAReserve` from the API
  * **poolinY**: `tokenBReserve` from the API
  * **poolinLPX**: liquidity of token X before the txn
    * if token X is ADA: = poolinX - pool\_in\_datum.platform\_fee\_x - pool\_in\_datum.total\_swap\_fee - pool\_utxo\_min\_ada
    * else: = poolinX - pool\_in\_datum.platform\_fee\_x
  * **poolinLPY**: liquidity of token Y before the txn = pool\_in\_asset\[Y] - pool\_in\_datum.platform\_fee\_y
  * $$\sqrt{P\_a} = pool\_in\_datum.sqrt\_lower\_price$$
  * $$\sqrt{P\_b} = pool\_in\_datum.sqrt\_upper\_price$$
  * $$L=CEIL(\frac{poolinLPY + poolinLPX\sqrt{P\_a}\sqrt{P\_b} + \sqrt{(poolinLPY-poolinLPX\sqrt{P\_a}\sqrt{P\_b})^2 + 4poolinLPXpoolinLPY\*P\_b}}{2(\sqrt{P\_b}-\sqrt{P\_a})})$$
  * $$X\_v=poolinLPX+CEIL(\frac{L}{\sqrt{P\_b}})$$
  * $$Y\_v=poolinLPY+CEIL(L\sqrt{P\_a})$$
  * $$offFee = 1-pool\_in\_datum.lp\_fee\_rate/basis$$

* Constraints:
  * |poolChangeX| >= pool\_in\_datum.min\_x\_change

  * |poolChangeY| >= pool\_in\_datum.min\_y\_change

  * IF pool\_in\_datum.token\_x == ""
    * Required withdrawal of the rewards by the pool's stake credential (before this action)

  * if poolChangeX > 0:
    * $$pool\_out\_datum.platform\_fee\_x = FLOOR(pool\_in\_datum.platform\_fee\_x + \frac{pool\_in\_datum.lp\_fee\_rate}{basis}\*\frac{platform\_fee\_rate}{basis}\*poolChangeX)$$

    * $$pool\_out\_datum.platform\_fee\_y = pool\_in\_datum.platform\_fee\_y$$

    * $$poolChangeY >= CEIL(X\_v\*Y\_v / (X\_v + poolChangeX \* offFee) - Y\_v)$$

  * else:
    * $$pool\_out\_datum.platform\_fee\_x = pool\_in\_datum.platform\_fee\_x$$
    * $$pool\_out\_datum.platform\_fee\_y = FLOOR(pool\_in\_datum.platform\_fee\_y + \frac{pool\_in\_datum.lp\_fee\_rate}{basis}\*\frac{platform\_fee\_rate}{basis}\*poolChangeY)$$
    * $$poolChangeX >= CEIL(X\_v\*Y\_v / (Y\_v + poolChangeY \* offFee) - X\_v)$$
    * pool\_out\_datum.last\_withdraw\_epoch = curEpoch
    * pool\_out\_datum.total\_swap\_fee = pool\_in\_datum.total\_swap\_fee + protocol\_config\_ref.swap\_fee

  * Other fields in datum are not changed:
    * pool\_out\_datum.lp\_fee\_rate = pool\_in\_datum.lp\_fee\_rate
    * pool\_out\_datum.token\_x = pool\_in\_datum.token\_x
    * pool\_out\_datum.token\_y = pool\_in\_datum.token\_y
    * pool\_out\_datum.sqrt\_lower\_price = pool\_in\_datum.sqrt\_lower\_price
    * pool\_out\_datum.sqrt\_upper\_price = pool\_in\_datum.sqrt\_upper\_price
    * pool\_out\_datum.circulating\_lp\_token = pool\_in\_datum.circulating\_lp\_token

  * Pool UTxO's address must not be changed

* Diagnostic Notation format:

```
24_0(<<121_0([_
     [_ h'', h''], // tokenX (ADA)
     [_
         h'834a15101873b4e1ddfaa830df46792913995d8738dcde34eda27905',
         h'665553444d',
     ], // tokenY
     30_0, // lp_fee_rate
     104495_2, // platform_fee_X
     6826_1, // platform_fee_Y
     2200000_2, // total_swap_fee
     121_0([_ 4472135954999579_3, 10000000000000000_3]), // sqrt_lower_price
     121_0([_ 6324555320336759_3, 10000000000000000_3]), // sqrt_upper_price
     348057_2, // min_x_change
     100000_2, // min_y_change
     997000000_2, // circulating_lp_token
     70711_2, // last_withdraw_epoch
 ])>>)
```

{% endstep %}

{% step %}

### Reference Input

* Add all reference inputs:
  * pool\_script\_out\_ref: current smart contract's out ref is `64d111b957e7d7848ffdde5149aa77fa4090a7fa1ad0ac108067900614848501#0`
  * protocol\_config\_out\_ref: returned by the API, field `pool.protocolConfigOutRef`
  * If withdraw staking, add staking\_script\_out\_ref: returned by the API, field `stakingUtxo.outRef`
    {% endstep %}

{% step %}

### Withdrawal

* **Withdraw Pool Script Hash**:
  * Add withdraw-zero: Add withdrawal to reward address of Pool Script Hash with reward amount = 0
    * Note: Pool Script Hash can be extracted using lpToken's policy ID from the API
  * Redeemer: Built according to the ExchangeActionRedeemer structure.
* **Withdraw Staking Reward**: Must add staking withdrawal if pool contains ADA and current\_epoch > pool\_datum.last\_withdraw\_epoch.
  * Check reward amount of pool script hash and add withdrawal to its reward account.
  * Redeemer: Built according to the **ExchangeActionRedeemer** structure.
    {% endstep %}
    {% endstepper %}

**Note**: After sorting the inputs and reference inputs according to the chain's sort order, ensure the correct indices are used when specifying them in the redeemers.

We've built a typescript SDK for your reference: [Here](https://github.com/dano-finance/clmm-sdk)&#x20;


---

# 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.dano.finance/developers/integration/concentrated-liquidity-pool-integration.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.
