diff --git a/README.md b/README.md index 9e067c4..c387745 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,36 @@ orionUnit.exchange.deposit({ }); ``` +### Get fee info + +```ts +orionUnit.exchange + .getSwapMarketFeeInfo({ + type: "exactSpend", + assetIn: "ORN", + assetOut: "USDT", + feeAsset: "ORN", + amount: 23.89045345, + options: { + // Optional + poolOnly: false, + }, + }) + .then(console.log); + +// { +// feeAsset: 'BNB', +// feeAssetAddress: '0x0000000000000000000000000000000000000000', +// feeAmount: '0.006' +// } + +// { +// feeAsset: 'ORN', +// feeAssetAddress: '0xf223eca06261145b3287a0fefd8cfad371c7eb34', +// feeAmount: '2.5910754708713146879833915507960091181362655' +// } +``` + ### Make swap market ```ts @@ -216,7 +246,7 @@ Swap info eesponse example: "minAmountIn": 8.2, "minAmountOut": 12, "availableAmountIn": 25.2, - "availableAmountOut": null, // Null when type is 'exactSpend' + "availableAmountOut": null, "path": ["ORN", "USDT"], "isThroughPoolOptimal": true, "orderInfo": { diff --git a/package.json b/package.json index a6a192c..57f2a8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.11.4", + "version": "0.11.5", "description": "Orion Protocol SDK", "main": "./lib/esm/index.js", "module": "./lib/esm/index.js", diff --git a/src/OrionUnit/Exchange/getSwapMarketFeeInfo.ts b/src/OrionUnit/Exchange/getSwapMarketFeeInfo.ts new file mode 100644 index 0000000..39c75ee --- /dev/null +++ b/src/OrionUnit/Exchange/getSwapMarketFeeInfo.ts @@ -0,0 +1,124 @@ +import BigNumber from 'bignumber.js'; +import { ethers } from 'ethers'; +import { utils } from '../..'; +import { NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants'; +import { OrionAggregator } from '../../services/OrionAggregator'; +import { OrionBlockchain } from '../../services/OrionBlockchain'; + +import simpleFetch from '../../simpleFetch'; +import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; + +export type GetSwapMarketInfoParams = { + type: 'exactSpend' | 'exactReceive', + assetIn: string, + assetOut: string, + amount: BigNumber.Value, + feeAsset: string, + orionBlockchain: OrionBlockchain, + orionAggregator: OrionAggregator + options?: { + poolOnly?: boolean, + } +} + +export default async function getSwapMarketFeeInfo({ + type, + assetIn, + assetOut, + amount, + feeAsset, + orionBlockchain, + orionAggregator, + options, +}: GetSwapMarketInfoParams) { + if (amount === '') throw new Error('Amount can not be empty'); + if (assetIn === '') throw new Error('AssetIn can not be empty'); + if (assetOut === '') throw new Error('AssetOut can not be empty'); + if (feeAsset === '') throw new Error('Fee asset can not be empty'); + + const amountBN = new BigNumber(amount); + if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`); + if (amountBN.lte(0)) throw new Error(`Amount '${amount.toString()}' should be greater than 0`); + + const { + assetToAddress, + } = await simpleFetch(orionBlockchain.getInfo)(); + const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); + + const feeAssets = await simpleFetch(orionBlockchain.getTokensFee)(); + const pricesInOrn = await simpleFetch(orionBlockchain.getPrices)(); + const gasPriceWei = await simpleFetch(orionBlockchain.getGasPriceWei)(); + + const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString(); + + const assetInAddress = assetToAddress[assetIn]; + if (!assetInAddress) throw new Error(`Asset '${assetIn}' not found`); + const feeAssetAddress = assetToAddress[feeAsset]; + if (!feeAssetAddress) throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`); + + const swapInfo = await simpleFetch(orionAggregator.getSwapInfo)( + type, + assetIn, + assetOut, + amount.toString(), + options?.poolOnly ? ['ORION_POOL'] : undefined, + ); + + if (swapInfo.orderInfo !== null && options?.poolOnly === true && options.poolOnly !== swapInfo.isThroughPoolOptimal) { + throw new Error(`Unexpected Orion Aggregator response. Please, contact support. Report swap request id: ${swapInfo.id}`); + } + + if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) { + throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`); + } + + if (swapInfo.type === 'exactSpend' && amountBN.lt(swapInfo.minAmountIn)) { + throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`); + } + + if (swapInfo.orderInfo === null) throw new Error(swapInfo.executionInfo); + + let isThroughPoolOptimal: boolean; + if (options?.poolOnly) isThroughPoolOptimal = true; + else isThroughPoolOptimal = swapInfo.isThroughPoolOptimal; + + if (isThroughPoolOptimal) { + const transactionCost = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT).mul(gasPriceWei); + const denormalizedTransactionCost = utils.denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION); + + return { + feeAsset: nativeCryptocurrency, + feeAssetAddress: ethers.constants.AddressZero, + feeAmount: denormalizedTransactionCost.toString(), + }; + } + + const [baseAssetName] = swapInfo.orderInfo.assetPair.split('-'); + const baseAssetAddress = assetToAddress[baseAssetName]; + if (!baseAssetAddress) throw new Error(`No asset address for ${baseAssetName}`); + + // Fee calculation + const baseAssetPriceInOrn = pricesInOrn?.[baseAssetAddress]; + if (!baseAssetPriceInOrn) throw new Error(`Base asset price ${baseAssetName} in ORN not found`); + const baseCurrencyPriceInOrn = pricesInOrn[ethers.constants.AddressZero]; + if (!baseCurrencyPriceInOrn) throw new Error('Base currency price in ORN not found'); + const feeAssetPriceInOrn = pricesInOrn[feeAssetAddress]; + if (!feeAssetPriceInOrn) throw new Error(`Fee asset price ${feeAsset} in ORN not found`); + const feePercent = feeAssets?.[feeAsset]; + if (!feePercent) throw new Error(`Fee asset ${feeAsset} not available`); + + const { totalFeeInFeeAsset } = utils.calculateFeeInFeeAsset( + swapInfo.orderInfo.amount, + feeAssetPriceInOrn, + baseAssetPriceInOrn, + baseCurrencyPriceInOrn, + gasPriceGwei, + feePercent, + ); + + return { + feeAsset, + feeAssetAddress, + feeAmount: totalFeeInFeeAsset, + }; +} diff --git a/src/OrionUnit/Exchange/index.ts b/src/OrionUnit/Exchange/index.ts index 6a77b03..f293970 100644 --- a/src/OrionUnit/Exchange/index.ts +++ b/src/OrionUnit/Exchange/index.ts @@ -1,11 +1,13 @@ import OrionUnit from '..'; import deposit, { DepositParams } from './deposit'; +import getSwapMarketFeeInfo, { GetSwapMarketInfoParams } from './getSwapMarketFeeInfo'; import swapMarket, { SwapMarketParams } from './swapMarket'; import withdraw, { WithdrawParams } from './withdraw'; type PureSwapMarketParams= Omit type PureDepositParams = Omit -type PureWithdrawParams= Omit +type PureWithdrawParams = Omit +type PureGetSwapMarketInfoParams= Omit export default class Exchange { private readonly orionUnit: OrionUnit; @@ -21,6 +23,14 @@ export default class Exchange { }); } + public getSwapMarketFeeInfo(params: PureGetSwapMarketInfoParams) { + return getSwapMarketFeeInfo({ + orionAggregator: this.orionUnit.orionAggregator, + orionBlockchain: this.orionUnit.orionBlockchain, + ...params, + }); + } + public deposit(params: PureDepositParams) { return deposit({ ...params, diff --git a/src/OrionUnit/Exchange/swapMarket.ts b/src/OrionUnit/Exchange/swapMarket.ts index a87dbc2..7eca6a3 100644 --- a/src/OrionUnit/Exchange/swapMarket.ts +++ b/src/OrionUnit/Exchange/swapMarket.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable max-len */ import BigNumber from 'bignumber.js'; import { ethers } from 'ethers';