Kodiak Finance
  • OVERVIEW
    • 🐻‍❄️Introducing Kodiak
    • 🐻Kodiak x Berachain
    • ✉️Contact Us
    • 🍯Kodiak Contracts
  • 🅱️Kodiak-Boyco
  • PROTOCOL
    • 🔃DEX
      • Swaps
      • Liquidity Provision
      • Trading Fees
    • 🏝️Islands
      • Island Liquidity Provision
      • Sweetened Islands
      • Auto-BGT
    • 🐼Panda Factory
  • 🪙Tokenomics
    • Kodiak Pre-TGE Rewards
  • 🧠User Guide
    • Launch a Token Launch on Panda Factory
    • Trading on Panda Factory
    • Swap
    • Create a V2 Position
    • Create a V3 Position
    • Add/Stake Islands Liquidity
    • Migrating to a Reward Vault
    • Deploying new Permissonless Islands
    • Deploying and Configuring a Kodiak Farm
    • Add your token
  • Add Your Project to the Ecosystem
  • 👨‍💻Developers
    • 🐼Panda
      • Technical Integration Guide
      • Subgraph
        • Entity Reference
        • Query Guide
        • Advanced Usage Guide
      • Smart Contract Reference
        • Panda Factory
        • Panda Pool
        • Panda Token
      • Api
    • Farms
      • Technical Integration Guide
      • Smart Contract Reference
    • 🌴Kodiak Islands
      • Technical Integration Guide
        • Understanding Token Deposit Ratio
      • Subgraph
        • Entity Reference
        • Query Guide
        • Advanced Usage Guide
      • Smart Contract Reference
        • Kodiak Island Factory
        • Kodiak Island
        • Kodiak Island Router
      • Api
    • 💰Pricing with Subgraph
    • 💱Quotes
    • Backend
  • 🛡️SECURITY
    • 🔍Audits
  • ℹ️Informational
    • 📜Terms of Use
    • 🔏Privacy Policy
    • TradingView Advanced License
Powered by GitBook
On this page
  • Overview
  • Managing Liquidity
  • Adding liquidity with both tokens
  • Adding Liquidity with a single token
  • Adding Liquidity with Native BERA
  • Important Notes
  • Removing Liquidity
  • ABI
  1. Developers
  2. Kodiak Islands
  3. Smart Contract Reference

Kodiak Island Router

A helper contract that enables easy, secure liquidity provisioning and withdrawals

Overview

The IslandRouter contract serves as a helper contract for users to easily provide liquidity to Kodiak Islands. It handles the complexities of depositing tokens, slippage protection, and token swaps when needed, while ensuring optimal liquidity provision.

Kodiak Islands require liquidity providers to deposit tokens in specific ratios that match the Island's underlying Kodiak V3 position. The router provides several key protections:

  • Minimum output amount protection for swaps when providing liquidity with single token.

  • Deposit ratio slippage protection

  • Minimum LP token (shares) protection

Managing Liquidity

Token Deposit Ratio

The router handles liquidity addition by:

  1. Calculating the optimal deposit amounts using island.getMintAmounts()

  2. Ensuring the amounts meet minimum requirements

  3. Transferring tokens and minting LP tokens

