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