diff --git a/package.json b/package.json index b5f9bc6..a98a651 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.20.26", + "version": "0.20.26-rc3", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index d5487f4..24ed183 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -1,73 +1,72 @@ -import type { LibValidator } from '@orionprotocol/contracts/lib/ethers-v6/Exchange.js'; -import { ethers, ZeroAddress } from 'ethers'; -import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from 'ethers'; -import cloneDeep from 'lodash.clonedeep'; -import { safeGet, SafeArray } from '../../utils/safeGetters.js'; -import { simpleFetch } from 'simple-typed-fetch'; -import type Unit from '../index.js'; -import { generateUni2Calls, generateUni2Call } from './callGenerators/uniswapV2.js'; +import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js"; +import { ethers, ZeroAddress } from "ethers"; +import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from "ethers"; +import cloneDeep from "lodash.clonedeep"; +import { safeGet, SafeArray } from "../../utils/safeGetters.js"; +import { simpleFetch } from "simple-typed-fetch"; +import type Unit from "../index.js"; +import { generateUni2Calls, generateUni2Call } from "./callGenerators/uniswapV2.js"; import { generateUni3Calls, generateOrion3Calls, generateUni3Call, generateOrion3Call, -} from './callGenerators/uniswapV3.js'; -import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js'; -import { generateTransferCall } from './callGenerators/erc20.js'; -import { generateCurveStableSwapCall } from './callGenerators/curve.js'; -import type { SingleSwap } from '../../types.js'; -import { addressLikeToString } from '../../utils/addressLikeToString.js'; -import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from './callGenerators/weth.js'; -import { getWalletBalance } from '../../utils/getBalance.js'; +} from "./callGenerators/uniswapV3.js"; +import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from "./callGenerators/utils.js"; +import { generateTransferCall } from "./callGenerators/erc20.js"; +import { generateCurveStableSwapCall } from "./callGenerators/curve.js"; +import type { SingleSwap } from "../../types.js"; +import { addressLikeToString } from "../../utils/addressLikeToString.js"; +import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js"; +import { getExchangeBalance, getWalletBalance } from "../../utils/getBalance.js"; -export type Factory = 'UniswapV2' | 'UniswapV3' | 'Curve' | 'OrionV2' | 'OrionV3'; +export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3"; export type GenerateSwapCalldataWithUnitParams = { - amount: BigNumberish - minReturnAmount: BigNumberish - receiverAddress: string - path: ArrayLike - unit: Unit + amount: BigNumberish; + minReturnAmount: BigNumberish; + initiatorAddress: string; + receiverAddress: string; + path: ArrayLike; + unit: Unit; }; export type GenerateSwapCalldataParams = { - amount: BigNumberish - minReturnAmount: BigNumberish - receiverAddress: string - useContractBalance: boolean - path: ArrayLike - wethAddress: AddressLike - curveRegistryAddress: AddressLike - swapExecutorContractAddress: AddressLike - provider: JsonRpcProvider + amount: BigNumberish; + minReturnAmount: BigNumberish; + initiatorAddress: string; + receiverAddress: string; + path: ArrayLike; + exchangeContractAddress: AddressLike; + wethAddress: AddressLike; + curveRegistryAddress: AddressLike; + swapExecutorContractAddress: AddressLike; + provider: JsonRpcProvider; }; export async function generateSwapCalldataWithUnit({ amount, minReturnAmount, + initiatorAddress, receiverAddress, path: arrayLikePath, unit, }: GenerateSwapCalldataWithUnitParams): Promise<{ - calldata: string - swapDescription: LibValidator.SwapDescriptionStruct + calldata: string; + swapDescription: LibValidator.SwapDescriptionStruct; + value: bigint; }> { if (arrayLikePath == undefined || arrayLikePath.length == 0) { - throw new Error('Empty path'); + throw new Error("Empty path"); } - const wethAddress = safeGet(unit.contracts, 'WETH'); - const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry'); - const { assetToAddress, swapExecutorContractAddress } = await simpleFetch( + const wethAddress = safeGet(unit.contracts, "WETH"); + const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry"); + const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch( unit.blockchainService.getInfo )(); const arrayLikePathCopy = cloneDeep(arrayLikePath); let path = SafeArray.from(arrayLikePathCopy); - const walletBalance = await getWalletBalance( - assetToAddress[path.first().assetIn] ?? path.first().assetIn.toLowerCase(), - receiverAddress, - unit.provider - ); path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => { swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase(); @@ -79,8 +78,9 @@ export async function generateSwapCalldataWithUnit({ amount, minReturnAmount, receiverAddress, - useContractBalance: walletBalance < await exchangeToNativeDecimals(path.first().assetIn, amount, unit.provider), + initiatorAddress, path, + exchangeContractAddress, wethAddress, curveRegistryAddress, swapExecutorContractAddress, @@ -91,21 +91,26 @@ export async function generateSwapCalldataWithUnit({ export async function generateSwapCalldata({ amount, minReturnAmount, + initiatorAddress, receiverAddress, - useContractBalance, path: arrayLikePath, + exchangeContractAddress, wethAddress: wethAddressLike, curveRegistryAddress: curveRegistryAddressLike, swapExecutorContractAddress: swapExecutorContractAddressLike, provider, -}: GenerateSwapCalldataParams) { +}: GenerateSwapCalldataParams): Promise<{ + calldata: string; + swapDescription: LibValidator.SwapDescriptionStruct; + value: bigint; +}> { const wethAddress = await addressLikeToString(wethAddressLike); const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike); const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike); let path = SafeArray.from(arrayLikePath); const { factory, assetIn: srcToken } = path.first(); - const dstToken = path.last().assetOut; + const { assetOut: dstToken } = path.last(); let swapDescription: LibValidator.SwapDescriptionStruct = { srcToken, @@ -156,11 +161,25 @@ export async function generateSwapCalldata({ )); const calldata = generateCalls(calls); - if (useContractBalance) { + const initiatorWalletBalance = await getWalletBalance(srcToken, initiatorAddress, provider); + const initiatorExchangeBalance = await getExchangeBalance( + srcToken, + initiatorAddress, + exchangeContractAddress, + provider, + true + ); + const useExchangeBalance = + initiatorExchangeBalance !== 0n && (srcToken === ZeroAddress || initiatorWalletBalance < amountNativeDecimals); + if (useExchangeBalance) { swapDescription.flags = 1n << 255n; } + let value = 0n; + if (srcToken === ZeroAddress && initiatorExchangeBalance < amountNativeDecimals) { + value = amountNativeDecimals - initiatorExchangeBalance; + } - return { swapDescription, calldata }; + return { swapDescription, calldata, value }; } async function processSingleFactorySwaps( @@ -174,27 +193,27 @@ async function processSingleFactorySwaps( ) { let calls: BytesLike[] = []; switch (factory) { - case 'OrionV2': { + case "OrionV2": { swapDescription.srcReceiver = path.first().pool; calls = await generateUni2Calls(path, swapExecutorContractAddress); break; } - case 'UniswapV2': { + case "UniswapV2": { swapDescription.srcReceiver = path.first().pool; calls = await generateUni2Calls(path, swapExecutorContractAddress); break; } - case 'UniswapV3': { + case "UniswapV3": { calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider); break; } - case 'OrionV3': { + case "OrionV3": { calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider); break; } - case 'Curve': { + case "Curve": { if (path.length > 1) { - throw new Error('Supporting only single stable swap on curve'); + throw new Error("Supporting only single stable swap on curve"); } calls = await generateCurveStableSwapCall( amount, @@ -224,33 +243,33 @@ async function processMultiFactorySwaps( let calls: BytesLike[] = []; for (const swap of path) { switch (swap.factory) { - case 'OrionV2': { + case "OrionV2": { let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0); transferCall = pathCallWithBalance(transferCall, swap.assetIn); const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress); calls.push(transferCall, uni2Call); break; } - case 'UniswapV2': { + case "UniswapV2": { let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0); transferCall = pathCallWithBalance(transferCall, swap.assetIn); const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress); calls.push(transferCall, uni2Call); break; } - case 'UniswapV3': { + case "UniswapV3": { let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider); uni3Call = pathCallWithBalance(uni3Call, swap.assetIn); calls.push(uni3Call); break; } - case 'OrionV3': { + case "OrionV3": { let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider); orion3Call = pathCallWithBalance(orion3Call, swap.assetIn); calls.push(orion3Call); break; } - case 'Curve': { + case "Curve": { let curveCalls = await generateCurveStableSwapCall( amount, swapExecutorContractAddress, diff --git a/src/utils/getBalance.ts b/src/utils/getBalance.ts index e7c84b2..c139574 100644 --- a/src/utils/getBalance.ts +++ b/src/utils/getBalance.ts @@ -1,9 +1,11 @@ -import { ERC20__factory, type Exchange } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; -import type { BigNumber } from 'bignumber.js'; -import { ethers } from 'ethers'; -import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from '../constants/index.js'; -import type { Aggregator } from '../services/Aggregator/index.js'; -import denormalizeNumber from './denormalizeNumber.js'; +import { ERC20__factory, Exchange__factory, type Exchange } from "@orionprotocol/contracts/lib/ethers-v6/index.js"; +import type { BigNumber } from "bignumber.js"; +import { ZeroAddress, ethers } from "ethers"; +import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from "../constants/index.js"; +import type { Aggregator } from "../services/Aggregator/index.js"; +import denormalizeNumber from "./denormalizeNumber.js"; +import type { AddressLike } from "ethers"; +import { addressLikeToString } from "./addressLikeToString.js"; export default async function getBalance( aggregator: Aggregator, @@ -11,7 +13,7 @@ export default async function getBalance( assetAddress: string, walletAddress: string, exchangeContract: Exchange, - provider: ethers.Provider, + provider: ethers.Provider ) { const assetIsNativeCryptocurrency = assetAddress === ethers.ZeroAddress; @@ -30,7 +32,10 @@ export default async function getBalance( denormalizedAssetInWalletBalance = denormalizeNumber(assetWalletBalance, BigInt(NATIVE_CURRENCY_PRECISION)); } const assetContractBalance = await exchangeContract.getBalance(assetAddress, walletAddress); - const denormalizedAssetInContractBalance = denormalizeNumber(assetContractBalance, BigInt(INTERNAL_PROTOCOL_PRECISION)); + const denormalizedAssetInContractBalance = denormalizeNumber( + assetContractBalance, + BigInt(INTERNAL_PROTOCOL_PRECISION) + ); const denormalizedAssetLockedBalanceResult = await aggregator.getLockedBalance(walletAddress, assetName); if (denormalizedAssetLockedBalanceResult.isErr()) { throw new Error(denormalizedAssetLockedBalanceResult.error.message); @@ -42,21 +47,130 @@ export default async function getBalance( }; } -export async function getWalletBalance( - assetAddress: string, - walletAddress: string, +async function getExchangeBalanceERC20( + tokenAddress: AddressLike, + walletAddress: AddressLike, + exchangeAddress: AddressLike, provider: ethers.Provider, + convertToNativeDecimals = true ) { - const assetIsNativeCryptocurrency = assetAddress === ethers.ZeroAddress; + walletAddress = await addressLikeToString(walletAddress); + exchangeAddress = await addressLikeToString(exchangeAddress); + tokenAddress = await addressLikeToString(tokenAddress); - let assetWalletBalance: bigint | undefined; + const exchange = Exchange__factory.connect(exchangeAddress, provider) + const exchangeBalance = await exchange.getBalance(tokenAddress, walletAddress); - if (!assetIsNativeCryptocurrency) { - const assetContract = ERC20__factory.connect(assetAddress, provider); - assetWalletBalance = await assetContract.balanceOf(walletAddress); - } else { - assetWalletBalance = await provider.getBalance(walletAddress); + if (convertToNativeDecimals) { + const tokenContract = ERC20__factory.connect(tokenAddress, provider); + const decimals = await tokenContract.decimals(); + const convertedExchangeBalance = (exchangeBalance * BigInt(10) ** decimals) / BigInt(10) ** 8n; + return convertedExchangeBalance; } - return assetWalletBalance + return exchangeBalance; +} + +async function getExchangeBalanceNative( + walletAddress: AddressLike, + exchangeAddress: AddressLike, + provider: ethers.Provider, + convertToNativeDecimals = true +) { + walletAddress = await addressLikeToString(walletAddress); + exchangeAddress = await addressLikeToString(exchangeAddress); + const exchange = Exchange__factory.connect(exchangeAddress, provider) + const exchangeBalance = await exchange.getBalance(ZeroAddress, walletAddress); + + if (convertToNativeDecimals) { + const convertedExchangeBalance = (exchangeBalance * BigInt(10) ** 18n) / BigInt(10) ** 8n; + return convertedExchangeBalance; + } + + return exchangeBalance; +} + +export async function getExchangeBalance( + tokenAddress: AddressLike, + walletAddress: AddressLike, + exchangeAddress: AddressLike, + provider: ethers.Provider, + convertToNativeDecimals = true +) { + walletAddress = await addressLikeToString(walletAddress); + tokenAddress = await addressLikeToString(tokenAddress); + + if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) { + return getExchangeBalanceNative(walletAddress, exchangeAddress, provider, convertToNativeDecimals); + } else { + return getExchangeBalanceERC20(tokenAddress, walletAddress, exchangeAddress, provider, convertToNativeDecimals); + } +} + +async function getWalletBalanceERC20( + tokenAddress: AddressLike, + walletAddress: AddressLike, + provider: ethers.Provider, + convertToExchangeDecimals = false +) { + walletAddress = await addressLikeToString(walletAddress); + tokenAddress = await addressLikeToString(tokenAddress); + + const tokenContract = ERC20__factory.connect(tokenAddress, provider); + let walletBalance = await tokenContract.balanceOf(walletAddress); + + if (convertToExchangeDecimals) { + const tokenContract = ERC20__factory.connect(tokenAddress, provider); + const decimals = await tokenContract.decimals(); + const convertedNativeBalance = (walletBalance * BigInt(10) ** 8n) / BigInt(10) ** decimals; + return convertedNativeBalance; + } + return walletBalance; +} + +async function getWalletBalanceNative( + walletAddress: AddressLike, + provider: ethers.Provider, + convertToExchangeDecimals = false +) { + walletAddress = await addressLikeToString(walletAddress); + const nativeBalance = await provider.getBalance(walletAddress); + + if (convertToExchangeDecimals) { + const convertedNativeBalance = (nativeBalance * BigInt(10) ** 8n) / BigInt(10) ** 18n; + return convertedNativeBalance; + } + + return nativeBalance; +} + +export async function getWalletBalance( + tokenAddress: AddressLike, + walletAddress: AddressLike, + provider: ethers.Provider, + convertToExchangeDecimals = false +) { + if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) { + return getWalletBalanceNative(walletAddress, provider, convertToExchangeDecimals); + } else { + return getWalletBalanceERC20(tokenAddress, walletAddress, provider, convertToExchangeDecimals); + } +} + +export async function getTotalBalance( + tokenAddress: AddressLike, + walletAddress: AddressLike, + exchangeAddress: AddressLike, + provider: ethers.Provider, + convertToNativeDecimals = true +) { + const walletBalance = await getWalletBalance(tokenAddress, walletAddress, provider, !convertToNativeDecimals); + const exchangeBalance = await getExchangeBalance( + tokenAddress, + walletAddress, + exchangeAddress, + provider, + convertToNativeDecimals + ); + return walletBalance + exchangeBalance; }