Steps to Add Liquidity

  1. Approve the router to spend your tokens (both tokens or one token in case of singleSided

  2. Choose the appropriate liquidity addition method based on your tokens

  3. If the liquidity addition is using a single token, find the appropriate swap data and use Kodiak Quoter api to get the calldata according to the swap params.

  4. Set reasonable slippage parameters

  5. Execute transaction

  6. Kodiak Island LP tokens sent to the receiver as passed in params

  7. In case of zaps msg.sender receives back any unused token0 or token1

Adding liquidity with both tokens

1. Standard Two Token Deposit

Prerequisites

  • Token0 and Token1 balances sufficient for desired liquidity.

  • Approved router contract to spend both tokens.

function addLiquidity(
    IKodiakIsland island,     // Address of the Kodiak Island
    uint256 amount0Max,       // Maximum amount of token0 willing to deposit
    uint256 amount1Max,       // Maximum amount of token1 willing to deposit
    uint256 amount0Min,       // Minimum acceptable token0 deposit (slippage protection)
    uint256 amount1Min,       // Minimum acceptable token1 deposit (slippage protection)
    uint256 amountSharesMin,  // Minimum IslandTokens to receive
    address receiver          // Address to receive LP tokens
) external returns (
    uint256 amount0,         // Actual token0 amount deposited
    uint256 amount1,         // Actual token1 amount deposited
    uint256 mintAmount       // LP tokens received
)

Implementation Example

// 1. Start with maximum amounts to deposit, for example 10 token0 and 10 token1
const amount0Max = ethers.parseUnits("10", token0Decimals);
const amount1Max = ethers.parseUnits("10", token1Decimals);

// 2. Find the appropriate ratio of tokens to deposit
(amount0Used, amount1Used, ) = await island.getMintAmounts(amount0Max, amount1Max)

amount0Max = amount0Used
amount1Max = amount1Used

// 3. Set slippage tolerance for the minimum amounts of token0 and token1 
// (e.g. 1% slippage). This means that atleast 99% of these tokens should 
// be deposited in the pool. This gives you a protection from the pool price
// deviating a lot before your transaction goes through
//  which affects the tokens and ratio they are deposited in
const amount0Min = amount0Max.mul(99).div(100);
const amount1Min = amount1Max.mul(99).div(100);
const amountSharesMin = 0; // Set based on expected shares

// 4. Approve router
await token0.approve(routerAddress, amount0Max);
await token1.approve(routerAddress, amount1Max);

// 5. Callstatic deposit to get the amountShares minted
const simulationResult = await router.callStatic.addLiquidity(
    islandAddress,
    amount0Max,
    amount1Max,
    amount0Min,
    amount1Min,
    amountSharesMin,
    receiverAddress
);

// 6. Mint Island tokens, Use the mintAmount from above
// add a comfortable slippage (ex 1%) to this and use this for amountSharesMin
// Use BPS for higher precision.
amountSharesMin = simulationResult[2]
       .mul(99).div(100).toString()

const tx = await router.addLiquidity(
    islandAddress,
    amount0Max,
    amount1Max,
    amount0Min,
    amount1Min,
    amountSharesMin,
    receiverAddress
);

2. Native BERA + Token Deposit

It is important that one of the tokens in the underlying pool is WBERA for depositing with native BERA.

Prerequisites

  • Sender must have sufficient NativeToken and Token1 balances for desired liquidity.

  • Approve router contract to spend Token1 tokens.

  • Send required bera as msg.value

// Assuming token0 is WBERA
function addLiquidityNative(
    IKodiakIsland island,     // Address of the Kodiak Island
    uint256 amount0Max,       // Maximum BERA amount
    uint256 amount1Max,       // Maximum token amount
    uint256 amount0Min,       // Minimum BERA deposit
    uint256 amount1Min,       // Minimum token deposit
    uint256 amountSharesMin,  // Minimum LP tokens to receive
    address receiver          // Address to receive LP tokens
) external payable returns (
    uint256 amount0,         // Actual BERA amount deposited
    uint256 amount1,         // Actual token amount deposited
    uint256 mintAmount       // LP tokens received
)

Implementation is same as above except for sending native token BERA as msg.value

const tx = await router.addLiquidityNative(
    islandAddress,
    amount0Max,
    amount1Max,
    amount0Min,
    amount1Min,
    amountSharesMin,
    receiverAddress,
    {
        value: amount0Max // assuming island.token0() is WBERA
    }
);

Adding Liquidity with a single token

When depositing into a concentrated liquidity position like an Island, it's crucial to understand that the underlying tokens need to be in a specific ratio to maximize the value of the position. If a user wants to deposit a single token, a swap is required to balance the tokens before adding liquidity to the position. This process involves calculating the ideal amount of the input token to swap for the other token in the pair.

The IslandRouter uses a external swap Routers to swap this token to achieve maximal efficiency during the swap. The kodiak router is whitelisted to begin with and other routers will be whitelisted later to further increase this swap efficiency.

Prerequisites

  • Sufficient balance of input token or native token.

  • Approved router for input token amount

  • Understanding on how to find the swap params such that after the swap the token0 and token1 are in correct ratio to deposit into the island

  • Understanding on how to generate swap calldata using Kodiak Router.

Single Token Deposit Implementation

function addLiquiditySingle(
    IKodiakIsland island,              // Island address
    uint256 totalAmountIn,             // Total input token amount
    uint256 amountSharesMin,           // Minimum LP tokens to receive
    uint256 maxStakingSlippageBPS,     // Max slippage in basis points (100 = 1%)
    RouterSwapParams calldata swapData, // Swap parameters for converting portion of input
    address receiver                    // LP token recipient
) external returns (
    uint256 amount0,                   // Final token0 amount deposited
    uint256 amount1,                   // Final token1 amount deposited
    uint256 mintAmount                 // LP tokens received
)

struct RouterSwapParams {
    bool zeroForOne;        // Swap direction
    uint256 amountIn;       // Amount to swap
    uint256 minAmountOut;   // Minimum output from swap
    bytes routeData;        // Encoded swap route data
}

Implementation Example

// 1. Prepare swap parameters. refer the section on finding the swap amounts
const swapParams = {
    zeroForOne: true, // true if swapping token0 for token1
    amountIn: swapAmount,
    minAmountOut: minimumSwapOutput,
    routeData: swapCalldata
};

// 2. Set slippage parameters
const maxStakingSlippageBPS = 100; // 1% slippage
const minShares = minShares; // find the min shares by making a static call 
// and adding a 1% slippage to it as demonstrated in previous examples

// 3. Approve island router to spend your inputToken
await token0.approve(islandRouterAddress, totalAmount);

// 3. Execute single token deposit
const tx = await islandRouter.addLiquiditySingle(
    islandAddress,
    totalAmount,
    minShares,
    maxStakingSlippageBPS,
    swapParams,
    receiverAddress
);

Adding Liquidity with Native BERA

When you want to provide liquidity to an island with native token BERA.

Prerequisites

  • Native BERA balance

  • Island must have WBERA as one of its tokens

Single Native Token Deposit

function addLiquiditySingleNative(
    IKodiakIsland island,              // Island address (must include WBERA)
    uint256 amountSharesMin,           // Minimum LP tokens to receive
    uint256 maxStakingSlippageBPS,     // Max slippage in basis points
    RouterSwapParams calldata swapData, // Swap parameters
    address receiver                    // LP token recipient
) external payable returns (
    uint256 amount0,                   // Final token0 amount deposited
    uint256 amount1,                   // Final token1 amount deposited
    uint256 mintAmount                 // LP tokens received
)

Implementation Example

// 1. Calculate BERA amount to send
const beraAmount = ethers.parseEther("1.0");

// 2. Prepare swap parameters
const swapParams = {
    zeroForOne: true,
    amountIn: swapAmount,
    minAmountOut: minOutput,
    routeData: swapCalldata
};

// 3. Execute native deposit
const tx = await islandRouter.addLiquiditySingleNative(
    islandAddress,
    minimumShares,
    100, // 1% max slippage
    swapParams,
    receiverAddress,
    { value: beraAmount }
);

Important Notes

  • For native BERA deposits, one token in the Island pair must be WBERA

  • Unused BERA is automatically returned to sender as native tokens when performing single sided deposits with native BERA

  • If the msg.sender is a contract, it must handle both type of unused tokens i.e ERC20 tokens and native tokens when depositing with single token or native token

  • Set appropriate slippage parameters based on market conditions

  • Monitor gas costs, especially for operations involving swaps

  • Verify all addresses and amounts before execution

  • Consider using view functions to estimate outputs before executing transactions

Removing Liquidity

The router exposes two functions for removing liquidity with slippage control

removeLiquidity

function removeLiquidity(
    IKodiakIsland island,
    uint256 burnAmount,
    uint256 amount0Min,
    uint256 amount1Min,
    address receiver
) external returns (uint256 amount0, uint256 amount1, uint128 liquidityBurned)

Purpose: Removes liquidity from a Kodiak Island position by burning LP tokens and getting the underlying tokens.

Parameters:

  • island: The address of the Kodiak Island position to withdraw from

  • burnAmount: The quantity of Kodiak Island LP tokens to burn

  • amount0Min: Minimum amount of token0 that must be received (slippage protection)

  • amount1Min: Minimum amount of token1 that must be received (slippage protection)

  • receiver: The address that will receive the withdrawn tokens

Returns:

  • amount0: The actual amount of token0 received

  • amount1: The actual amount of token1 received

  • liquidityBurned: The amount of liquidity removed from the underlying Kodiak V3 position

Important Notes:

  • Caller must approve the router contract to spend their Kodiak Island LP tokens

  • Transaction will revert if received amounts are less than specified minimums

  • Useful for standard token pairs where wrapped native token conversion isn't needed


removeLiquidityNative

function removeLiquidityNative(
    IKodiakIsland island,
    uint256 burnAmount,
    uint256 amount0Min,
    uint256 amount1Min,
    address payable receiver
) external returns (uint256 amount0, uint256 amount1, uint128 liquidityBurned)

Purpose: Similar to removeLiquidity but specifically handles positions involving WBERA (wrapped BERA), automatically unwrapping it to native BERA before returning it to the user.

Parameters:

  • island: The address of the Kodiak Island position to withdraw from

  • burnAmount: The quantity of Kodiak Island LP tokens to burn

  • amount0Min: Minimum amount of token0 that must be received (slippage protection)

  • amount1Min: Minimum amount of token1 that must be received (slippage protection)

  • receiver: The address that will receive the withdrawn tokens (must be payable to receive native BERA)

Returns:

  • amount0: The actual amount of token0 received

  • amount1: The actual amount of token1 received

  • liquidityBurned: The amount of liquidity removed from the underlying Kodiak V3 position

Important Notes:

  • Caller must approve the router contract to spend their Kodiak Island LP tokens

  • One of the tokens in the pair must be WBERA

  • WBERA portion will be automatically unwrapped and sent as native BERA to the receiver

  • The non-WBERA token will be sent as is to the receiver

  • Transaction will revert if received amounts are less than specified minimums

  • Receiver address must be payable to receive native BERA

  • Particularly useful for positions involving native BERA pairs

For both functions, it's recommended to:

  1. Calculate expected output amounts before calling

  2. Include reasonable slippage protection via amount0Min and amount1Min

  3. Ensure sufficient approvals are in place

  4. Handle both success and failure cases in your integration

  5. Verify received amounts match expectations after the transaction

ABI

```json
[
        {
            "type": "constructor",
            "inputs": [
                {
                    "name": "_wBera",
                    "type": "address",
                    "internalType": "contract IWETH"
                },
                {
                    "name": "_kodiakRouter",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "stateMutability": "nonpayable"
        },
        {
            "type": "receive",
            "stateMutability": "payable"
        },
        {
            "type": "function",
            "name": "addLiquidity",
            "inputs": [
                {
                    "name": "island",
                    "type": "address",
                    "internalType": "contract IKodiakIsland"
                },
                {
                    "name": "amount0Max",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1Max",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount0Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amountSharesMin",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "receiver",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "outputs": [
                {
                    "name": "amount0",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "mintAmount",
                    "type": "uint256",
                    "internalType": "uint256"
                }
            ],
            "stateMutability": "nonpayable"
        },
        {
            "type": "function",
            "name": "addLiquidityNative",
            "inputs": [
                {
                    "name": "island",
                    "type": "address",
                    "internalType": "contract IKodiakIsland"
                },
                {
                    "name": "amount0Max",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1Max",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount0Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amountSharesMin",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "receiver",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "outputs": [
                {
                    "name": "amount0",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "mintAmount",
                    "type": "uint256",
                    "internalType": "uint256"
                }
            ],
            "stateMutability": "payable"
        },
        {
            "type": "function",
            "name": "addLiquiditySingle",
            "inputs": [
                {
                    "name": "island",
                    "type": "address",
                    "internalType": "contract IKodiakIsland"
                },
                {
                    "name": "totalAmountIn",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amountSharesMin",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "maxStakingSlippageBPS",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "swapData",
                    "type": "tuple",
                    "internalType": "struct RouterSwapParams",
                    "components": [
                        {
                            "name": "amountIn",
                            "type": "uint256",
                            "internalType": "uint256"
                        },
                        {
                            "name": "minAmountOut",
                            "type": "uint256",
                            "internalType": "uint256"
                        },
                        {
                            "name": "zeroForOne",
                            "type": "bool",
                            "internalType": "bool"
                        },
                        {
                            "name": "routeData",
                            "type": "bytes",
                            "internalType": "bytes"
                        }
                    ]
                },
                {
                    "name": "receiver",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "outputs": [
                {
                    "name": "amount0",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "mintAmount",
                    "type": "uint256",
                    "internalType": "uint256"
                }
            ],
            "stateMutability": "nonpayable"
        },
        {
            "type": "function",
            "name": "addLiquiditySingleNative",
            "inputs": [
                {
                    "name": "island",
                    "type": "address",
                    "internalType": "contract IKodiakIsland"
                },
                {
                    "name": "amountSharesMin",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "maxStakingSlippageBPS",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "swapData",
                    "type": "tuple",
                    "internalType": "struct RouterSwapParams",
                    "components": [
                        {
                            "name": "amountIn",
                            "type": "uint256",
                            "internalType": "uint256"
                        },
                        {
                            "name": "minAmountOut",
                            "type": "uint256",
                            "internalType": "uint256"
                        },
                        {
                            "name": "zeroForOne",
                            "type": "bool",
                            "internalType": "bool"
                        },
                        {
                            "name": "routeData",
                            "type": "bytes",
                            "internalType": "bytes"
                        }
                    ]
                },
                {
                    "name": "receiver",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "outputs": [
                {
                    "name": "amount0",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "mintAmount",
                    "type": "uint256",
                    "internalType": "uint256"
                }
            ],
            "stateMutability": "payable"
        },
        {
            "type": "function",
            "name": "kodiakRouter",
            "inputs": [],
            "outputs": [
                {
                    "name": "",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "stateMutability": "view"
        },
        {
            "type": "function",
            "name": "removeLiquidity",
            "inputs": [
                {
                    "name": "island",
                    "type": "address",
                    "internalType": "contract IKodiakIsland"
                },
                {
                    "name": "burnAmount",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount0Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "receiver",
                    "type": "address",
                    "internalType": "address"
                }
            ],
            "outputs": [
                {
                    "name": "amount0",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "liquidityBurned",
                    "type": "uint128",
                    "internalType": "uint128"
                }
            ],
            "stateMutability": "nonpayable"
        },
        {
            "type": "function",
            "name": "removeLiquidityNative",
            "inputs": [
                {
                    "name": "island",
                    "type": "address",
                    "internalType": "contract IKodiakIsland"
                },
                {
                    "name": "burnAmount",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount0Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1Min",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "receiver",
                    "type": "address",
                    "internalType": "address payable"
                }
            ],
            "outputs": [
                {
                    "name": "amount0",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "amount1",
                    "type": "uint256",
                    "internalType": "uint256"
                },
                {
                    "name": "liquidityBurned",
                    "type": "uint128",
                    "internalType": "uint128"
                }
            ],
            "stateMutability": "nonpayable"
        },
        {
            "type": "function",
            "name": "wBera",
            "inputs": [],
            "outputs": [
                {
                    "name": "",
                    "type": "address",
                    "internalType": "contract IWETH"
                }
            ],
            "stateMutability": "view"
        }
    ]
```
PreviousKodiak IslandNextApi

Last updated 2 months ago

👨‍💻
🌴