@symmetry-hq/liquidity-sdk
Version:
Exchange functionality using symmetry funds liquidity
205 lines (204 loc) • 11.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
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;
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_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);
fundState.currentCompAmount[to_token_index] -= amount_without_fees - fund_fee;
fundState.currentCompAmount[from_token_index] += from_amount;
let from_token_worth_after_swap = amount_to_usd_value(fundState.currentCompAmount[from_token_index], from_token_settings.decimals, from_token_price.avg_price);
let to_token_worth_after_swap = amount_to_usd_value(fundState.currentCompAmount[to_token_index], 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;