diff --git a/src/utils/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts similarity index 78% rename from src/utils/generateSwapCalldata.ts rename to src/Unit/Exchange/generateSwapCalldata.ts index b6ba623..9c86866 100644 --- a/src/utils/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -2,8 +2,11 @@ import type { ExchangeWithGenericSwap } from '@orionprotocol/contracts/lib/ether import { UniswapV3Pool__factory, ERC20__factory, SwapExecutor__factory, CurveRegistry__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js'; import { BigNumber, ethers } from 'ethers'; import { concat, defaultAbiCoder, type BytesLike } from 'ethers/lib/utils.js'; +import { safeGet, type SafeArray } from '../../utils/safeGetters.js'; +import type Unit from '../index.js'; +import { simpleFetch } from 'simple-typed-fetch'; -type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" +export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" export type SwapInfo = { pool: string, @@ -12,24 +15,33 @@ export type SwapInfo = { factory: Factory } -type CallParams = { +export type CallParams = { isMandatory?: boolean, target?: string, gaslimit?: BigNumber, value?: BigNumber } -export default async function generateSwapCalldata( +export type GenerateSwapCalldataParams = { amount: string, minReturnAmount: string, receiverAddress: string, - exchangeAddress: string, - executorAddress: string, - path: SwapInfo[], - weth: string, - curveRegistry: string, - provider: ethers.providers.JsonRpcProvider + path: SafeArray, + unit: Unit +} + +export default async function generateSwapCalldata({ + amount, + minReturnAmount, + receiverAddress, + path, + unit +}: GenerateSwapCalldataParams ): Promise<{ calldata: string, swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct }> { + const wethAddress = safeGet(unit.contracts, "WETH") + const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry") + const { exchangeContractAddress, swapExecutorContractAddress } = await simpleFetch(unit.blockchainService.getInfo)(); + if (path == undefined || path.length == 0) { throw new Error(`Empty path`); } @@ -42,34 +54,42 @@ export default async function generateSwapCalldata( const swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct = { srcToken: path.first().assetIn, dstToken: path.last().assetOut, - srcReceiver: executorAddress, + srcReceiver: swapExecutorContractAddress, dstReceiver: receiverAddress, amount: amount, minReturnAmount: minReturnAmount, flags: 0 } + let calldata: string switch (factory) { case "OrionV2": { swapDescription.srcReceiver = path.first().pool - calldata = await generateUni2Calls(exchangeAddress, path); + calldata = await generateUni2Calls(exchangeContractAddress, path); break; } case "UniswapV2": { swapDescription.srcReceiver = path.first().pool - calldata = await generateUni2Calls(exchangeAddress, path); + calldata = await generateUni2Calls(exchangeContractAddress, path); break; } case "UniswapV3": { - calldata = await generateUni3Calls(amount, exchangeAddress, weth, path, provider) + calldata = await generateUni3Calls(amount, exchangeContractAddress, wethAddress, path, unit.provider) break; } case "OrionV3": { - calldata = await generateOrion3Calls(amount, exchangeAddress, weth, path, provider) + calldata = await generateOrion3Calls(amount, exchangeContractAddress, wethAddress, path, unit.provider) break; } case "Curve": { - calldata = await generateCurveStableSwapCalls(amount, exchangeAddress, executorAddress, path, provider, curveRegistry); + calldata = await generateCurveStableSwapCalls( + amount, + exchangeContractAddress, + swapExecutorContractAddress, + path, + unit.provider, + curveRegistryAddress + ); break; } default: { @@ -83,7 +103,7 @@ export default async function generateSwapCalldata( export async function generateUni2Calls( exchangeAddress: string, - path: SwapInfo[] + path: SafeArray ) { const executorInterface = SwapExecutor__factory.createInterface() const calls: BytesLike[] = [] @@ -114,11 +134,11 @@ export async function generateUni2Calls( return generateCalls(calls) } -export async function generateUni3Calls( +async function generateUni3Calls( amount: string, - exchangeAddress: string, + exchangeContractAddress: string, weth: string, - path: SwapInfo[], + path: SafeArray, provider: ethers.providers.JsonRpcProvider ) { const encodedPools: BytesLike[] = [] @@ -141,17 +161,17 @@ export async function generateUni3Calls( encodedPools.push(encodedPool) } const executorInterface = SwapExecutor__factory.createInterface() - let calldata = executorInterface.encodeFunctionData("uniswapV3SwapTo", [encodedPools, exchangeAddress, amount]) + let calldata = executorInterface.encodeFunctionData("uniswapV3SwapTo", [encodedPools, exchangeContractAddress, amount]) calldata = addCallParams(calldata) return generateCalls([calldata]) } -export async function generateOrion3Calls( +async function generateOrion3Calls( amount: string, - exchangeAddress: string, + exchangeContractAddress: string, weth: string, - path: SwapInfo[], + path: SafeArray, provider: ethers.providers.JsonRpcProvider ) { const encodedPools: BytesLike[] = [] @@ -174,17 +194,17 @@ export async function generateOrion3Calls( encodedPools.push(encodedPool) } const executorInterface = SwapExecutor__factory.createInterface() - let calldata = executorInterface.encodeFunctionData("orionV3SwapTo", [encodedPools, exchangeAddress, amount]) + let calldata = executorInterface.encodeFunctionData("orionV3SwapTo", [encodedPools, exchangeContractAddress, amount]) calldata = addCallParams(calldata) return generateCalls([calldata]) } -export async function generateCurveStableSwapCalls( +async function generateCurveStableSwapCalls( amount: string, - exchangeAddress: string, + exchangeContractAddress: string, executorAddress: string, - path: SwapInfo[], + path: SafeArray, provider: ethers.providers.JsonRpcProvider, curveRegistry: string ) { @@ -204,7 +224,7 @@ export async function generateCurveStableSwapCalls( if (executorAllowance.lt(amount)) { const calldata = addCallParams( executorInterface.encodeFunctionData("safeApprove", [ - swap.assetIn, + swap.assetIn, swap.pool, ethers.constants.MaxUint256 ]) @@ -218,8 +238,7 @@ export async function generateCurveStableSwapCalls( j, amount, 0, - exchangeAddress - ]) + exchangeContractAddress]) calldata = addCallParams(calldata) calls.push(calldata) @@ -227,7 +246,7 @@ export async function generateCurveStableSwapCalls( return generateCalls(calls) } -export function addCallParams( +function addCallParams( calldata: BytesLike, callParams?: CallParams ) { @@ -257,37 +276,7 @@ export function addCallParams( } -export async function generateCalls(calls: BytesLike[]) { +async function generateCalls(calls: BytesLike[]) { const executorInterface = SwapExecutor__factory.createInterface() return "0x" + executorInterface.encodeFunctionData("func_70LYiww", [ethers.constants.AddressZero, calls]).slice(74) -} - -declare global { - interface Array { - get(index: number): T; - last(): T - first(): T - } -} - -if (!Array.prototype.get) { - Array.prototype.get = function (this: T[], index: number): T { - const value = this.at(index); - if (value === undefined) { - throw new Error(`Element at index ${index} is undefined. Array: ${this}`) - } - return value - } -} - -if (!Array.prototype.last) { - Array.prototype.last = function (this: T[]): T { - return this.get(this.length - 1) - } -} - -if (!Array.prototype.first) { - Array.prototype.first = function (this: T[]): T { - return this.get(0) - } } \ No newline at end of file diff --git a/src/Unit/Exchange/index.ts b/src/Unit/Exchange/index.ts index c5d36cc..2c90119 100644 --- a/src/Unit/Exchange/index.ts +++ b/src/Unit/Exchange/index.ts @@ -1,8 +1,8 @@ import type Unit from '../index.js'; import deposit, { type DepositParams } from './deposit.js'; import getSwapInfo, { type GetSwapInfoParams } from './getSwapInfo.js'; -import type { SwapLimitParams } from './swapLimit.js'; -import swapLimit from './swapLimit.js'; +import generateSwapCalldata, { type GenerateSwapCalldataParams } from './generateSwapCalldata.js'; +import swapLimit, {type SwapLimitParams} from './swapLimit.js'; import swapMarket, { type SwapMarketParams } from './swapMarket.js'; import withdraw, { type WithdrawParams } from './withdraw.js'; @@ -11,6 +11,7 @@ type PureSwapLimitParams = Omit type PureDepositParams = Omit type PureWithdrawParams = Omit type PureGetSwapMarketInfoParams = Omit +type PureGenerateSwapCalldataParams = Omit export default class Exchange { private readonly unit: Unit; @@ -54,4 +55,11 @@ export default class Exchange { unit: this.unit, }); } + + public generateSwapCalldata(params: PureGenerateSwapCalldataParams) { + return generateSwapCalldata({ + ...params, + unit: this.unit + }) + } } diff --git a/src/Unit/index.ts b/src/Unit/index.ts index b9fb66c..c8b4462 100644 --- a/src/Unit/index.ts +++ b/src/Unit/index.ts @@ -32,6 +32,8 @@ export default class Unit { public readonly config: VerboseUnitConfig; + public readonly contracts: Record; + constructor(config: KnownConfig | VerboseUnitConfig) { if ('env' in config) { const staticConfig = envs[config.env]; @@ -42,7 +44,6 @@ export default class Unit { const networkConfig = staticConfig.networks[config.chainId]; if (!networkConfig) throw new Error(`Invalid chainId: ${config.chainId}. Available chainIds: ${Object.keys(staticConfig.networks).join(', ')}`); - this.config = { chainId: config.chainId, nodeJsonRpc: networkConfig.rpc ?? chainConfig.rpc, @@ -64,9 +65,10 @@ export default class Unit { } const chainInfo = chains[config.chainId]; if (!chainInfo) throw new Error('Chain info is required'); - + this.chainId = config.chainId; this.networkCode = chainInfo.code; + this.contracts = chainInfo.contracts const intNetwork = parseInt(this.chainId, 10); if (Number.isNaN(intNetwork)) throw new Error('Invalid chainId (not a number)' + this.chainId); this.provider = new ethers.providers.StaticJsonRpcProvider(this.config.nodeJsonRpc, intNetwork);