mirror of
https://github.com/orionprotocol/sdk.git
synced 2026-03-14 06:02:36 +03:00
Merge pull request #228 from orionprotocol/feature/payMatcherFee
added fee payment to matcher if dst.Token === feeToken
This commit is contained in:
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@orionprotocol/sdk",
|
||||
"version": "0.20.28",
|
||||
"version": "0.20.34-rc-0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@orionprotocol/sdk",
|
||||
"version": "0.20.28",
|
||||
"version": "0.20.34-rc-0",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"@ethersproject/abstract-signer": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@orionprotocol/contracts": "1.22.3",
|
||||
"@orionprotocol/contracts": "1.22.6",
|
||||
"@types/lodash.clonedeep": "^4.5.9",
|
||||
"bignumber.js": "^9.1.1",
|
||||
"bson-objectid": "^2.0.4",
|
||||
@@ -2421,9 +2421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@orionprotocol/contracts": {
|
||||
"version": "1.22.3",
|
||||
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.22.3.tgz",
|
||||
"integrity": "sha512-TVZftFbrHA+ldZSvMAGTntSiTT20UWn6P/+N392A9dv6RtiIXaQpMic5hOhVdIed74DU/KixVxwjVL1Hr0RLGQ=="
|
||||
"version": "1.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.22.6.tgz",
|
||||
"integrity": "sha512-pPQGPNBcf1LQlNJk/Iq4BtV6ccuDSEBJhBIEORNf93vgYyUJ3SZ5O2/5y/nWgywLghPh2WwoAhnUKFeUEUmgsA=="
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
@@ -13480,9 +13480,9 @@
|
||||
}
|
||||
},
|
||||
"@orionprotocol/contracts": {
|
||||
"version": "1.22.3",
|
||||
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.22.3.tgz",
|
||||
"integrity": "sha512-TVZftFbrHA+ldZSvMAGTntSiTT20UWn6P/+N392A9dv6RtiIXaQpMic5hOhVdIed74DU/KixVxwjVL1Hr0RLGQ=="
|
||||
"version": "1.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.22.6.tgz",
|
||||
"integrity": "sha512-pPQGPNBcf1LQlNJk/Iq4BtV6ccuDSEBJhBIEORNf93vgYyUJ3SZ5O2/5y/nWgywLghPh2WwoAhnUKFeUEUmgsA=="
|
||||
},
|
||||
"@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@orionprotocol/sdk",
|
||||
"version": "0.20.34",
|
||||
"version": "0.20.34-rc-6",
|
||||
"description": "Orion Protocol SDK",
|
||||
"main": "./lib/index.cjs",
|
||||
"module": "./lib/index.js",
|
||||
@@ -88,7 +88,7 @@
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"@ethersproject/abstract-signer": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@orionprotocol/contracts": "1.22.3",
|
||||
"@orionprotocol/contracts": "1.22.6",
|
||||
"@types/lodash.clonedeep": "^4.5.9",
|
||||
"bignumber.js": "^9.1.1",
|
||||
"bson-objectid": "^2.0.4",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import type { BigNumberish } from "ethers"
|
||||
import type { BigNumberish, AddressLike } from "ethers"
|
||||
import { type CallParams, addCallParams } from "./utils.js"
|
||||
import type { AddressLike } from "ethers"
|
||||
|
||||
export async function generateTransferCall(
|
||||
export function generateTransferCall(
|
||||
token: AddressLike,
|
||||
target: AddressLike,
|
||||
amount: BigNumberish,
|
||||
@@ -20,7 +19,7 @@ export async function generateTransferCall(
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
|
||||
export async function generateApproveCall(
|
||||
export function generateApproveCall(
|
||||
token: AddressLike,
|
||||
target: AddressLike,
|
||||
amount: BigNumberish,
|
||||
|
||||
21
src/Unit/Exchange/callGenerators/feePayment.ts
Normal file
21
src/Unit/Exchange/callGenerators/feePayment.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import type { BigNumberish, AddressLike } from "ethers"
|
||||
import { type CallParams, addCallParams } from "./utils.js"
|
||||
|
||||
|
||||
export function generateFeePaymentCall(
|
||||
matcher: AddressLike,
|
||||
token: AddressLike,
|
||||
amount: BigNumberish,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('payFeeToMatcher', [
|
||||
matcher,
|
||||
token,
|
||||
amount
|
||||
])
|
||||
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export async function generateUni2Calls(
|
||||
return calls
|
||||
}
|
||||
|
||||
export async function generateUni2Call(
|
||||
export function generateUni2Call(
|
||||
pool: string,
|
||||
assetIn: string,
|
||||
assetOut: string,
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { SingleSwap } from "../../types.js";
|
||||
import { addressLikeToString } from "../../utils/addressLikeToString.js";
|
||||
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js";
|
||||
import { getExchangeAllowance, getTotalBalance } from "../../utils/getBalance.js";
|
||||
import { generateFeePaymentCall } from "./callGenerators/feePayment.js";
|
||||
|
||||
export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3";
|
||||
|
||||
@@ -27,6 +28,9 @@ export type GenerateSwapCalldataWithUnitParams = {
|
||||
minReturnAmount: BigNumberish;
|
||||
initiatorAddress: string;
|
||||
receiverAddress: string;
|
||||
matcher: AddressLike,
|
||||
feeToken: AddressLike,
|
||||
fee: BigNumberish;
|
||||
path: ArrayLike<SingleSwap>;
|
||||
unit: Unit;
|
||||
};
|
||||
@@ -37,6 +41,9 @@ export type GenerateSwapCalldataParams = {
|
||||
initiatorAddress: string;
|
||||
receiverAddress: string;
|
||||
path: ArrayLike<SingleSwap>;
|
||||
matcher: AddressLike,
|
||||
feeToken: AddressLike,
|
||||
fee: BigNumberish;
|
||||
exchangeContractAddress: AddressLike;
|
||||
wethAddress: AddressLike;
|
||||
curveRegistryAddress: AddressLike;
|
||||
@@ -50,6 +57,9 @@ export async function generateSwapCalldataWithUnit({
|
||||
initiatorAddress,
|
||||
receiverAddress,
|
||||
path: arrayLikePath,
|
||||
matcher = ZeroAddress,
|
||||
feeToken = ZeroAddress,
|
||||
fee = 0,
|
||||
unit,
|
||||
}: GenerateSwapCalldataWithUnitParams): Promise<{
|
||||
calldata: string;
|
||||
@@ -69,8 +79,10 @@ export async function generateSwapCalldataWithUnit({
|
||||
let path = SafeArray.from(arrayLikePathCopy);
|
||||
|
||||
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => {
|
||||
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase();
|
||||
swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut.toLowerCase();
|
||||
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn
|
||||
swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut
|
||||
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
|
||||
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
|
||||
return swapInfo;
|
||||
});
|
||||
|
||||
@@ -80,6 +92,9 @@ export async function generateSwapCalldataWithUnit({
|
||||
receiverAddress,
|
||||
initiatorAddress,
|
||||
path,
|
||||
matcher,
|
||||
feeToken,
|
||||
fee,
|
||||
exchangeContractAddress,
|
||||
wethAddress,
|
||||
curveRegistryAddress,
|
||||
@@ -94,6 +109,9 @@ export async function generateSwapCalldata({
|
||||
initiatorAddress,
|
||||
receiverAddress,
|
||||
path: arrayLikePath,
|
||||
matcher: matcherAddressLike = ZeroAddress,
|
||||
feeToken: feeTokenAddressLike = ZeroAddress,
|
||||
fee = 0,
|
||||
exchangeContractAddress,
|
||||
wethAddress: wethAddressLike,
|
||||
curveRegistryAddress: curveRegistryAddressLike,
|
||||
@@ -107,7 +125,13 @@ export async function generateSwapCalldata({
|
||||
const wethAddress = await addressLikeToString(wethAddressLike);
|
||||
const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike);
|
||||
const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike);
|
||||
let path = SafeArray.from(arrayLikePath);
|
||||
const feeToken = await addressLikeToString(feeTokenAddressLike);
|
||||
const matcher = await addressLikeToString(matcherAddressLike);
|
||||
let path = SafeArray.from(arrayLikePath).map((swapInfo) => {
|
||||
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
|
||||
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
|
||||
return swapInfo;
|
||||
});
|
||||
|
||||
const { assetIn: srcToken } = path.first();
|
||||
const { assetOut: dstToken } = path.last();
|
||||
@@ -122,6 +146,7 @@ export async function generateSwapCalldata({
|
||||
flags: 0,
|
||||
};
|
||||
const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, provider);
|
||||
const feeNativeDecimals = await exchangeToNativeDecimals(feeToken, fee, provider)
|
||||
|
||||
path = SafeArray.from(arrayLikePath).map((singleSwap) => {
|
||||
if (singleSwap.assetIn == ethers.ZeroAddress) singleSwap.assetIn = wethAddress;
|
||||
@@ -134,6 +159,9 @@ export async function generateSwapCalldata({
|
||||
swapDescription,
|
||||
path,
|
||||
amountNativeDecimals,
|
||||
matcher,
|
||||
feeToken,
|
||||
feeNativeDecimals,
|
||||
wethAddress,
|
||||
swapExecutorContractAddress,
|
||||
curveRegistryAddress,
|
||||
@@ -159,6 +187,9 @@ async function processSwaps(
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
path: SafeArray<SingleSwap>,
|
||||
amount: BigNumberish,
|
||||
matcher: string,
|
||||
feeToken: string,
|
||||
fee: BigNumberish,
|
||||
wethAddress: string,
|
||||
swapExecutorContractAddress: string,
|
||||
curveRegistryAddress: string,
|
||||
@@ -187,13 +218,17 @@ async function processSwaps(
|
||||
provider
|
||||
));
|
||||
}
|
||||
({ swapDescription, calls } = await wrapOrUnwrapIfNeeded(
|
||||
|
||||
({swapDescription, calls} = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription));
|
||||
|
||||
({ swapDescription, calls } = wrapOrUnwrapIfNeeded(
|
||||
amount,
|
||||
swapDescription,
|
||||
calls,
|
||||
swapExecutorContractAddress,
|
||||
wethAddress
|
||||
));
|
||||
|
||||
return { swapDescription, calls };
|
||||
}
|
||||
|
||||
@@ -259,16 +294,16 @@ async function processMultiFactorySwaps(
|
||||
for (const swap of path) {
|
||||
switch (swap.factory) {
|
||||
case "OrionV2": {
|
||||
let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0);
|
||||
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
|
||||
const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
|
||||
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
|
||||
calls.push(transferCall, uni2Call);
|
||||
break;
|
||||
}
|
||||
case "UniswapV2": {
|
||||
let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0);
|
||||
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
|
||||
const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
|
||||
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
|
||||
calls.push(transferCall, uni2Call);
|
||||
break;
|
||||
}
|
||||
@@ -305,25 +340,44 @@ async function processMultiFactorySwaps(
|
||||
return { swapDescription, calls };
|
||||
}
|
||||
|
||||
async function wrapOrUnwrapIfNeeded(
|
||||
async function payFeeToMatcher(
|
||||
matcher: string,
|
||||
feeToken: string,
|
||||
feeAmount: BigNumberish,
|
||||
calls: BytesLike[],
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
) {
|
||||
console.log(matcher)
|
||||
console.log(feeAmount)
|
||||
console.log(feeToken)
|
||||
console.log(swapDescription.dstToken)
|
||||
if (BigInt(feeAmount) !== 0n && feeToken === swapDescription.dstToken) {
|
||||
const feePaymentCall = generateFeePaymentCall(matcher, feeToken, feeAmount)
|
||||
calls.push(feePaymentCall)
|
||||
}
|
||||
return {swapDescription, calls}
|
||||
}
|
||||
|
||||
function wrapOrUnwrapIfNeeded(
|
||||
amount: BigNumberish,
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
calls: BytesLike[],
|
||||
swapExecutorContractAddress: string,
|
||||
wethAddress: string
|
||||
) {
|
||||
if (swapDescription.srcToken === ZeroAddress) {
|
||||
const wrapCall = generateWrapAndTransferCall(swapDescription.srcReceiver, { value: amount });
|
||||
const {dstReceiver, srcReceiver, srcToken, dstToken} = swapDescription;
|
||||
if (srcToken === ZeroAddress) {
|
||||
const wrapCall = generateWrapAndTransferCall(srcReceiver, { value: amount });
|
||||
swapDescription.srcReceiver = swapExecutorContractAddress;
|
||||
calls = ([wrapCall] as BytesLike[]).concat(calls);
|
||||
}
|
||||
if (swapDescription.dstToken === ZeroAddress) {
|
||||
let unwrapCall = generateUnwrapAndTransferCall(swapDescription.dstReceiver, 0);
|
||||
if (dstToken === ZeroAddress) {
|
||||
let unwrapCall = generateUnwrapAndTransferCall(dstReceiver, 0);
|
||||
unwrapCall = pathCallWithBalance(unwrapCall, wethAddress);
|
||||
calls.push(unwrapCall);
|
||||
} else {
|
||||
let transferCall = await generateTransferCall(swapDescription.dstToken, swapDescription.dstReceiver, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, swapDescription.dstToken);
|
||||
let transferCall = generateTransferCall(dstToken, dstReceiver, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, dstToken);
|
||||
calls.push(transferCall);
|
||||
}
|
||||
return { swapDescription, calls };
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
import { Exchange__factory, IUniswapV2Pair__factory, IUniswapV2Router__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { simpleFetch } from 'simple-typed-fetch';
|
||||
import type Unit from '../index.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import { ADD_LIQUIDITY_GAS_LIMIT, INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from '../../constants/index.js';
|
||||
import { denormalizeNumber, normalizeNumber } from '../../utils/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js';
|
||||
|
||||
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 unit: Unit;
|
||||
|
||||
constructor(unit: Unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
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 (assetA === undefined) throw new Error('Asset A undefined');
|
||||
if (assetB === undefined) throw new Error('Asset B undefined');
|
||||
if (amountAsset !== assetA && amountAsset !== assetB) throw new Error('Amount asset must be either assetA or assetB');
|
||||
|
||||
const {
|
||||
exchangeContractAddress,
|
||||
assetToAddress,
|
||||
assetToDecimals,
|
||||
} = await simpleFetch(this.unit.blockchainService.getInfo)();
|
||||
|
||||
const walletAddress = await signer.getAddress();
|
||||
|
||||
const exchangeContract = Exchange__factory
|
||||
.connect(exchangeContractAddress, this.unit.provider);
|
||||
|
||||
const assetAAddress = assetToAddress[assetA];
|
||||
if (assetAAddress === undefined) throw new Error(`Asset '${assetA}' not found`);
|
||||
const assetBAddress = assetToAddress[assetB];
|
||||
if (assetBAddress === undefined) throw new Error(`Asset '${assetB}' not found`);
|
||||
|
||||
const assetADecimals = assetToDecimals[assetA];
|
||||
if (assetADecimals === undefined) throw new Error(`Decimals for asset '${assetA}' not found`);
|
||||
const assetBDecimals = assetToDecimals[assetB];
|
||||
if (assetBDecimals === undefined) throw new Error(`Decimals for asset '${assetB}' not found`);
|
||||
|
||||
const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress);
|
||||
const balances = await getBalances(
|
||||
{
|
||||
[assetA]: assetAAddress,
|
||||
[assetB]: assetBAddress,
|
||||
[nativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
this.unit.aggregator,
|
||||
walletAddress,
|
||||
exchangeContract,
|
||||
this.unit.provider,
|
||||
);
|
||||
const balanceGuard = new BalanceGuard(
|
||||
balances,
|
||||
{
|
||||
address: ethers.ZeroAddress,
|
||||
name: nativeCryptocurrency,
|
||||
},
|
||||
this.unit.provider,
|
||||
signer,
|
||||
);
|
||||
|
||||
const poolsConfig = await simpleFetch(this.unit.blockchainService.getPoolsConfig)();
|
||||
const pool = poolsConfig.pools[poolName];
|
||||
if (!pool) throw new Error(`Pool ${poolName} not found`);
|
||||
|
||||
const pairContract = IUniswapV2Pair__factory
|
||||
.connect(pool.lpTokenAddress, this.unit.provider);
|
||||
const routerContract = IUniswapV2Router__factory
|
||||
.connect(poolsConfig.routerAddress, this.unit.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, BigInt(assetADecimals));
|
||||
const denormalizedAssetBReserve = denormalizeNumber(assetBReserve, BigInt(assetBDecimals));
|
||||
|
||||
const price = denormalizedAssetBReserve.div(denormalizedAssetAReserve);
|
||||
|
||||
const assetAIsNativeCurrency = assetAAddress === ethers.ZeroAddress;
|
||||
const assetBIsNativeCurrency = assetBAddress === ethers.ZeroAddress;
|
||||
|
||||
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.withdrawToPool.populateTransaction(
|
||||
assetBIsNativeCurrency ? assetBAddress : assetAAddress,
|
||||
assetBIsNativeCurrency ? assetAAddress : assetBAddress,
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetBAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString()
|
||||
: normalizeNumber(assetAAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString(),
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetAAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString()
|
||||
: normalizeNumber(assetBAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString(),
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetBAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString()
|
||||
: normalizeNumber(assetAAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString(),
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetAAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString()
|
||||
: normalizeNumber(assetBAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString(),
|
||||
);
|
||||
|
||||
const { gasPrice, maxFeePerGas } = await this.unit.provider.getFeeData();
|
||||
|
||||
const transactionCost = BigInt(ADD_LIQUIDITY_GAS_LIMIT) * (gasPrice ?? 0n);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
const nonce = await this.unit.provider.getTransactionCount(walletAddress, 'pending');
|
||||
|
||||
const network = await this.unit.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (gasPrice !== null && maxFeePerGas !== null) {
|
||||
unsignedTx.gasPrice = gasPrice;
|
||||
unsignedTx.maxFeePerGas = maxFeePerGas;
|
||||
}
|
||||
unsignedTx.chainId = network.chainId;
|
||||
unsignedTx.nonce = nonce;
|
||||
unsignedTx.from = walletAddress;
|
||||
const gasLimit = await this.unit.provider.estimateGas(unsignedTx);
|
||||
unsignedTx.gasLimit = gasLimit;
|
||||
|
||||
await balanceGuard.check(true);
|
||||
|
||||
const signedTx = await signer.signTransaction(unsignedTx);
|
||||
const txResponse = await this.unit.provider.broadcastTransaction(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.hash}`);
|
||||
} else {
|
||||
console.log(`Add liquidity tx failed: ${txReceipt?.hash}`);
|
||||
}
|
||||
}
|
||||
|
||||
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('-');
|
||||
if (assetA === undefined) throw new Error('Asset A is not defined');
|
||||
if (assetB === undefined) throw new Error('Asset B is not defined');
|
||||
|
||||
const {
|
||||
assetToAddress,
|
||||
assetToDecimals,
|
||||
exchangeContractAddress,
|
||||
} = await simpleFetch(this.unit.blockchainService.getInfo)();
|
||||
|
||||
const assetAAddress = assetToAddress[assetA];
|
||||
if (assetAAddress === undefined) throw new Error(`Asset '${assetA}' not found`);
|
||||
const assetBAddress = assetToAddress[assetB];
|
||||
if (assetBAddress === undefined) throw new Error(`Asset '${assetB}' not found`);
|
||||
|
||||
const assetADecimals = assetToDecimals[assetA];
|
||||
if (assetADecimals === undefined) throw new Error(`Decimals for asset '${assetA}' not found`);
|
||||
const assetBDecimals = assetToDecimals[assetB];
|
||||
if (assetBDecimals === undefined) throw new Error(`Decimals for asset '${assetB}' not found`);
|
||||
|
||||
const poolsConfig = await simpleFetch(this.unit.blockchainService.getPoolsConfig)();
|
||||
const pool = poolsConfig.pools[poolName];
|
||||
if (!pool) throw new Error(`Pool ${poolName} not found`);
|
||||
|
||||
const walletAddress = await signer.getAddress();
|
||||
|
||||
const exchangeContract = Exchange__factory
|
||||
.connect(exchangeContractAddress, this.unit.provider);
|
||||
const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress);
|
||||
const balances = await getBalances(
|
||||
{
|
||||
[assetA]: assetAAddress,
|
||||
[assetB]: assetBAddress,
|
||||
[`${poolName} LP Token`]: pool.lpTokenAddress,
|
||||
[nativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
this.unit.aggregator,
|
||||
walletAddress,
|
||||
exchangeContract,
|
||||
this.unit.provider,
|
||||
);
|
||||
|
||||
const balanceGuard = new BalanceGuard(
|
||||
balances,
|
||||
{
|
||||
address: ethers.ZeroAddress,
|
||||
name: nativeCryptocurrency,
|
||||
},
|
||||
this.unit.provider,
|
||||
signer,
|
||||
);
|
||||
|
||||
const pairContract = IUniswapV2Pair__factory
|
||||
.connect(pool.lpTokenAddress, this.unit.provider);
|
||||
|
||||
const { _reserve0, _reserve1 } = await pairContract.getReserves();
|
||||
|
||||
const routerContract = IUniswapV2Router__factory
|
||||
.connect(poolsConfig.routerAddress, this.unit.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, BigInt(assetADecimals));
|
||||
const denormalizedAssetBReserve = denormalizeNumber(assetBReserve, BigInt(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.ZeroAddress;
|
||||
const assetBIsNativeCurrency = assetBAddress === ethers.ZeroAddress;
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: `${poolName} liquidity`,
|
||||
asset: {
|
||||
name: `${poolName} LP Token`,
|
||||
address: pool.lpTokenAddress,
|
||||
},
|
||||
spenderAddress: poolsConfig.routerAddress,
|
||||
amount: denormalizedLpTokenUserBalance.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
let unsignedTx: ethers.TransactionLike;
|
||||
if (assetAIsNativeCurrency || assetBIsNativeCurrency) {
|
||||
unsignedTx = await routerContract.removeLiquidityETH.populateTransaction(
|
||||
assetBIsNativeCurrency ? assetAAddress : assetBAddress, // token
|
||||
lpTokenUserBalance,
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(
|
||||
denormalizedUserPooledAssetAWithSlippage,
|
||||
assetADecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toString()
|
||||
: normalizeNumber(
|
||||
denormalizedUserPooledAssetBWithSlippage,
|
||||
assetBDecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toString(), // token min
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(
|
||||
denormalizedUserPooledAssetBWithSlippage,
|
||||
assetBDecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toString()
|
||||
: normalizeNumber(
|
||||
denormalizedUserPooledAssetAWithSlippage,
|
||||
assetADecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toString(), // eth min
|
||||
walletAddress,
|
||||
Math.floor(Date.now() / 1000) + 60 * 20,
|
||||
);
|
||||
} else {
|
||||
unsignedTx = await routerContract.removeLiquidity.populateTransaction(
|
||||
assetAAddress,
|
||||
assetBAddress,
|
||||
lpTokenUserBalance,
|
||||
normalizeNumber(
|
||||
denormalizedUserPooledAssetAWithSlippage,
|
||||
assetADecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toString(),
|
||||
normalizeNumber(
|
||||
denormalizedUserPooledAssetBWithSlippage,
|
||||
assetBDecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toString(),
|
||||
walletAddress,
|
||||
Math.floor(Date.now() / 1000) + 60 * 20,
|
||||
);
|
||||
}
|
||||
|
||||
const { gasPrice } = await this.unit.provider.getFeeData()
|
||||
|
||||
const transactionCost = BigInt(ADD_LIQUIDITY_GAS_LIMIT) * (gasPrice ?? 0n);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
await balanceGuard.check(true);
|
||||
const nonce = await this.unit.provider.getTransactionCount(walletAddress, 'pending');
|
||||
const network = await this.unit.provider.getNetwork();
|
||||
|
||||
unsignedTx.chainId = network.chainId;
|
||||
unsignedTx.gasPrice = gasPrice;
|
||||
unsignedTx.nonce = nonce;
|
||||
unsignedTx.from = walletAddress;
|
||||
const gasLimit = await this.unit.provider.estimateGas(unsignedTx);
|
||||
unsignedTx.gasLimit = gasLimit;
|
||||
|
||||
const signedTx = await signer.signTransaction(unsignedTx);
|
||||
const txResponse = await this.unit.provider.broadcastTransaction(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.hash}`);
|
||||
} else {
|
||||
console.log(`Remove all liquidity tx failed: ${txReceipt?.hash}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
VerboseUnitConfig,
|
||||
} from '../types.js';
|
||||
import Exchange from './Exchange/index.js';
|
||||
import FarmingManager from './FarmingManager/index.js';
|
||||
import { chains, envs } from '../config/index.js';
|
||||
import type { networkCodes } from '../constants/index.js';
|
||||
import { IndexerService } from '../services/Indexer/index.js';
|
||||
@@ -35,8 +34,6 @@ export default class Unit {
|
||||
|
||||
public readonly exchange: Exchange;
|
||||
|
||||
public readonly farmingManager: FarmingManager;
|
||||
|
||||
public readonly config: VerboseUnitConfig;
|
||||
|
||||
public readonly contracts: Record<string, string>;
|
||||
@@ -123,6 +120,5 @@ export default class Unit {
|
||||
this.config.basicAuth
|
||||
);
|
||||
this.exchange = new Exchange(this);
|
||||
this.farmingManager = new FarmingManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ export async function addressLikeToString(address: AddressLike): Promise<string>
|
||||
if (typeof address !== 'string') {
|
||||
address = await address.getAddress()
|
||||
}
|
||||
return address
|
||||
return address.toLowerCase()
|
||||
}
|
||||
Reference in New Issue
Block a user