Understanding Token Deposit Ratio

This guide outlines the token Deposit ratio, how to find it, and how to reach there with a single token

Overview

When providing liquidity to a Kodiak Island, tokens must be deposited in a specific ratio determined by the current price and position of the underlying Kodiak V3 pool.

Core Concepts

  • Island Token Ratio: Each Island has a target ratio of its underlying tokens (token0 and token1). This ratio is determined by the current price of the tokens and the position's boundaries.

  • Single-Sided Deposits: Users often want to deposit a single token. In this case, a portion of the input token must be swapped for the other token to match the Island's target ratio.

  • Price Impact: Swapping tokens can cause price impact, which is the change in price due to the size of the trade. We want to minimize this impact while still achieving the desired token ratio.

  • Optimal Swap Amount: The goal is to find the swap amount that results in the most efficient deposit into the Island, considering the target ratio and minimizing price impact.

  • Make sure to account for difference in token decimals in all calculations carried out.

Depositing with Both Tokens

The ratio of tokens to be deposited in an island can be found by calling the function island.getMintAmounts(amount0Max, amount1Max) on the particular island with the max amounts of token0 and token1 the user is willing to deposit. This returns (amount0, amount1, liquidityMinted) where amount0 and amount1 correspond to the amount of tokens that will be used by the island and the amount of liquidity that is minted using (amount0, amount1). This is the ratio of tokens that needs to be deposited into the island based on the current island position and the underlying Kodiak pool's current token price.

function getMintAmounts(
    uint256 amount0Max,
    uint256 amount1Max
) external view returns (
    uint256 amount0,
    uint256 amount1,
    uint256 mintAmount
)

Finding Deposit Ratio and Swap Data for Zaps

We will describe below the process of finding the swap amount and to keep it simple we will assume that the liquidity is high and swap does not have a large price impact thus ignoring it for the following example.

Step 1 - Finding the correct ratio of tokens to deposit

We use one unit of both tokens and call the getMintAmounts function on the island to check the amounts of tokens deposited thus giving us a ratio. We then normalize it to 18 decimals to remove all decimal discrepancy

async function getIslandRatio(islandContract: ethers.Contract, token0: Token, token1: Token): Promise<IslandState> {
    const amount0 = parseUnits('1', token0Decimals);
    const amount1 = parseUnits('1', token1Decimals);
    const rawMintAmounts = await islandContract.getMintAmounts(amount0.toString(), amount1.toString());
    const amount0Used = BigNumber.from(rawMintAmounts[0]);
    const amount1Used = BigNumber.from(rawMintAmounts[1]);

    const normalizedAmount0 = amount0Used.mul(BigNumber.from(10).pow(18 - (token0?.decimals || 18)));
    const normalizedAmount1 = amount1Used.mul(BigNumber.from(10).pow(18 - (token1?.decimals || 18)));
    const ratio = normalizedAmount1.mul(10000).div(normalizedAmount0);

    return { amount0: normalizedAmount0, amount1: normalizedAmount1, ratio };
}

Step 2 - We get the swap price of inputToken -> OutputToken from the Swap router(KodiakRouter)

We use the kodiak quoter api to fetch the current price of the input token in terms of the output token.

The response returns the quote which is the amount of output token you would receive for 1 unit of input token. We normalize this to 18 decimals to correspond to step1 and bring everything normalized to 18 decimals

async function getKodiakQuote(
    kodiakApiUrl: string,
    baseToken: Token, // inputToken
    quoteToken: Token, // outputToken
    inputAmount: BigNumber, // we use 1 unit (i.e 1e18 for honey or 1e6 for usdc)
    // of inputToken
    chainId: number,
    slippage: string,
    deadline: number,
    receiver?: string // should be kodiak router
): Promise<QuoteResult> {
    const publicApiUrl = new URL(kodiakApiUrl);
    publicApiUrl.searchParams.set('tokenInAddress', baseToken.address);
    publicApiUrl.searchParams.set('tokenInChainId', chainId.toString());
    publicApiUrl.searchParams.set('tokenOutAddress', quoteToken.address);
    publicApiUrl.searchParams.set('tokenOutChainId', chainId.toString());
    publicApiUrl.searchParams.set('amount', inputAmount.toString());
    publicApiUrl.searchParams.set('type', 'exactIn');
    publicApiUrl.searchParams.set('slippageTolerance', slippage);
    publicApiUrl.searchParams.set('deadline', deadline.toString());
    if (receiver) publicApiUrl.searchParams.set('recipient', receiver);

    const response = await fetch(publicApiUrl.toString());
    const json: QuoteResult = await response.json();
    return json;
}

// This is what the API returns.
export interface GetQuoteResult {
  quoteId?: string
  blockNumber: string
  amount: string
  amountDecimals: string
  gasPriceWei: string
  gasUseEstimate: string
  gasUseEstimateQuote: string
  gasUseEstimateQuoteDecimals: string
  gasUseEstimateUSD: string
  methodParameters?: { calldata: string; value: string }
  quote: string
  quoteDecimals: string
  quoteGasAdjusted: string
  quoteGasAdjustedDecimals: string
  route: Array<(V3PoolInRoute | V2PoolInRoute)[]>
  routeString: string
}

Step 3 - Calculate the swap amount based on the information from step 1 and step 2

This step calculates the ideal swap amount assuming no price impact.

The formula depends on whether the input token is token0 or token1.

If input token is token0:

swapAmount = (i1 * amount * 1 ether) / (i0 * exchangePriceX18 + (i1 * 1 ether))

If input token is token1:

swapAmount = (i0 * amount * 1 ether) / (i1 * exchangePriceX18 + i0 * 1 ether)        

Where:

- i0 is the amount of token0 normalized to 18 decimals,
   deposited in the island from step1
- i1 is the amount of token1 normalized to 18 decimals,
   deposited in the island from step1
- amount is the total amount of the input token the user wants to deposit
- exchangePriceX18 is the price of the input token in terms of the output token,
  normalized to 18 decimals.

4. Get Final Swap Data

Now fetch the final swap data using the amount from step3, this returns the outputQuote and the calldata for the transaction which need to be used for constructing the RouterSwapParams passed to the IslandRouter.

Last updated