diff --git a/README.md b/README.md index 4a29961..ccc9904 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ const env = "testing"; const startApp = async (provider: BaseProvider) => { const web3provider = new providers.Web3Provider(provider); await web3Provider.ready; - const signer = web3Provider.getSigner(account); // ready to go + const signer = web3Provider.getSigner(); // ready to go const orionUnit = initOrionUnit(chain, env); // ready to go }; diff --git a/package.json b/package.json index cc9c83b..f7339f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.2.4", + "version": "0.2.5", "description": "Orion Protocol SDK", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/BalanceGuard.ts b/src/BalanceGuard.ts index d6b89b0..2496040 100644 --- a/src/BalanceGuard.ts +++ b/src/BalanceGuard.ts @@ -6,6 +6,7 @@ import { APPROVE_ERC20_GAS_LIMIT, NATIVE_CURRENCY_PRECISION } from './constants' import { AggregatedBalanceRequirement, ApproveFix, Asset, BalanceIssue, BalanceRequirement, Source, } from './types'; +import { denormalizeNumber } from './utils'; import arrayEquals from './utils/arrayEquals'; export default class BalanceGuard { @@ -13,7 +14,7 @@ export default class BalanceGuard { Record< string, Record< - 'exchange' | 'wallet' | 'allowance', + 'exchange' | 'wallet', BigNumber> > >; @@ -27,7 +28,7 @@ export default class BalanceGuard { private readonly signer: ethers.Signer; constructor( - balances: Partial>>, + balances: Partial>>, nativeCryptocurrency: Asset, provider: ethers.providers.Provider, signer: ethers.Signer, @@ -220,11 +221,24 @@ export default class BalanceGuard { remainingBalance.exchange = remainingBalance.exchange.minus(itemsAmountSum); if (remainingBalance.exchange.lt(0)) { const lackAmount = remainingBalance.exchange.abs(); // e.g. -435.234234 to 434.234234 + + let denormalizedAllowance: BigNumber; + if (asset.address === ethers.constants.AddressZero) { + denormalizedAllowance = remainingBalance.wallet; + } else { + if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`); + const tokenContract = contracts.ERC20__factory.connect(asset.address, this.provider); + const tokenDecimals = await tokenContract.decimals(); + const walletAddress = await this.signer.getAddress(); + const tokenAllowance = await tokenContract.allowance(walletAddress, spenderAddress); + denormalizedAllowance = denormalizeNumber(tokenAllowance, tokenDecimals); + } + // Try to take lack amount from wallet const approvedWalletBalance = BigNumber .min( remainingBalance.wallet, - remainingBalance.allowance, + denormalizedAllowance, // For native cryptocurrency allowance is always just current balance ); if (lackAmount.lte(approvedWalletBalance)) { // We can take lack amount from wallet @@ -302,10 +316,23 @@ export default class BalanceGuard { if (!remainingBalance) throw new Error(`No ${asset.name} balance`); const itemsAmountSum = Object.values(items) .reduce((p, c) => (c ? p.plus(c) : p), new BigNumber(0)); + + let denormalizedAllowance: BigNumber; + if (asset.address === ethers.constants.AddressZero) { + denormalizedAllowance = remainingBalance.wallet; + } else { + if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`); + const tokenContract = contracts.ERC20__factory.connect(asset.address, this.provider); + const tokenDecimals = await tokenContract.decimals(); + const walletAddress = await this.signer.getAddress(); + const tokenAllowance = await tokenContract.allowance(walletAddress, spenderAddress); + denormalizedAllowance = denormalizeNumber(tokenAllowance, tokenDecimals); + } + const approvedWalletBalance = BigNumber .min( remainingBalance.wallet, - remainingBalance.allowance, + denormalizedAllowance, ); if (itemsAmountSum.lte(approvedWalletBalance)) { // Approved wallet balance is enough remainingBalance.wallet = remainingBalance.wallet.minus(itemsAmountSum); diff --git a/src/OrionUnit/Exchange/deposit.ts b/src/OrionUnit/Exchange/deposit.ts index fee1a2e..dd6e86e 100644 --- a/src/OrionUnit/Exchange/deposit.ts +++ b/src/OrionUnit/Exchange/deposit.ts @@ -9,6 +9,7 @@ import { DEPOSIT_ERC20_GAS_LIMIT, DEPOSIT_ETH_GAS_LIMIT, INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, } from '../../constants'; import { normalizeNumber } from '../../utils'; +import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; export type DepositParams = { asset: string, @@ -38,18 +39,8 @@ export default async function deposit({ exchangeContractAddress, assetToAddress, } = await orionBlockchain.getInfo(); - const addressToAsset = Object - .entries(assetToAddress) - .reduce>>((prev, [assetName, address]) => { - if (!address) return prev; - return { - ...prev, - [address]: assetName, - }; - }, {}); - const nativeCryptocurrency = addressToAsset[ethers.constants.AddressZero]; - if (!nativeCryptocurrency) throw new Error('Native cryptocurrency asset is not found'); + const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider); const gasPriceWei = await orionBlockchain.getGasPriceWei(); diff --git a/src/OrionUnit/Exchange/swapMarket.ts b/src/OrionUnit/Exchange/swapMarket.ts index dbfd532..4fe36e9 100644 --- a/src/OrionUnit/Exchange/swapMarket.ts +++ b/src/OrionUnit/Exchange/swapMarket.ts @@ -7,6 +7,7 @@ import getAvailableSources from '../../utils/getAvailableFundsSources'; import OrionUnit from '..'; import { contracts, crypt, utils } from '../..'; import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants'; +import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; export type SwapMarketParams = { type: 'exactSpend' | 'exactReceive', @@ -74,18 +75,7 @@ export default async function swapMarket({ matcherAddress, assetToAddress, } = await orionBlockchain.getInfo(); - const addressToAsset = Object - .entries(assetToAddress) - .reduce>>((prev, [asset, address]) => { - if (!address) return prev; - return { - ...prev, - [address]: asset, - }; - }, {}); - - const nativeCryptocurrency = addressToAsset[ethers.constants.AddressZero]; - if (!nativeCryptocurrency) throw new Error('Native cryptocurrency asset is not found'); + const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider); const feeAssets = await orionBlockchain.getTokensFee(); diff --git a/src/OrionUnit/Exchange/withdraw.ts b/src/OrionUnit/Exchange/withdraw.ts index 653a764..64a968d 100644 --- a/src/OrionUnit/Exchange/withdraw.ts +++ b/src/OrionUnit/Exchange/withdraw.ts @@ -9,6 +9,7 @@ import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, WITHDRAW_GAS_LIMIT, } from '../../constants'; import { normalizeNumber } from '../../utils'; +import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; export type WithdrawParams = { asset: string, @@ -39,19 +40,7 @@ export default async function withdraw({ assetToAddress, } = await orionBlockchain.getInfo(); - const addressToAsset = Object - .entries(assetToAddress) - .reduce>>((prev, [assetName, address]) => { - if (!address) return prev; - return { - ...prev, - [address]: assetName, - }; - }, {}); - - const nativeCryptocurrency = addressToAsset[ethers.constants.AddressZero]; - if (!nativeCryptocurrency) throw new Error('Native cryptocurrency asset is not found'); - + const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider); const gasPriceWei = await orionBlockchain.getGasPriceWei(); diff --git a/src/OrionUnit/FarmingManager/index.ts b/src/OrionUnit/FarmingManager/index.ts new file mode 100644 index 0000000..b105548 --- /dev/null +++ b/src/OrionUnit/FarmingManager/index.ts @@ -0,0 +1,405 @@ +import BigNumber from 'bignumber.js'; +import { ethers } from 'ethers'; +import OrionUnit from '..'; +import { contracts } from '../..'; +import BalanceGuard from '../../BalanceGuard'; +import { ADD_LIQUIDITY_GAS_LIMIT, INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION } from '../../constants'; +import { denormalizeNumber, normalizeNumber } from '../../utils'; +import getBalances from '../../utils/getBalances'; +import getNativeCryptocurrency from '../../utils/getNativeCryptocurrency'; + +const ADD_LIQUIDITY_SLIPPAGE = 0.05; + +export type AddLiquidityParams = { + poolName: string, + amountAsset: string, + amount: BigNumber.Value, + signer: ethers.Signer +} + +export type RemoveAllLiquidityParams = { + poolName: string, + signer: ethers.Signer +} + +export default class FarmingManager { + private readonly orionUnit: OrionUnit; + + constructor(orionUnit: OrionUnit) { + this.orionUnit = orionUnit; + } + + public async addLiquidity({ + poolName, + amountAsset, + amount, + signer, + }: AddLiquidityParams) { + const amountBN = new BigNumber(amount); + if (amountBN.isNaN()) throw new Error('Invalid amount'); + if (amountBN.lte(0)) throw new Error('Amount must be greater than 0'); + if (!poolName.includes('-')) throw new Error('Pool name must be in the format of "assetA-AssetB"'); + const [assetA, assetB] = poolName.split('-'); + if (amountAsset !== assetA && amountAsset !== assetB) throw new Error('Amount asset must be either assetA or assetB'); + + const { + exchangeContractAddress, + assetToAddress, + assetToDecimals, + } = await this.orionUnit.orionBlockchain.getInfo(); + + const walletAddress = await signer.getAddress(); + + const exchangeContract = contracts.Exchange__factory + .connect(exchangeContractAddress, this.orionUnit.provider); + + const assetAAddress = assetToAddress[assetA]; + if (!assetAAddress) throw new Error(`Asset '${assetA}' not found`); + const assetBAddress = assetToAddress[assetB]; + if (!assetBAddress) throw new Error(`Asset '${assetB}' not found`); + + const assetADecimals = assetToDecimals[assetA]; + if (!assetADecimals) throw new Error(`Decimals for asset '${assetA}' not found`); + const assetBDecimals = assetToDecimals[assetB]; + if (!assetBDecimals) throw new Error(`Decimals for asset '${assetB}' not found`); + + const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); + const balances = await getBalances( + { + [assetA]: assetAAddress, + [assetB]: assetBAddress, + [nativeCryptocurrency]: ethers.constants.AddressZero, + }, + this.orionUnit.orionAggregator, + walletAddress, + exchangeContract, + this.orionUnit.provider, + ); + const balanceGuard = new BalanceGuard( + balances, + { + address: ethers.constants.AddressZero, + name: nativeCryptocurrency, + }, + this.orionUnit.provider, + signer, + ); + + const poolsConfig = await this.orionUnit.orionBlockchain.getPoolsConfig(); + const pool = poolsConfig.pools[poolName]; + if (!pool) throw new Error(`Pool ${poolName} not found`); + + const pairContract = contracts.IUniswapV2Pair__factory + .connect(pool.lpTokenAddress, this.orionUnit.provider); + const routerContract = contracts.IUniswapV2Router__factory + .connect(poolsConfig.routerAddress, this.orionUnit.provider); + + let pairTokensIsInversed = false; + const token0 = await pairContract.token0(); + const wrappedNativeAddress = await routerContract.WETH(); + + // const token1 = await pairContract.token1(); + if (token0.toLowerCase() !== wrappedNativeAddress.toLowerCase()) pairTokensIsInversed = true; + + const { _reserve0, _reserve1 } = await pairContract.getReserves(); + + const assetAReserve = pairTokensIsInversed ? _reserve1 : _reserve0; + const assetBReserve = pairTokensIsInversed ? _reserve0 : _reserve1; + + const denormalizedAssetAReserve = denormalizeNumber(assetAReserve, assetADecimals); + const denormalizedAssetBReserve = denormalizeNumber(assetBReserve, assetBDecimals); + + const price = denormalizedAssetBReserve.div(denormalizedAssetAReserve); + + const assetAIsNativeCurrency = assetAAddress === ethers.constants.AddressZero; + const assetBIsNativeCurrency = assetBAddress === ethers.constants.AddressZero; + + const assetAAmount = assetA === amountAsset ? amountBN : amountBN.div(price); + const assetBAmount = assetA === amountAsset ? amountBN.multipliedBy(price) : amountBN; + + const assetAAmountWithSlippage = assetAAmount.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE); + const assetBAmountWithSlippage = assetBAmount.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE); + + balanceGuard.registerRequirement({ + reason: `${assetA} liquidity`, + amount: assetAAmount.toString(), + asset: { + name: assetA, + address: assetAAddress, + }, + spenderAddress: exchangeContractAddress, + sources: ['exchange', 'wallet'], + }); + + balanceGuard.registerRequirement({ + reason: `${assetB} liquidity`, + amount: assetBAmount.toString(), + asset: { + name: assetB, + address: assetBAddress, + }, + spenderAddress: exchangeContractAddress, + sources: ['exchange', 'wallet'], + }); + + const unsignedTx = await exchangeContract?.populateTransaction.withdrawToPool( + assetBIsNativeCurrency ? assetBAddress : assetAAddress, + assetBIsNativeCurrency ? assetAAddress : assetBAddress, + assetBIsNativeCurrency + ? normalizeNumber(assetBAmount, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR) + : normalizeNumber(assetAAmount, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR), + assetBIsNativeCurrency + ? normalizeNumber(assetAAmount, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR) + : normalizeNumber(assetBAmount, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR), + assetBIsNativeCurrency + ? normalizeNumber(assetBAmountWithSlippage, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR) + : normalizeNumber(assetAAmountWithSlippage, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR), + assetBIsNativeCurrency + ? normalizeNumber(assetAAmountWithSlippage, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR) + : normalizeNumber(assetBAmountWithSlippage, INTERNAL_ORION_PRECISION, BigNumber.ROUND_FLOOR), + ); + + const gasPrice = await this.orionUnit.provider.getGasPrice(); + + const transactionCost = ethers.BigNumber.from(ADD_LIQUIDITY_GAS_LIMIT).mul(gasPrice); + const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION); + + balanceGuard.registerRequirement({ + reason: 'Network fee', + asset: { + name: nativeCryptocurrency, + address: ethers.constants.AddressZero, + }, + amount: denormalizedTransactionCost.toString(), + sources: ['wallet'], + }); + + const nonce = await this.orionUnit.provider.getTransactionCount(walletAddress, 'pending'); + + const network = await this.orionUnit.provider.getNetwork(); + + if (assetAIsNativeCurrency || assetBIsNativeCurrency) { + const contractBalance = balances[nativeCryptocurrency]?.exchange; + if (!contractBalance) throw new Error(`No balance for '${nativeCryptocurrency}'`); + const nativeAssetAmount = assetBIsNativeCurrency ? assetBAmount : assetAAmount; + + if (nativeAssetAmount.gt(contractBalance)) { + unsignedTx.value = normalizeNumber( + nativeAssetAmount.minus(contractBalance), + NATIVE_CURRENCY_PRECISION, + BigNumber.ROUND_CEIL, + ); + } + } + + unsignedTx.chainId = network.chainId; + unsignedTx.gasPrice = gasPrice; + unsignedTx.nonce = nonce; + unsignedTx.from = walletAddress; + const gasLimit = await this.orionUnit.provider.estimateGas(unsignedTx); + unsignedTx.gasLimit = gasLimit; + + await balanceGuard.check(true); + + const signedTx = await signer.signTransaction(unsignedTx); + const txResponse = await this.orionUnit.provider.sendTransaction(signedTx); + console.log(`Add liquidity tx sent: ${txResponse.hash}. Waiting for confirmation...`); + const txReceipt = await txResponse.wait(); + if (txReceipt.status === 1) { + console.log(`Add liquidity tx confirmed: ${txReceipt.transactionHash}`); + } else { + console.log(`Add liquidity tx failed: ${txReceipt.transactionHash}`); + } + } + + public async removeAllLiquidity({ + poolName, + signer, + }: RemoveAllLiquidityParams) { + if (!poolName.includes('-')) throw new Error('Pool name must be in the format of "assetA-AssetB"'); + const [assetA, assetB] = poolName.split('-'); + + const { + assetToAddress, + assetToDecimals, + exchangeContractAddress, + } = await this.orionUnit.orionBlockchain.getInfo(); + + const assetAAddress = assetToAddress[assetA]; + if (!assetAAddress) throw new Error(`Asset '${assetA}' not found`); + const assetBAddress = assetToAddress[assetB]; + if (!assetBAddress) throw new Error(`Asset '${assetB}' not found`); + + const assetADecimals = assetToDecimals[assetA]; + if (!assetADecimals) throw new Error(`Decimals for asset '${assetA}' not found`); + const assetBDecimals = assetToDecimals[assetB]; + if (!assetBDecimals) throw new Error(`Decimals for asset '${assetB}' not found`); + + const poolsConfig = await this.orionUnit.orionBlockchain.getPoolsConfig(); + const pool = poolsConfig.pools[poolName]; + if (!pool) throw new Error(`Pool ${poolName} not found`); + + const walletAddress = await signer.getAddress(); + + const exchangeContract = contracts.Exchange__factory + .connect(exchangeContractAddress, this.orionUnit.provider); + const nativeCryptocurrency = getNativeCryptocurrency(assetToAddress); + const balances = await getBalances( + { + [assetA]: assetAAddress, + [assetB]: assetBAddress, + [`${poolName} LP Token`]: pool.lpTokenAddress, + [nativeCryptocurrency]: ethers.constants.AddressZero, + }, + this.orionUnit.orionAggregator, + walletAddress, + exchangeContract, + this.orionUnit.provider, + ); + + const balanceGuard = new BalanceGuard( + balances, + { + address: ethers.constants.AddressZero, + name: nativeCryptocurrency, + }, + this.orionUnit.provider, + signer, + ); + + const pairContract = contracts.IUniswapV2Pair__factory + .connect(pool.lpTokenAddress, this.orionUnit.provider); + + const { _reserve0, _reserve1 } = await pairContract.getReserves(); + + const routerContract = contracts.IUniswapV2Router__factory + .connect(poolsConfig.routerAddress, this.orionUnit.provider); + + let pairTokensIsInversed = false; + + const lpTokenUserBalance = await pairContract.balanceOf(walletAddress); + const lpTokenDecimals = await pairContract.decimals(); + + const token0 = await pairContract.token0(); + const totalSupply = await pairContract.totalSupply(); + const wrappedNativeAddress = await routerContract.WETH(); + if (token0.toLowerCase() !== wrappedNativeAddress.toLowerCase()) pairTokensIsInversed = true; + + const denormalizedLpTokenUserBalance = denormalizeNumber(lpTokenUserBalance, lpTokenDecimals); + const denormalizedLpTokenSupply = denormalizeNumber(totalSupply, lpTokenDecimals); + + const userShare = denormalizedLpTokenUserBalance.div(denormalizedLpTokenSupply); + + const assetAReserve = pairTokensIsInversed ? _reserve1 : _reserve0; + const assetBReserve = pairTokensIsInversed ? _reserve0 : _reserve1; + + const denormalizedAssetAReserve = denormalizeNumber(assetAReserve, assetADecimals); + const denormalizedAssetBReserve = denormalizeNumber(assetBReserve, assetBDecimals); + + const denormalizedUserPooledAssetA = denormalizedAssetAReserve.multipliedBy(userShare); + const denormalizedUserPooledAssetB = denormalizedAssetBReserve.multipliedBy(userShare); + + const denormalizedUserPooledAssetAWithSlippage = denormalizedUserPooledAssetA.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE); + const denormalizedUserPooledAssetBWithSlippage = denormalizedUserPooledAssetB.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE); + + const assetAIsNativeCurrency = assetAAddress === ethers.constants.AddressZero; + const assetBIsNativeCurrency = assetBAddress === ethers.constants.AddressZero; + + balanceGuard.registerRequirement({ + reason: `${poolName} liquidity`, + asset: { + name: `${poolName} LP Token`, + address: pool.lpTokenAddress, + }, + spenderAddress: poolsConfig.routerAddress, + amount: denormalizedLpTokenUserBalance.toString(), + sources: ['wallet'], + }); + + let unsignedTx: ethers.PopulatedTransaction; + if (assetAIsNativeCurrency || assetBIsNativeCurrency) { + unsignedTx = await routerContract.populateTransaction.removeLiquidityETH( + assetBIsNativeCurrency ? assetAAddress : assetBAddress, // token + lpTokenUserBalance, + assetBIsNativeCurrency + ? normalizeNumber( + denormalizedUserPooledAssetAWithSlippage, + assetADecimals, + BigNumber.ROUND_FLOOR, + ) + : normalizeNumber( + denormalizedUserPooledAssetBWithSlippage, + assetBDecimals, + BigNumber.ROUND_FLOOR, + ), // token min + assetBIsNativeCurrency + ? normalizeNumber( + denormalizedUserPooledAssetBWithSlippage, + assetBDecimals, + BigNumber.ROUND_FLOOR, + ) + : normalizeNumber( + denormalizedUserPooledAssetAWithSlippage, + assetADecimals, + BigNumber.ROUND_FLOOR, + ), // eth min + walletAddress, + Math.floor(Date.now() / 1000) + 60 * 20, + ); + } else { + unsignedTx = await routerContract.populateTransaction.removeLiquidity( + assetAAddress, + assetBAddress, + lpTokenUserBalance, + normalizeNumber( + denormalizedUserPooledAssetAWithSlippage, + assetADecimals, + BigNumber.ROUND_FLOOR, + ), + normalizeNumber( + denormalizedUserPooledAssetBWithSlippage, + assetBDecimals, + BigNumber.ROUND_FLOOR, + ), + walletAddress, + Math.floor(Date.now() / 1000) + 60 * 20, + ); + } + + const gasPrice = await this.orionUnit.provider.getGasPrice(); + + const transactionCost = ethers.BigNumber.from(ADD_LIQUIDITY_GAS_LIMIT).mul(gasPrice); + const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION); + + balanceGuard.registerRequirement({ + reason: 'Network fee', + asset: { + name: nativeCryptocurrency, + address: ethers.constants.AddressZero, + }, + amount: denormalizedTransactionCost.toString(), + sources: ['wallet'], + }); + + await balanceGuard.check(true); + const nonce = await this.orionUnit.provider.getTransactionCount(walletAddress, 'pending'); + const network = await this.orionUnit.provider.getNetwork(); + + unsignedTx.chainId = network.chainId; + unsignedTx.gasPrice = gasPrice; + unsignedTx.nonce = nonce; + unsignedTx.from = walletAddress; + const gasLimit = await this.orionUnit.provider.estimateGas(unsignedTx); + unsignedTx.gasLimit = gasLimit; + + const signedTx = await signer.signTransaction(unsignedTx); + const txResponse = await this.orionUnit.provider.sendTransaction(signedTx); + console.log(`Remove all liquidity tx sent: ${txResponse.hash}. Waiting for confirmation...`); + const txReceipt = await txResponse.wait(); + if (txReceipt.status === 1) { + console.log(`Remove all liquidity tx confirmed: ${txReceipt.transactionHash}`); + } else { + console.log(`Remove all liquidity tx failed: ${txReceipt.transactionHash}`); + } + } +} diff --git a/src/OrionUnit/index.ts b/src/OrionUnit/index.ts index c5802c0..026ac67 100644 --- a/src/OrionUnit/index.ts +++ b/src/OrionUnit/index.ts @@ -4,6 +4,7 @@ import { OrionBlockchain } from '../services/OrionBlockchain'; import { PriceFeed } from '../services/PriceFeed'; import { SupportedChainId } from '../types'; import Exchange from './Exchange'; +import FarmingManager from './FarmingManager'; export default class OrionUnit { public readonly env: string; @@ -20,6 +21,8 @@ export default class OrionUnit { public readonly exchange: Exchange; + public readonly farmingManager: FarmingManager; + public readonly apiUrl: string; constructor( @@ -37,5 +40,6 @@ export default class OrionUnit { this.orionAggregator = new OrionAggregator(apiUrl, chainId); this.priceFeed = new PriceFeed(apiUrl); this.exchange = new Exchange(this); + this.farmingManager = new FarmingManager(this); } } diff --git a/src/utils/getBalance.ts b/src/utils/getBalance.ts index 5ee080b..7d87f10 100644 --- a/src/utils/getBalance.ts +++ b/src/utils/getBalance.ts @@ -15,43 +15,25 @@ export default async function getBalance( const assetIsNativeCryptocurrency = assetAddress === ethers.constants.AddressZero; let assetWalletBalance: ethers.BigNumber | undefined; - let assetAllowance: ethers.BigNumber | undefined; let denormalizedAssetInWalletBalance: BigNumber | undefined; - let denormalizedAssetInAllowance: BigNumber | undefined; if (!assetIsNativeCryptocurrency) { const assetContract = contracts.ERC20__factory.connect(assetAddress, provider); const assetDecimals = await assetContract.decimals(); assetWalletBalance = await assetContract.balanceOf(walletAddress); - assetAllowance = await assetContract.allowance(walletAddress, exchangeContract.address); denormalizedAssetInWalletBalance = utils.denormalizeNumber(assetWalletBalance, assetDecimals); - denormalizedAssetInAllowance = utils.denormalizeNumber(assetAllowance, assetDecimals); } else { assetWalletBalance = await provider.getBalance(walletAddress); denormalizedAssetInWalletBalance = utils.denormalizeNumber(assetWalletBalance, NATIVE_CURRENCY_PRECISION); - denormalizedAssetInAllowance = denormalizedAssetInWalletBalance; // For native crypto no allowance is needed } const assetContractBalance = await exchangeContract.getBalance(assetAddress, walletAddress); const denormalizedAssetInContractBalance = utils.denormalizeNumber(assetContractBalance, INTERNAL_ORION_PRECISION); - // const denormalizedAssetWalletBalanceAvailable = BigNumber.min(denormalizedAssetInAllowance, denormalizedAssetInWalletBalance); const denormalizedAssetLockedBalance = await orionAggregator.getLockedBalance(walletAddress, asset); - // const denormalizedAssetAvailableBalance = denormalizedAssetInContractBalance - // .plus(denormalizedAssetWalletBalanceAvailable) - // .minus(denormalizedAssetLockedBalance[asset] ?? 0); - // const denormalizedAssetImaginaryBalance = denormalizedAssetInContractBalance - // .plus(denormalizedAssetInWalletBalance) - // .minus(denormalizedAssetLockedBalance[asset] ?? 0); - return { exchange: denormalizedAssetInContractBalance.minus(denormalizedAssetLockedBalance[asset] ?? 0), - // imaginary: denormalizedAssetImaginaryBalance, wallet: denormalizedAssetInWalletBalance, - allowance: denormalizedAssetInAllowance, - // approvedWalletBalance: denormalizedAssetWalletBalanceAvailable, - // available: denormalizedAssetAvailableBalance, - // locked: denormalizedAssetLockedBalance[asset], }; } diff --git a/src/utils/getBalances.ts b/src/utils/getBalances.ts index cdd77be..89a9b26 100644 --- a/src/utils/getBalances.ts +++ b/src/utils/getBalances.ts @@ -33,7 +33,6 @@ export default async ( return balances.reduce>>((prev, curr) => ({ ...prev, [curr.asset]: curr.amount, diff --git a/src/utils/getNativeCryptocurrency.ts b/src/utils/getNativeCryptocurrency.ts new file mode 100644 index 0000000..73f2424 --- /dev/null +++ b/src/utils/getNativeCryptocurrency.ts @@ -0,0 +1,19 @@ +import { ethers } from 'ethers'; + +const getNativeCryptocurrency = (assetToAddress: Partial>) => { + const addressToAsset = Object + .entries(assetToAddress) + .reduce>>((prev, [asset, address]) => { + if (!address) return prev; + return { + ...prev, + [address]: asset, + }; + }, {}); + + const nativeCryptocurrency = addressToAsset[ethers.constants.AddressZero]; + if (!nativeCryptocurrency) throw new Error('Native cryptocurrency asset is not found'); + return nativeCryptocurrency; +}; + +export default getNativeCryptocurrency;