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