mirror of
https://github.com/orionprotocol/sdk.git
synced 2026-03-14 06:02:36 +03:00
Add liquidity / remove liquidity / balancee guard improvements
This commit is contained in:
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<Record<string, Record<'exchange' | 'wallet' | 'allowance', BigNumber>>>,
|
||||
balances: Partial<Record<string, Record<'exchange' | 'wallet', BigNumber>>>,
|
||||
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<BigNumber>((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);
|
||||
|
||||
@@ -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<Partial<Record<string, string>>>((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();
|
||||
|
||||
@@ -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<Partial<Record<string, string>>>((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();
|
||||
|
||||
@@ -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<Partial<Record<string, string>>>((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();
|
||||
|
||||
|
||||
405
src/OrionUnit/FarmingManager/index.ts
Normal file
405
src/OrionUnit/FarmingManager/index.ts
Normal file
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ export default async (
|
||||
return balances.reduce<Partial<Record<string, {
|
||||
exchange: BigNumber,
|
||||
wallet: BigNumber,
|
||||
allowance: BigNumber,
|
||||
}>>>((prev, curr) => ({
|
||||
...prev,
|
||||
[curr.asset]: curr.amount,
|
||||
|
||||
19
src/utils/getNativeCryptocurrency.ts
Normal file
19
src/utils/getNativeCryptocurrency.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
const getNativeCryptocurrency = (assetToAddress: Partial<Record<string, string>>) => {
|
||||
const addressToAsset = Object
|
||||
.entries(assetToAddress)
|
||||
.reduce<Partial<Record<string, string>>>((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;
|
||||
Reference in New Issue
Block a user