diff --git a/package-lock.json b/package-lock.json index b7ab572..b166e41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@orionprotocol/sdk", - "version": "0.20.88-rc3", + "version": "0.20.88-rc8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.20.88-rc3", + "version": "0.20.88-rc8", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 8de0c64..6ebda75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.20.88-rc7", + "version": "0.20.88-rc8", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Unit/Exchange/callGenerators/uniswapV2.ts b/src/Unit/Exchange/callGenerators/uniswapV2.ts index 4526fca..86778bc 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV2.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV2.ts @@ -3,6 +3,15 @@ import { SafeArray } from "../../../utils/safeGetters.js" import { type BytesLike, type BigNumberish, concat, ethers, toBeHex } from "ethers" import { addCallParams } from "./utils.js" import type { SingleSwap } from "../../../types.js" +import { BigNumber } from 'bignumber.js'; + +const BILLION = 1000000000; +const TEN_THOUSANDS = 10000; + +function countScaledFee(fee: string) { + // The count is needed for the swapUniV2Scaled function, where the denominator is one billion + return new BigNumber(fee).multipliedBy(BILLION).div(TEN_THOUSANDS).toNumber(); +} export async function generateUni2Calls( path: SafeArray, @@ -19,17 +28,21 @@ export async function generateUni2Calls( currentSwap.pool, currentSwap.assetIn, currentSwap.assetOut, - nextSwap.pool + nextSwap.pool, + currentSwap.fee ) calls.push(call) } } + const lastSwap = path.last(); - const calldata = executorInterface.encodeFunctionData('swapUniV2', [ + const fee = lastSwap.fee ?? 3; + const scaledFee = countScaledFee(fee.toString()); + const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [ lastSwap.pool, lastSwap.assetIn, lastSwap.assetOut, - ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat(['0x03', recipient])]), + ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]), ]) calls.push(addCallParams(calldata)) @@ -44,11 +57,12 @@ export function generateUni2Call( fee: BigNumberish = 3, ) { const executorInterface = SwapExecutor__factory.createInterface() - const calldata = executorInterface.encodeFunctionData('swapUniV2', [ + const scaledFee = countScaledFee(fee.toString()); + const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [ pool, assetIn, assetOut, - ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(fee), recipient])]), + ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]), ]) return addCallParams(calldata) -} \ No newline at end of file +} diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index fd7fe08..d58a68a 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -1,49 +1,50 @@ -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 { getExchangeAllowance, getTotalBalance } from "../../utils/getBalance.js"; -import { generateFeePaymentCall } from "./callGenerators/feePayment.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 { getExchangeAllowance, getTotalBalance } from '../../utils/getBalance.js'; +import { generateFeePaymentCall } from './callGenerators/feePayment.js'; -export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3"; +export type Factory = 'UniswapV2' | 'UniswapV3' | 'Curve' | 'OrionV2' | 'OrionV3'; type BaseGenerateSwapCalldataParams = { - amount: BigNumberish; - minReturnAmount: BigNumberish; - initiatorAddress: string; - receiverAddress: string; - path: ArrayLike; - matcher?: AddressLike, - feeToken?: AddressLike, - fee?: BigNumberish; + amount: BigNumberish + minReturnAmount: BigNumberish + initiatorAddress: string + receiverAddress: string + path: ArrayLike + matcher?: AddressLike + feeToken?: AddressLike + fee?: BigNumberish } export type GenerateSwapCalldataWithUnitParams = BaseGenerateSwapCalldataParams & { - unit: Unit; + unit: Unit }; export type GenerateSwapCalldataParams = BaseGenerateSwapCalldataParams & { - exchangeContractAddress: AddressLike; - wethAddress: AddressLike; - curveRegistryAddress: AddressLike; - swapExecutorContractAddress: AddressLike; - provider: JsonRpcProvider; + exchangeContractAddress: AddressLike + wethAddress: AddressLike + curveRegistryAddress: AddressLike + swapExecutorContractAddress: AddressLike + provider: JsonRpcProvider + logger?: ((message: string) => void) | undefined }; export async function generateSwapCalldataWithUnit({ @@ -57,29 +58,27 @@ export async function generateSwapCalldataWithUnit({ fee = 0, unit, }: GenerateSwapCalldataWithUnitParams): Promise<{ - calldata: string; - swapDescription: LibValidator.SwapDescriptionStruct; - value: bigint; + 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, exchangeContractAddress } = await simpleFetch( + const wethAddress = safeGet(unit.contracts, 'WETH'); + const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry'); + const { swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch( unit.blockchainService.getInfo )(); const arrayLikePathCopy = cloneDeep(arrayLikePath); let path = SafeArray.from(arrayLikePathCopy); - path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => { - 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; - }); + path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => ({ + ...swapInfo, + assetIn: swapInfo.assetAddressIn.toLowerCase(), + assetOut: swapInfo.assetAddressOut.toLowerCase(), + })); return await generateSwapCalldata({ amount, @@ -95,6 +94,7 @@ export async function generateSwapCalldataWithUnit({ curveRegistryAddress, swapExecutorContractAddress, provider: unit.provider, + logger: unit.logger, }); } @@ -112,21 +112,30 @@ export async function generateSwapCalldata({ curveRegistryAddress: curveRegistryAddressLike, swapExecutorContractAddress: swapExecutorContractAddressLike, provider, + logger, }: GenerateSwapCalldataParams): Promise<{ - calldata: string; - swapDescription: LibValidator.SwapDescriptionStruct; - value: bigint; + calldata: string + swapDescription: LibValidator.SwapDescriptionStruct + value: bigint }> { const wethAddress = await addressLikeToString(wethAddressLike); + logger?.(`wethAddress: ${wethAddress}`); const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike); + logger?.(`curveRegistryAddress: ${curveRegistryAddress}`); const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike); + logger?.(`swapExecutorContractAddress, ${swapExecutorContractAddress}`); const feeToken = await addressLikeToString(feeTokenAddressLike); + logger?.(`feeToken, ${feeToken}`); const matcher = await addressLikeToString(matcherAddressLike); + logger?.(`matcher: ${matcher}`); + logger?.(`arrayLikePath: ${arrayLikePath}`); let path = SafeArray.from(arrayLikePath).map((swapInfo) => { + logger?.(`swapInfo: ${swapInfo}`); swapInfo.assetIn = swapInfo.assetIn.toLowerCase() swapInfo.assetOut = swapInfo.assetOut.toLowerCase() return swapInfo; }); + logger?.(`path: ${path}`); const { assetIn: srcToken } = path.first(); const { assetOut: dstToken } = path.last(); @@ -140,14 +149,18 @@ export async function generateSwapCalldata({ minReturnAmount, flags: 0, }; + logger?.(`swapDescription: ${swapDescription}`); const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, provider); + logger?.(`amountNativeDecimals: ${amountNativeDecimals}`); const feeNativeDecimals = await exchangeToNativeDecimals(feeToken, fee, provider) + logger?.(`feeNativeDecimals: ${feeNativeDecimals}`); path = SafeArray.from(arrayLikePath).map((singleSwap) => { if (singleSwap.assetIn == ethers.ZeroAddress) singleSwap.assetIn = wethAddress; if (singleSwap.assetOut == ethers.ZeroAddress) singleSwap.assetOut = wethAddress; return singleSwap; }); + logger?.(`path2: ${path}`); let calls: BytesLike[]; ({ swapDescription, calls } = await processSwaps( @@ -162,19 +175,26 @@ export async function generateSwapCalldata({ curveRegistryAddress, provider )); + logger?.(`swapDescription: ${swapDescription}`); + logger?.(`calls: ${calls}`); const calldata = generateCalls(calls); + logger?.(`calldata: ${calldata}`); const { useExchangeBalance, additionalTransferAmount } = await shouldUseExchangeBalance( srcToken, initiatorAddress, exchangeContractAddress, amountNativeDecimals, - provider + provider, + logger ); + logger?.(`useExchangeBalance: ${useExchangeBalance}`); + logger?.(`additionalTransferAmount: ${additionalTransferAmount}`); if (useExchangeBalance) { swapDescription.flags = 1n << 255n; } const value = srcToken == ZeroAddress ? additionalTransferAmount : 0n; + logger?.(`value: ${value}`); return { swapDescription, calldata, value }; } @@ -214,7 +234,7 @@ async function processSwaps( )); } - ({swapDescription, calls} = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription)); + ({ swapDescription, calls } = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription)); ({ swapDescription, calls } = wrapOrUnwrapIfNeeded( amount, @@ -238,27 +258,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, @@ -285,37 +305,37 @@ async function processMultiFactorySwaps( curveRegistryAddress: string, provider: JsonRpcProvider ) { - let calls: BytesLike[] = []; + const calls: BytesLike[] = []; for (const swap of path) { switch (swap.factory) { - case "OrionV2": { + case 'OrionV2': { let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0); transferCall = pathCallWithBalance(transferCall, swap.assetIn); - const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress); + const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress, swap.fee); calls.push(transferCall, uni2Call); break; } - case "UniswapV2": { + case 'UniswapV2': { let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0); transferCall = pathCallWithBalance(transferCall, swap.assetIn); - const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress); + const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress, swap.fee); 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": { - let curveCalls = await generateCurveStableSwapCall( + case 'Curve': { + const curveCalls = await generateCurveStableSwapCall( amount, swapExecutorContractAddress, swap, @@ -346,7 +366,7 @@ async function payFeeToMatcher( const feePaymentCall = generateFeePaymentCall(matcher, feeToken, feeAmount) calls.push(feePaymentCall) } - return {swapDescription, calls} + return { swapDescription, calls } } function wrapOrUnwrapIfNeeded( @@ -356,7 +376,7 @@ function wrapOrUnwrapIfNeeded( swapExecutorContractAddress: string, wethAddress: string ) { - const {dstReceiver, srcReceiver, srcToken, dstToken} = swapDescription; + const { dstReceiver, srcReceiver, srcToken, dstToken } = swapDescription; if (srcToken === ZeroAddress) { const wrapCall = generateWrapAndTransferCall(srcReceiver, { value: amount }); swapDescription.srcReceiver = swapExecutorContractAddress; @@ -379,7 +399,8 @@ async function shouldUseExchangeBalance( initiatorAddress: AddressLike, exchangeContractAddress: AddressLike, amount: bigint, - provider: JsonRpcProvider + provider: JsonRpcProvider, + logger?: ((message: string) => void) | undefined ) { const { walletBalance, exchangeBalance } = await getTotalBalance( srcToken, @@ -387,11 +408,13 @@ async function shouldUseExchangeBalance( exchangeContractAddress, provider ); + const exchangeAllowance = await getExchangeAllowance(srcToken, initiatorAddress, exchangeContractAddress, provider); + logger?.('test_123'); if (walletBalance + exchangeBalance < amount) { throw new Error( - `Not enough balance to make swap, totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}` + `Not enough balance to make swap, walletBalance: ${walletBalance} exchangeBalance: ${exchangeBalance} totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}` ); } let useExchangeBalance = true; diff --git a/src/Unit/Exchange/index.ts b/src/Unit/Exchange/index.ts index 217afb3..7b84666 100644 --- a/src/Unit/Exchange/index.ts +++ b/src/Unit/Exchange/index.ts @@ -47,7 +47,7 @@ export default class Exchange { public generateSwapCalldata(params: PureGenerateSwapCalldataParams) { return generateSwapCalldataWithUnit({ ...params, - unit: this.unit + unit: this.unit, }) } diff --git a/src/services/Aggregator/schemas/swapInfoSchema.ts b/src/services/Aggregator/schemas/swapInfoSchema.ts index 758e7af..46a663f 100644 --- a/src/services/Aggregator/schemas/swapInfoSchema.ts +++ b/src/services/Aggregator/schemas/swapInfoSchema.ts @@ -12,6 +12,8 @@ const exchangeContractStep = z.object({ assetIn: z.string(), assetOut: z.string(), factory: z.string(), + assetAddressIn: z.string(), + assetAddressOut: z.string(), }); const swapInfoBase = z.object({ diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index 98629a1..8443458 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -514,6 +514,9 @@ class AggregatorWS { assetIn: path.ai, assetOut: path.ao, factory: path.f, + assetAddressIn: path.aai, + assetAddressOut: path.aao, + fee: path.fee, })), poolOptimal: json.po, ...(json.oi) && { diff --git a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts index 8e37fd8..66581e1 100644 --- a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts +++ b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts @@ -40,6 +40,9 @@ const swapInfoSchemaBase = baseMessageSchema.extend({ ai: z.string().toUpperCase(), // asset in ao: z.string().toUpperCase(), // asset out f: factorySchema, // factory + aai: z.string(), // asset address in + aao: z.string(), // asset address out + fee: z.number().optional(), // fee })), usd: z.object({ // USD info of this swap, nullable aa: z.number().optional(), // available amount in, USD diff --git a/src/types.ts b/src/types.ts index 8696dec..c02bc8f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -179,6 +179,9 @@ export type SingleSwap = { assetIn: string assetOut: string factory: Factory + assetAddressIn: string + assetAddressOut: string + fee?: number | undefined } export type SwapInfoBase = {