UNPKG

@symmetry-hq/liquidity-sdk

Version:

Exchange functionality using symmetry funds liquidity

287 lines (286 loc) 15.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadRouteData = exports.checkForLiquidity = exports.computeOutputAmount = exports.compute_amount_of_bought_token = exports.compute_value_of_sold_token = exports.usd_value_to_amount = exports.amount_to_usd_value = exports.mul_div = exports.WEIGHT_MULTIPLIER = exports.BPS_DIVIDER = void 0; const web3_js_1 = require("@solana/web3.js"); const types_1 = require("./types"); exports.BPS_DIVIDER = 10000; exports.WEIGHT_MULTIPLIER = 10000; function mul_div(a, b, c) { if (c == 0) return 0; return Math.floor(a * b / c); } exports.mul_div = mul_div; function amount_to_usd_value(amount, decimals, price) { return mul_div(amount, price, 10 ** decimals); } exports.amount_to_usd_value = amount_to_usd_value; function usd_value_to_amount(worth, decimals, price) { return mul_div(worth, 10 ** decimals, price); } exports.usd_value_to_amount = usd_value_to_amount; function compute_value_of_sold_token(amount, token_settings, price, start_amount, target_amount, curve_data) { let current_amount = start_amount; let curve_offset = (start_amount > target_amount) ? (start_amount - target_amount) : 0; let current_output_value = 0; let amount_left = amount; let current_price = price.sell_price; let total_fees = 0; for (let step = 0; step <= 10; step++) { let step_amount = (step < 10) ? curve_data.amount[step] : amount_left; if (step < 10 && curve_data.price[step] < current_price) { if (token_settings.useCurveData == true) { current_price = curve_data.price[step]; } } if (step == 10) { curve_offset = 0; } if (step_amount <= curve_offset) { curve_offset -= curve_data.amount[step]; continue; } let amount_in_interval = step_amount - curve_offset; curve_offset = 0; if (amount_in_interval > amount_left) { amount_in_interval = amount_left; } ; let amount_before_tw = amount_in_interval; if (current_amount >= target_amount) { amount_before_tw = 0; } else if (current_amount + amount_in_interval >= target_amount) { amount_before_tw -= current_amount + amount_in_interval - target_amount; } let amount_after_tw = amount_in_interval - amount_before_tw; let value_before_tw = amount_to_usd_value(amount_before_tw, token_settings.decimals, current_price); let value_after_tw = amount_to_usd_value(amount_after_tw, token_settings.decimals, current_price); let fees = mul_div(value_before_tw, token_settings.tokenSwapFeeBeforeTwBps, exports.BPS_DIVIDER) + mul_div(value_after_tw, token_settings.tokenSwapFeeAfterTwBps, exports.BPS_DIVIDER); total_fees += fees; current_output_value += value_before_tw + value_after_tw - fees; amount_left -= amount_in_interval; current_amount += amount_in_interval; if (amount_left == 0) { break; } } ; let fee_bps_x100 = mul_div(total_fees, exports.BPS_DIVIDER * 100, total_fees + current_output_value); return current_output_value; } exports.compute_value_of_sold_token = compute_value_of_sold_token; function compute_amount_of_bought_token(value, token_settings, price, start_amount, target_amount, curve_data) { let current_amount = start_amount; let curve_offset = (start_amount < target_amount) ? (target_amount - start_amount) : 0; let current_output_amount = 0; let value_left = value; let current_price = price.buy_price; let total_fees = 0; for (let step = 0; step <= 10; step++) { let step_amount = (step < 10) ? curve_data.amount[step] : usd_value_to_amount(value_left * 2, token_settings.decimals, current_price); if (step < 10 && curve_data.price[step] > current_price) { if (token_settings.useCurveData == true) { current_price = curve_data.price[step]; } ; } if (step == 10) { curve_offset = 0; } if (step_amount <= curve_offset) { curve_offset -= curve_data.amount[step]; continue; } let amount_in_interval = step_amount - curve_offset; curve_offset = 0; let value_in_interval = amount_to_usd_value(amount_in_interval, token_settings.decimals, current_price); if (value_in_interval > value_left) { value_in_interval = value_left; amount_in_interval = usd_value_to_amount(value_in_interval, token_settings.decimals, current_price); } let value_before_tw = value_in_interval; if (current_amount <= target_amount) { value_before_tw = 0; } else if (current_amount <= target_amount + amount_in_interval) { value_before_tw -= amount_to_usd_value(target_amount + amount_in_interval - current_amount, token_settings.decimals, current_price); } let value_after_tw = value_in_interval - value_before_tw; let fees = mul_div(value_before_tw, token_settings.tokenSwapFeeBeforeTwBps, exports.BPS_DIVIDER) + mul_div(value_after_tw, token_settings.tokenSwapFeeAfterTwBps, exports.BPS_DIVIDER); let amount_bought = usd_value_to_amount(value_in_interval - fees, token_settings.decimals, current_price); current_output_amount += amount_bought; value_left -= value_in_interval; total_fees += fees; if (amount_bought > current_amount) { current_amount = 0; } else { current_amount -= amount_bought; } if (value_left == 0) { break; } } ; let fee_bps_x100 = mul_div(total_fees, exports.BPS_DIVIDER * 100, value); return current_output_amount; } exports.compute_amount_of_bought_token = compute_amount_of_bought_token; function computeOutputAmount(from_token_id, to_token_id, from_amount, fundState, oracle_prices, curve_data, token_list) { fundState.currentCompToken = fundState.currentCompToken.map(x => x.toNumber()); fundState.currentCompAmount = fundState.currentCompAmount.map(x => x.toNumber()); fundState.targetWeight = fundState.targetWeight.map(x => x.toNumber()); fundState.weightSum = fundState.weightSum.toNumber(); fundState.numOfTokens = fundState.numOfTokens.toNumber(); let from_token_settings = token_list.list[from_token_id]; let to_token_settings = token_list.list[to_token_id]; let from_token_index = fundState.currentCompToken.findIndex((x) => x == from_token_id); let to_token_index = fundState.currentCompToken.findIndex((x) => x == to_token_id); if (!from_token_settings.isLive || !to_token_settings.isLive) return { toAmount: undefined, feePct: undefined }; if (from_token_index == -1 || to_token_index == -1) return { toAmount: undefined, feePct: undefined }; let fund_worth = 0; for (let i = 0; i < fundState.numOfTokens; i++) { let token = fundState.currentCompToken[i]; let token_settings = token_list.list[token]; let token_price = oracle_prices[token]; fund_worth += amount_to_usd_value(fundState.currentCompAmount[i], token_settings.decimals, token_price.avg_price); } let from_token_price = oracle_prices[from_token_id]; let to_token_price = oracle_prices[to_token_id]; let from_token_target_amount = usd_value_to_amount(mul_div(fundState.targetWeight[from_token_index], fund_worth, fundState.weightSum), from_token_settings.decimals, from_token_price.avg_price); let to_token_target_amount = usd_value_to_amount(mul_div(fundState.targetWeight[to_token_index], fund_worth, fundState.weightSum), to_token_settings.decimals, to_token_price.avg_price); let value = compute_value_of_sold_token(from_amount, from_token_settings, from_token_price, fundState.currentCompAmount[from_token_index], from_token_target_amount, curve_data.sell[from_token_id]); let to_amount = compute_amount_of_bought_token(value, to_token_settings, to_token_price, fundState.currentCompAmount[to_token_index], to_token_target_amount, curve_data.buy[to_token_id]); let amount_without_fees = usd_value_to_amount(amount_to_usd_value(from_amount, from_token_settings.decimals, from_token_price.sell_price), to_token_settings.decimals, to_token_price.buy_price); let fair_amount = usd_value_to_amount(amount_to_usd_value(from_amount, from_token_settings.decimals, from_token_price.avg_price), to_token_settings.decimals, to_token_price.avg_price); if (amount_without_fees > fundState.currentCompAmount[to_token_index]) { amount_without_fees = fundState.currentCompAmount[to_token_index]; } if (to_amount > amount_without_fees) { to_amount = amount_without_fees; } let total_fees = amount_without_fees - to_amount; let symmetry_bps = token_list.list[0].additionalData[60]; let symmetry_fee = mul_div(total_fees, symmetry_bps, 100); let host_bps = token_list.list[0].additionalData[61]; let host_fee = mul_div(total_fees, host_bps, 100); let manager_bps = token_list.list[0].additionalData[62]; let manager_fee = mul_div(total_fees, manager_bps, 100); let fund_fee = total_fees - symmetry_fee - host_fee - manager_fee; let confidence_bps = mul_div(fair_amount - amount_without_fees, exports.BPS_DIVIDER * 100, fair_amount); let fee_bps = mul_div(amount_without_fees - to_amount, exports.BPS_DIVIDER * 100, fair_amount); let from_token_worth_before_swap = amount_to_usd_value(fundState.currentCompAmount[from_token_index], from_token_settings.decimals, from_token_price.avg_price); let to_token_worth_before_swap = amount_to_usd_value(fundState.currentCompAmount[to_token_index], to_token_settings.decimals, to_token_price.avg_price); let toTokenAfter = fundState.currentCompAmount[to_token_index] - (amount_without_fees - fund_fee); let fromTokenAfter = fundState.currentCompAmount[from_token_index] + from_amount; let from_token_worth_after_swap = amount_to_usd_value(fromTokenAfter, from_token_settings.decimals, from_token_price.avg_price); let to_token_worth_after_swap = amount_to_usd_value(toTokenAfter, to_token_settings.decimals, to_token_price.avg_price); let from_old_weight = mul_div(from_token_worth_before_swap, exports.WEIGHT_MULTIPLIER, fund_worth); let to_old_weight = mul_div(to_token_worth_before_swap, exports.WEIGHT_MULTIPLIER, fund_worth); fund_worth = fund_worth - from_token_worth_before_swap; fund_worth = fund_worth - to_token_worth_before_swap; fund_worth = fund_worth + from_token_worth_after_swap; fund_worth = fund_worth + to_token_worth_after_swap; let from_new_weight = mul_div(from_token_worth_after_swap, exports.WEIGHT_MULTIPLIER, fund_worth); let to_new_weight = mul_div(to_token_worth_after_swap, exports.WEIGHT_MULTIPLIER, fund_worth); let allowed_offset = fundState.rebalanceThreshold.toNumber() * fundState.lpOffsetThreshold.toNumber(); let allowed_from_target_weight = mul_div(fundState.targetWeight[from_token_index], exports.BPS_DIVIDER * exports.BPS_DIVIDER + allowed_offset, exports.BPS_DIVIDER * exports.BPS_DIVIDER); let allowed_to_target_weight = mul_div(fundState.targetWeight[to_token_index], exports.BPS_DIVIDER * exports.BPS_DIVIDER - allowed_offset, exports.BPS_DIVIDER * exports.BPS_DIVIDER); if (allowed_from_target_weight > exports.WEIGHT_MULTIPLIER) { allowed_from_target_weight = exports.WEIGHT_MULTIPLIER; } let removing_dust = (from_token_id == 0 && fundState.targetWeight[to_token_index] == 0); if (from_new_weight > allowed_from_target_weight && (!removing_dust)) return { toAmount: undefined, feePct: undefined }; if (to_new_weight < allowed_to_target_weight) return { toAmount: undefined, feePct: undefined }; return { toAmount: Math.floor(to_amount), feePct: fee_bps / 100, }; } exports.computeOutputAmount = computeOutputAmount; function checkForLiquidity(tokenListData, curveChainData, fundAddress, fund, oraclePrices, tokenFrom, tokenTo, fromAmount) { if (tokenFrom == undefined || tokenTo == undefined) throw new Error("From or To tokens are not defined"); let initialAmount = fromAmount; let { toAmount, feePct } = computeOutputAmount(tokenFrom, tokenTo, fromAmount, Object.assign({}, fund), oraclePrices, Object.assign({}, curveChainData), { list: tokenListData }); if (toAmount == undefined || feePct == undefined) return undefined; let oracleAccounts = []; for (let i = 0; i < fund.numOfTokens.toNumber(); i++) oracleAccounts.push({ pubkey: new web3_js_1.PublicKey(tokenListData[fund.currentCompToken[i].toNumber()].oracleAccount), isSigner: false, isWritable: false, }); return { fromAmount: initialAmount / (10 ** tokenListData[tokenFrom].decimals), toAmount: toAmount / (10 ** tokenListData[tokenTo].decimals), fromTokenId: tokenFrom, toTokenId: tokenTo, feePct: feePct, priceImpactPct: feePct, swapAccounts: { program: types_1.FUNDS_PROGRAM_ID, fundState: fundAddress, authority: types_1.FUNDS_PROGRAM_PDA, source: new web3_js_1.PublicKey(tokenListData[tokenFrom].pdaTokenAccount), destination: new web3_js_1.PublicKey(tokenListData[tokenTo].pdaTokenAccount), fees: { smfWallet: types_1.SWAP_FEE_ACCOUNT, hostWallet: fund.hostPubkey, managerWallet: fund.manager, feeTokenMint: new web3_js_1.PublicKey(tokenListData[tokenTo].tokenMint) }, tokenList: types_1.TOKEN_LIST_ADDRESS, curveData: types_1.CURVE_DATA_ADDRESS, remainingAccounts: oracleAccounts, } }; } exports.checkForLiquidity = checkForLiquidity; function loadRouteData(tokenListData, curveChainData, funds, oraclePrices, tokenFrom, tokenTo, fromAmount) { if (tokenFrom == undefined || tokenTo == undefined) throw new Error("From or To tokens are not defined"); let fromTokenAmount = fromAmount * 10 ** tokenListData[tokenFrom].decimals; let bestRouteData = { fromAmount: 0, toAmount: 0, fromTokenId: 0, toTokenId: 0, feePct: 0, priceImpactPct: 0, swapAccounts: { program: types_1.FUNDS_PROGRAM_ID, fundState: web3_js_1.PublicKey.default, authority: types_1.FUNDS_PROGRAM_PDA, source: web3_js_1.PublicKey.default, destination: web3_js_1.PublicKey.default, fees: { smfWallet: types_1.SWAP_FEE_ACCOUNT, hostWallet: web3_js_1.PublicKey.default, managerWallet: web3_js_1.PublicKey.default, feeTokenMint: web3_js_1.PublicKey.default }, tokenList: types_1.TOKEN_LIST_ADDRESS, curveData: types_1.CURVE_DATA_ADDRESS, remainingAccounts: [], } }; for (let i = 0; i < funds.length; i++) { let routeData = checkForLiquidity(tokenListData, curveChainData, funds[i].pubkey, funds[i].fund, oraclePrices, tokenFrom, tokenTo, fromTokenAmount); if (!routeData) continue; if (routeData.toAmount > bestRouteData.toAmount) bestRouteData = routeData; } return bestRouteData; } exports.loadRouteData = loadRouteData;