From 7ae44c930cad7d477b9d1edf5ae483686fee8c99 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Fri, 4 Aug 2023 13:21:54 +0300 Subject: [PATCH 01/38] Added support for single factory swaps in utils --- package.json | 2 +- src/utils/arrayHelpers.ts | 29 +++ src/utils/generateSwapCalldata.ts | 293 ++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/utils/arrayHelpers.ts create mode 100644 src/utils/generateSwapCalldata.ts diff --git a/package.json b/package.json index 501882e..793e234 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@babel/runtime": "^7.21.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@orionprotocol/contracts": "1.9.0", + "@orionprotocol/contracts": "1.16.1", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", diff --git a/src/utils/arrayHelpers.ts b/src/utils/arrayHelpers.ts new file mode 100644 index 0000000..fc583e2 --- /dev/null +++ b/src/utils/arrayHelpers.ts @@ -0,0 +1,29 @@ +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/utils/generateSwapCalldata.ts b/src/utils/generateSwapCalldata.ts new file mode 100644 index 0000000..b6ba623 --- /dev/null +++ b/src/utils/generateSwapCalldata.ts @@ -0,0 +1,293 @@ +import type { ExchangeWithGenericSwap } from '@orionprotocol/contracts/lib/ethers-v5/Exchange.js'; +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'; + +type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" + +export type SwapInfo = { + pool: string, + assetIn: string, + assetOut: string, + factory: Factory +} + +type CallParams = { + isMandatory?: boolean, + target?: string, + gaslimit?: BigNumber, + value?: BigNumber +} + +export default async function generateSwapCalldata( + amount: string, + minReturnAmount: string, + receiverAddress: string, + exchangeAddress: string, + executorAddress: string, + path: SwapInfo[], + weth: string, + curveRegistry: string, + provider: ethers.providers.JsonRpcProvider +): Promise<{ calldata: string, swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct }> { + if (path == undefined || path.length == 0) { + throw new Error(`Empty path`); + } + const factory = path.first().factory + + if (!path.every(e => e.factory === factory)) { + throw new Error(`Supporting only swaps with single factory`); + } + + const swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct = { + srcToken: path.first().assetIn, + dstToken: path.last().assetOut, + srcReceiver: executorAddress, + 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); + break; + } + case "UniswapV2": { + swapDescription.srcReceiver = path.first().pool + calldata = await generateUni2Calls(exchangeAddress, path); + break; + } + case "UniswapV3": { + calldata = await generateUni3Calls(amount, exchangeAddress, weth, path, provider) + break; + } + case "OrionV3": { + calldata = await generateOrion3Calls(amount, exchangeAddress, weth, path, provider) + break; + } + case "Curve": { + calldata = await generateCurveStableSwapCalls(amount, exchangeAddress, executorAddress, path, provider, curveRegistry); + break; + } + default: { + throw new Error(`Factory ${factory} is not supported`) + } + } + return { swapDescription, calldata } +} + + + +export async function generateUni2Calls( + exchangeAddress: string, + path: SwapInfo[] +) { + const executorInterface = SwapExecutor__factory.createInterface() + const calls: BytesLike[] = [] + if (path.length > 1) { + for (let i = 0; i < path.length - 1; ++i) { + const currentSwap = path.get(i) + const nextSwap = path.get(i + 1) + + const calldata = executorInterface.encodeFunctionData("swapUniV2", [ + currentSwap.pool, + currentSwap.assetIn, + currentSwap.assetOut, + defaultAbiCoder.encode(["uint256"], [concat(["0x03", nextSwap.pool])]), + ] + ) + calls.push(addCallParams(calldata)) + } + } + const lastSwap = path.last(); + const calldata = executorInterface.encodeFunctionData("swapUniV2", [ + lastSwap.pool, + lastSwap.assetIn, + lastSwap.assetOut, + defaultAbiCoder.encode(["uint256"], [concat(["0x03", exchangeAddress])]), + ]) + calls.push(addCallParams(calldata)) + + return generateCalls(calls) +} + +export async function generateUni3Calls( + amount: string, + exchangeAddress: string, + weth: string, + path: SwapInfo[], + provider: ethers.providers.JsonRpcProvider +) { + const encodedPools: BytesLike[] = [] + for (const swap of path) { + const pool = UniswapV3Pool__factory.connect(swap.pool, provider) + const token0 = await pool.token0() + const zeroForOne = token0 === swap.assetIn + const unwrapWETH = swap.assetOut === ethers.constants.AddressZero + if (unwrapWETH) { + swap.assetOut = weth + } + + let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address]) + encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) + let firstByte = 0 + if (unwrapWETH) firstByte += 32 + if (!zeroForOne) firstByte += 128 + const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte]) + encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool])) + encodedPools.push(encodedPool) + } + const executorInterface = SwapExecutor__factory.createInterface() + let calldata = executorInterface.encodeFunctionData("uniswapV3SwapTo", [encodedPools, exchangeAddress, amount]) + calldata = addCallParams(calldata) + + return generateCalls([calldata]) +} + +export async function generateOrion3Calls( + amount: string, + exchangeAddress: string, + weth: string, + path: SwapInfo[], + provider: ethers.providers.JsonRpcProvider +) { + const encodedPools: BytesLike[] = [] + for (const swap of path) { + const pool = UniswapV3Pool__factory.connect(swap.pool, provider) + const token0 = await pool.token0() + const zeroForOne = token0 === swap.assetIn + const unwrapWETH = swap.assetOut === ethers.constants.AddressZero + if (unwrapWETH) { + swap.assetOut = weth + } + + let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address]) + encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) + let firstByte = 0 + if (unwrapWETH) firstByte += 32 + if (!zeroForOne) firstByte += 128 + const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte]) + encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool])) + encodedPools.push(encodedPool) + } + const executorInterface = SwapExecutor__factory.createInterface() + let calldata = executorInterface.encodeFunctionData("orionV3SwapTo", [encodedPools, exchangeAddress, amount]) + calldata = addCallParams(calldata) + + return generateCalls([calldata]) +} + +export async function generateCurveStableSwapCalls( + amount: string, + exchangeAddress: string, + executorAddress: string, + path: SwapInfo[], + provider: ethers.providers.JsonRpcProvider, + curveRegistry: string +) { + if (path.length > 1) { + throw new Error("Supporting only single stable swap on curve") + } + const executorInterface = SwapExecutor__factory.createInterface() + const registry = CurveRegistry__factory.connect(curveRegistry, provider) + + const swap = path.first() + const firstToken = ERC20__factory.connect(swap.assetIn, provider) + const { pool, assetIn, assetOut } = swap + const [i, j,] = await registry.get_coin_indices(pool, assetIn, assetOut) + + const executorAllowance = await firstToken.allowance(executorAddress, swap.pool) + const calls: BytesLike[] = [] + if (executorAllowance.lt(amount)) { + const calldata = addCallParams( + executorInterface.encodeFunctionData("safeApprove", [ + swap.assetIn, + swap.pool, + ethers.constants.MaxUint256 + ]) + ) + calls.push(calldata) + } + let calldata = executorInterface.encodeFunctionData("curveSwapStableAmountIn", [ + pool, + assetOut, + i, + j, + amount, + 0, + exchangeAddress + ]) + + calldata = addCallParams(calldata) + calls.push(calldata) + + return generateCalls(calls) +} + +export function addCallParams( + calldata: BytesLike, + callParams?: CallParams +) { + let firstByte = 0 + if (callParams) { + if (callParams.value !== undefined) { + firstByte += 16 + const encodedValue = ethers.utils.solidityPack(["uint128"], [callParams.value]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedValue, calldata])) + } + if (callParams.target !== undefined) { + firstByte += 32 + const encodedAddress = ethers.utils.solidityPack(["address"], [callParams.target]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedAddress, calldata])) + } + if (callParams.gaslimit !== undefined) { + firstByte += 64 + const encodedGaslimit = ethers.utils.solidityPack(["uint32"], [callParams.gaslimit]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedGaslimit, calldata])) + } + if (callParams.isMandatory !== undefined) firstByte += 128 + } + + const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, calldata])) + return calldata +} + + +export 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 From 232fae6631262956601600ad967a55e718bc0bf8 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Fri, 4 Aug 2023 13:27:45 +0300 Subject: [PATCH 02/38] Updated package-lock.json --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index da8adff..3a2bd24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.34", + "version": "0.19.41", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.19.34", + "version": "0.19.41", "license": "ISC", "dependencies": { "@babel/runtime": "^7.21.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@orionprotocol/contracts": "1.9.0", + "@orionprotocol/contracts": "1.16.1", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", @@ -2330,9 +2330,9 @@ } }, "node_modules/@orionprotocol/contracts": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.9.0.tgz", - "integrity": "sha512-nYShitqtgVoz5BiXPapZ0Fi+IaW4ufBLGFeHki7ZRV6oXgwDuXoCbD0kLGdOUZDeDjzuMqGRolbrI3PAoI3Lpg==" + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.16.1.tgz", + "integrity": "sha512-2Y7oubSfjP5Xn+1ldAY051DrvF2LLoLewGyFbwf1bKlUzbhDNGF2noBO0mWU5sQNQf7eCC913YCob3lvd3AvyQ==" }, "node_modules/@sinclair/typebox": { "version": "0.25.23", From 98bac7f86d04d4d451ded1b55d4a2c5d4c8f82f5 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Sat, 5 Aug 2023 16:42:51 +0300 Subject: [PATCH 03/38] Created array subclass with safeGetter --- src/utils/arrayHelpers.ts | 29 ------------------------ src/utils/safeGetters.ts | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 29 deletions(-) delete mode 100644 src/utils/arrayHelpers.ts create mode 100644 src/utils/safeGetters.ts diff --git a/src/utils/arrayHelpers.ts b/src/utils/arrayHelpers.ts deleted file mode 100644 index fc583e2..0000000 --- a/src/utils/arrayHelpers.ts +++ /dev/null @@ -1,29 +0,0 @@ -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/utils/safeGetters.ts b/src/utils/safeGetters.ts new file mode 100644 index 0000000..944f498 --- /dev/null +++ b/src/utils/safeGetters.ts @@ -0,0 +1,47 @@ +export class SafeArray extends Array { + + public static override from(array: T[]): SafeArray { + return new SafeArray(array); + } + + constructor(array: T[]) { + super(array.length); + array.forEach((element, index) => { + this[index] = element; + }) + } + + public toArray(): T[] { + return [...this]; + } + + public override map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): SafeArray { + return new SafeArray(super.map(callbackfn, thisArg)); + } + + public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): SafeArray { + return new SafeArray(super.filter(callbackfn, thisArg)); + } + + public get(this: SafeArray, index: number): T { + const value = this.at(index); + if (value === undefined) { + throw new Error(`Element at index ${index} is undefined. Array: ${this}`) + } + return value + } + + public last(this: SafeArray): T { + return this.get(this.length - 1) + } + + public first(this: SafeArray): T { + return this.get(0) + } +} + +export function safeGet(obj: Partial>, key: string, errorMessage?: string) { + const value = obj[key]; + if (value === undefined) throw new Error(`Key '${key.toString()}' not found in object. Available keys: ${Object.keys(obj).join(', ')}.${errorMessage ? ` ${errorMessage}` : ''}`); + return value; +} \ No newline at end of file From d9ca7cba1ad857451bef71a062a3ae181047e6c6 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Sat, 5 Aug 2023 16:43:58 +0300 Subject: [PATCH 04/38] moved generateSwapCalldata to Exchange unit --- .../Exchange}/generateSwapCalldata.ts | 113 ++++++++---------- src/Unit/Exchange/index.ts | 12 +- src/Unit/index.ts | 6 +- 3 files changed, 65 insertions(+), 66 deletions(-) rename src/{utils => Unit/Exchange}/generateSwapCalldata.ts (78%) 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); From 03056563f26009b99f8ce646200b9c8292a86446 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Sat, 5 Aug 2023 16:44:36 +0300 Subject: [PATCH 05/38] Update schemas due to generateSwapCalldata method --- src/config/chains.json | 80 +++++++++++++++---- src/config/schemas/pureChainSchema.ts | 1 + .../BlockchainService/schemas/infoSchema.ts | 1 + 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/config/chains.json b/src/config/chains.json index d5de68c..f4b4d29 100644 --- a/src/config/chains.json +++ b/src/config/chains.json @@ -6,7 +6,11 @@ "shortName": "ETH", "code": "eth", "rpc": "https://trade.orionprotocol.io/rpc", - "baseCurrencyName": "ETH" + "baseCurrencyName": "ETH", + "contracts": { + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "curveRegistry": "0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5" + } }, "56": { "chainId": "56", @@ -15,7 +19,11 @@ "shortName": "BSC", "code": "bsc", "rpc": "https://bsc-dataseed.binance.org/", - "baseCurrencyName": "BNB" + "baseCurrencyName": "BNB", + "contracts": { + "WETH": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + "curveRegistry": "" + } }, "97": { "chainId": "97", @@ -24,7 +32,11 @@ "shortName": "BSC-Testnet", "code": "bsc", "rpc": "https://bsc-stage.node.orionprotocol.io/", - "baseCurrencyName": "BNB" + "baseCurrencyName": "BNB", + "contracts": { + "WETH": "0x23eE96bEaAB62abE126AA192e677c52bB7d274F0", + "curveRegistry": "0x8845b36C3DE93379F468590FFa102D4aF8C49A6c" + } }, "3": { "chainId": "3", @@ -33,7 +45,11 @@ "shortName": "ETH-Ropsten", "code": "eth", "rpc": "https://testing.orionprotocol.io/eth-ropsten/rpc", - "baseCurrencyName": "ETH" + "baseCurrencyName": "ETH", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "5": { "chainId": "5", @@ -42,7 +58,11 @@ "shortName": "ETH-Goerli", "code": "eth", "rpc": "https://testing.orionprotocol.io/eth-goerli/rpc", - "baseCurrencyName": "ETH" + "baseCurrencyName": "ETH", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "421613": { "chainId": "421613", @@ -51,7 +71,11 @@ "shortName": "Arbitrum Goerli", "code": "arb", "rpc": "https://goerli-rollup.arbitrum.io/rpc", - "baseCurrencyName": "ETH" + "baseCurrencyName": "ETH", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "4002": { "chainId": "4002", @@ -60,7 +84,11 @@ "shortName": "FTM-Testnet", "code": "ftm", "rpc": "https://testing.orionprotocol.io/ftm-testnet/rpc", - "baseCurrencyName": "FTM" + "baseCurrencyName": "FTM", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "250": { "chainId": "250", @@ -69,7 +97,11 @@ "shortName": "FTM", "code": "ftm", "rpc": "https://rpcapi.fantom.network/", - "baseCurrencyName": "FTM" + "baseCurrencyName": "FTM", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "137": { "chainId": "137", @@ -78,7 +110,11 @@ "code": "polygon", "baseCurrencyName": "MATIC", "rpc": "https://polygon-rpc.com/", - "explorer": "https://polygonscan.com/" + "explorer": "https://polygonscan.com/", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "80001": { "chainId": "80001", @@ -87,7 +123,11 @@ "code": "polygon", "baseCurrencyName": "MATIC", "rpc": "https://rpc-mumbai.matic.today", - "explorer": "https://mumbai.polygonscan.com/" + "explorer": "https://mumbai.polygonscan.com/", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "66": { "chainId": "66", @@ -96,7 +136,11 @@ "shortName": "OKC", "code": "okc", "rpc": "https://exchainrpc.okex.org/", - "baseCurrencyName": "OKT" + "baseCurrencyName": "OKT", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "65": { "chainId": "65", @@ -105,7 +149,11 @@ "shortName": "OKC-Testnet", "code": "okc", "rpc": "https://exchaintestrpc.okex.org/", - "baseCurrencyName": "OKT" + "baseCurrencyName": "OKT", + "contracts": { + "WETH": "", + "curveRegistry": "" + } }, "56303": { "chainId": "56303", @@ -114,6 +162,10 @@ "code": "drip", "baseCurrencyName": "DRIP", "rpc": "testnet.1d.rip", - "explorer": "https://explorer-testnet.1d.rip/" + "explorer": "https://explorer-testnet.1d.rip/", + "contracts": { + "WETH": "", + "curveRegistry": "" + } } -} +} \ No newline at end of file diff --git a/src/config/schemas/pureChainSchema.ts b/src/config/schemas/pureChainSchema.ts index abc224b..9c45941 100644 --- a/src/config/schemas/pureChainSchema.ts +++ b/src/config/schemas/pureChainSchema.ts @@ -10,6 +10,7 @@ export const pureChainInfoPayloadSchema = z.object({ explorer: z.string(), rpc: z.string(), baseCurrencyName: z.string(), + contracts: z.record(z.string(), z.string()) }); export const pureChainInfoSchema = z.record( diff --git a/src/services/BlockchainService/schemas/infoSchema.ts b/src/services/BlockchainService/schemas/infoSchema.ts index 326f18e..472a895 100644 --- a/src/services/BlockchainService/schemas/infoSchema.ts +++ b/src/services/BlockchainService/schemas/infoSchema.ts @@ -11,6 +11,7 @@ const infoSchema = z.object({ chainId: z.number(), chainName: z.string(), exchangeContractAddress: z.string(), + swapExecutorContractAddress: z.string(), oracleContractAddress: z.string(), matcherAddress: z.string(), orderFeePercent: z.number(), From 9fcccdcfd67ad25766b2c158f6ede5592f5c2569 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Sun, 6 Aug 2023 13:16:39 +0300 Subject: [PATCH 06/38] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 793e234..56ad85e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.41", + "version": "0.19.42", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From 8bac672bfdd813ce2e79ff50873ded8632c5ec84 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Sun, 6 Aug 2023 13:18:14 +0300 Subject: [PATCH 07/38] SafeArray from Arraylike --- src/Unit/Exchange/generateSwapCalldata.ts | 5 ++--- src/utils/safeGetters.ts | 16 ++++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index 9c86866..2bfeefa 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -2,7 +2,7 @@ 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 { safeGet, SafeArray } from '../../utils/safeGetters.js'; import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; @@ -46,7 +46,6 @@ export default async function generateSwapCalldata({ throw new Error(`Empty path`); } const factory = path.first().factory - if (!path.every(e => e.factory === factory)) { throw new Error(`Supporting only swaps with single factory`); } @@ -60,7 +59,7 @@ export default async function generateSwapCalldata({ minReturnAmount: minReturnAmount, flags: 0 } - + let calldata: string switch (factory) { case "OrionV2": { diff --git a/src/utils/safeGetters.ts b/src/utils/safeGetters.ts index 944f498..3bf4336 100644 --- a/src/utils/safeGetters.ts +++ b/src/utils/safeGetters.ts @@ -1,14 +1,18 @@ export class SafeArray extends Array { - public static override from(array: T[]): SafeArray { + public static override from(array: ArrayLike): SafeArray { return new SafeArray(array); } - constructor(array: T[]) { + constructor(array: ArrayLike) { super(array.length); - array.forEach((element, index) => { - this[index] = element; - }) + for (const index in array) { + const value = array[index] + if (value === undefined) { + throw new Error("Array passed to constructor has undefined values") + } + this[index] = value + } } public toArray(): T[] { @@ -26,7 +30,7 @@ export class SafeArray extends Array { public get(this: SafeArray, index: number): T { const value = this.at(index); if (value === undefined) { - throw new Error(`Element at index ${index} is undefined. Array: ${this}`) + throw new Error(`Element at index ${index} is undefined.`) } return value } From e9d908445d74dfa024c976306b0d57f3d5b355ee Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Sun, 6 Aug 2023 13:21:50 +0300 Subject: [PATCH 08/38] update version once again --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 56ad85e..686d15d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.42", + "version": "0.19.46", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From e205ebeb970f1a48f71e264c5e143235e0cf349e Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Mon, 7 Aug 2023 10:30:52 +0300 Subject: [PATCH 09/38] minor fixes --- src/Unit/Exchange/generateSwapCalldata.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index 2bfeefa..ed32ea9 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -6,6 +6,8 @@ import { safeGet, SafeArray } from '../../utils/safeGetters.js'; import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; +const EXECUTOR_SWAP_FUNCTION = "func_70LYiww" + export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" export type SwapInfo = { @@ -26,7 +28,7 @@ export type GenerateSwapCalldataParams = { amount: string, minReturnAmount: string, receiverAddress: string, - path: SafeArray, + path: ArrayLike, unit: Unit } @@ -34,13 +36,14 @@ export default async function generateSwapCalldata({ amount, minReturnAmount, receiverAddress, - path, + path: 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)(); + const path = SafeArray.from(path_) if (path == undefined || path.length == 0) { throw new Error(`Empty path`); @@ -245,28 +248,29 @@ async function generateCurveStableSwapCalls( return generateCalls(calls) } +// Adds additional byte to single swap with settings function addCallParams( calldata: BytesLike, callParams?: CallParams ) { let firstByte = 0 if (callParams) { - if (callParams.value !== undefined) { - firstByte += 16 + if (callParams.value !== undefined) { + firstByte += 16 // 00000010 const encodedValue = ethers.utils.solidityPack(["uint128"], [callParams.value]) calldata = ethers.utils.hexlify(ethers.utils.concat([encodedValue, calldata])) } if (callParams.target !== undefined) { - firstByte += 32 + firstByte += 32 // 00000100 const encodedAddress = ethers.utils.solidityPack(["address"], [callParams.target]) calldata = ethers.utils.hexlify(ethers.utils.concat([encodedAddress, calldata])) } if (callParams.gaslimit !== undefined) { - firstByte += 64 + firstByte += 64 // 00000100 const encodedGaslimit = ethers.utils.solidityPack(["uint32"], [callParams.gaslimit]) calldata = ethers.utils.hexlify(ethers.utils.concat([encodedGaslimit, calldata])) } - if (callParams.isMandatory !== undefined) firstByte += 128 + if (callParams.isMandatory !== undefined) firstByte += 128 // 00001000 } const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte]) @@ -277,5 +281,5 @@ function addCallParams( async function generateCalls(calls: BytesLike[]) { const executorInterface = SwapExecutor__factory.createInterface() - return "0x" + executorInterface.encodeFunctionData("func_70LYiww", [ethers.constants.AddressZero, calls]).slice(74) + return "0x" + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.constants.AddressZero, calls]).slice(74) } \ No newline at end of file From 994d76639106f9dc9174232da89344f5222d9eac Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Mon, 7 Aug 2023 12:10:55 +0300 Subject: [PATCH 10/38] Add conditional paremeters --- src/Unit/Exchange/generateSwapCalldata.ts | 15 +++++++++++++-- src/utils/safeGetters.ts | 9 +++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index ed32ea9..f3fd6f4 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -2,7 +2,7 @@ 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, SafeArray } from '../../utils/safeGetters.js'; +import must, { safeGet, SafeArray } from '../../utils/safeGetters.js'; import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; @@ -29,6 +29,8 @@ export type GenerateSwapCalldataParams = { minReturnAmount: string, receiverAddress: string, path: ArrayLike, + exchangeContractAddress?: string, + swapExecutorContractAddress?: string, unit: Unit } @@ -37,12 +39,21 @@ export default async function generateSwapCalldata({ minReturnAmount, receiverAddress, path: path_, + exchangeContractAddress, + swapExecutorContractAddress, 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 (swapExecutorContractAddress === undefined || swapExecutorContractAddress === undefined) { + const info = await simpleFetch(unit.blockchainService.getInfo)(); + if (swapExecutorContractAddress === undefined) swapExecutorContractAddress = info.swapExecutorContractAddress + if (exchangeContractAddress === undefined) exchangeContractAddress = info.exchangeContractAddress + } + must(swapExecutorContractAddress !== undefined) + must(exchangeContractAddress !== undefined) + const path = SafeArray.from(path_) if (path == undefined || path.length == 0) { diff --git a/src/utils/safeGetters.ts b/src/utils/safeGetters.ts index 3bf4336..17f273f 100644 --- a/src/utils/safeGetters.ts +++ b/src/utils/safeGetters.ts @@ -48,4 +48,13 @@ export function safeGet(obj: Partial>, key: string, errorMe const value = obj[key]; if (value === undefined) throw new Error(`Key '${key.toString()}' not found in object. Available keys: ${Object.keys(obj).join(', ')}.${errorMessage ? ` ${errorMessage}` : ''}`); return value; +} + +const prefix = 'Requirement not met'; + +export default function must(condition: unknown, message?: string | (() => string)): asserts condition { + if (condition) return; + const provided = typeof message === 'function' ? message() : message; + const value = provided ? `${prefix}: ${provided}` : prefix; + throw new Error(value); } \ No newline at end of file From f64738ec6ad9071d7f4546bfc8669776c653736d Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Wed, 9 Aug 2023 13:19:05 +0300 Subject: [PATCH 11/38] Removed conditional params, added exchange->factory record --- package-lock.json | 4 +- src/Unit/Exchange/generateSwapCalldata.ts | 56 ++++++++++++++--------- src/utils/safeGetters.ts | 2 +- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a2bd24..281bb66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.41", + "version": "0.19.46", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.19.41", + "version": "0.19.46", "license": "ISC", "dependencies": { "@babel/runtime": "^7.21.0", diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index f3fd6f4..1679376 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -2,19 +2,38 @@ 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 must, { safeGet, SafeArray } from '../../utils/safeGetters.js'; +import { safeGet, SafeArray } from '../../utils/safeGetters.js'; import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; +import type { Exchange } from '../../types.js'; const EXECUTOR_SWAP_FUNCTION = "func_70LYiww" export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" +const exchangeToType: Partial> = { + 'SPOOKYSWAP': 'UniswapV2', + 'PANCAKESWAP': 'UniswapV2', + 'UNISWAP': 'UniswapV2', + 'QUICKSWAP': 'UniswapV2', + 'ORION_POOL': 'UniswapV2', + 'CHERRYSWAP': 'UniswapV2', + 'OKXSWAP': 'UniswapV2', + 'INTERNAL_POOL_V2': 'UniswapV2', + 'INTERNAL_POOL_V3': "OrionV3", + 'INTERNAL_POOL_V3_0_01': "OrionV3", + 'INTERNAL_POOL_V3_0_05': "OrionV3", + 'INTERNAL_POOL_V3_0_3': "OrionV3", + 'INTERNAL_POOL_V3_1_0': "OrionV3", + 'CURVE': "Curve", + 'CURVE_FACTORY': "Curve", +} + export type SwapInfo = { pool: string, assetIn: string, assetOut: string, - factory: Factory + factory: Exchange } export type CallParams = { @@ -29,8 +48,6 @@ export type GenerateSwapCalldataParams = { minReturnAmount: string, receiverAddress: string, path: ArrayLike, - exchangeContractAddress?: string, - swapExecutorContractAddress?: string, unit: Unit } @@ -39,28 +56,25 @@ export default async function generateSwapCalldata({ minReturnAmount, receiverAddress, path: path_, - exchangeContractAddress, - swapExecutorContractAddress, unit }: GenerateSwapCalldataParams ): Promise<{ calldata: string, swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct }> { - const wethAddress = safeGet(unit.contracts, "WETH") - const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry") - if (swapExecutorContractAddress === undefined || swapExecutorContractAddress === undefined) { - const info = await simpleFetch(unit.blockchainService.getInfo)(); - if (swapExecutorContractAddress === undefined) swapExecutorContractAddress = info.swapExecutorContractAddress - if (exchangeContractAddress === undefined) exchangeContractAddress = info.exchangeContractAddress - } - must(swapExecutorContractAddress !== undefined) - must(exchangeContractAddress !== undefined) - - const path = SafeArray.from(path_) - - if (path == undefined || path.length == 0) { + if (path_ == undefined || path_.length == 0) { throw new Error(`Empty path`); } + const wethAddress = safeGet(unit.contracts, "WETH") + const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry") + const {assetToAddress, swapExecutorContractAddress, exchangeContractAddress} = await simpleFetch(unit.blockchainService.getInfo)(); + + const path = SafeArray.from(path_).map((swapInfo) => { + const swapInfoWithAddresses: SwapInfo = swapInfo + swapInfoWithAddresses.assetIn = safeGet(assetToAddress, swapInfo.assetIn); + swapInfoWithAddresses.assetOut = safeGet(assetToAddress, swapInfo.assetOut); + return swapInfoWithAddresses; + }); + const factory = path.first().factory - if (!path.every(e => e.factory === factory)) { + if (!path.every(swapInfo => swapInfo.factory === factory)) { throw new Error(`Supporting only swaps with single factory`); } @@ -75,7 +89,7 @@ export default async function generateSwapCalldata({ } let calldata: string - switch (factory) { + switch (exchangeToType[factory]) { case "OrionV2": { swapDescription.srcReceiver = path.first().pool calldata = await generateUni2Calls(exchangeContractAddress, path); diff --git a/src/utils/safeGetters.ts b/src/utils/safeGetters.ts index 17f273f..0da1797 100644 --- a/src/utils/safeGetters.ts +++ b/src/utils/safeGetters.ts @@ -52,7 +52,7 @@ export function safeGet(obj: Partial>, key: string, errorMe const prefix = 'Requirement not met'; -export default function must(condition: unknown, message?: string | (() => string)): asserts condition { +export function must(condition: unknown, message?: string | (() => string)): asserts condition { if (condition) return; const provided = typeof message === 'function' ? message() : message; const value = provided ? `${prefix}: ${provided}` : prefix; From b92ba570f825a04a8d6f19242424c4368a2b62ef Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Wed, 9 Aug 2023 13:24:47 +0300 Subject: [PATCH 12/38] Upd version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 686d15d..8994810 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.46", + "version": "0.19.48-dev.1", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From 452da5ad875a27f70e0fd4f4a6e79c6fed4cf887 Mon Sep 17 00:00:00 2001 From: Dmitriy Pavlov Date: Thu, 10 Aug 2023 14:19:04 +0300 Subject: [PATCH 13/38] add eps field to swap --- package-lock.json | 4 ++-- package.json | 2 +- src/services/Aggregator/ws/schemas/swapInfoSchema.ts | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 281bb66..20d7a15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.46", + "version": "0.19.48-dev.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.19.46", + "version": "0.19.48-dev.1", "license": "ISC", "dependencies": { "@babel/runtime": "^7.21.0", diff --git a/package.json b/package.json index 8994810..bd43adb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.1", + "version": "0.19.48-dev.1-rc-0", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts index 7cc8e35..9c383e6 100644 --- a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts +++ b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts @@ -34,6 +34,12 @@ const swapInfoSchemaBase = baseMessageSchema.extend({ }).optional(), as: alternativeSchema.array(), anm: z.record(z.string()).optional(), // address to ERC20 names + eps: z.array(z.object({ + p: z.string(), + ai: z.string().toUpperCase(), + ao: z.string().toUpperCase(), + f: z.string().toUpperCase(), + })) }); const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({ From 4af3da4bf58f307c2a5bf4371a778706a11a2f9b Mon Sep 17 00:00:00 2001 From: Dmitriy Pavlov Date: Thu, 10 Aug 2023 14:34:24 +0300 Subject: [PATCH 14/38] add field comments --- src/services/Aggregator/ws/schemas/swapInfoSchema.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts index 9c383e6..9607cb2 100644 --- a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts +++ b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts @@ -35,10 +35,10 @@ const swapInfoSchemaBase = baseMessageSchema.extend({ as: alternativeSchema.array(), anm: z.record(z.string()).optional(), // address to ERC20 names eps: z.array(z.object({ - p: z.string(), - ai: z.string().toUpperCase(), - ao: z.string().toUpperCase(), - f: z.string().toUpperCase(), + p: z.string(), // pool address + ai: z.string().toUpperCase(), // asset in + ao: z.string().toUpperCase(), // asset out + f: z.string().toUpperCase(), // factory })) }); From a886c343d370157c76ea952db2d4e36d2935f98e Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 10 Aug 2023 15:26:12 +0300 Subject: [PATCH 15/38] Removed Exchange type --- src/Unit/Exchange/generateSwapCalldata.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index 1679376..15f7399 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -5,13 +5,12 @@ import { concat, defaultAbiCoder, type BytesLike } from 'ethers/lib/utils.js'; import { safeGet, SafeArray } from '../../utils/safeGetters.js'; import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; -import type { Exchange } from '../../types.js'; const EXECUTOR_SWAP_FUNCTION = "func_70LYiww" export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" -const exchangeToType: Partial> = { +const exchangeToType: Partial> = { 'SPOOKYSWAP': 'UniswapV2', 'PANCAKESWAP': 'UniswapV2', 'UNISWAP': 'UniswapV2', @@ -33,7 +32,7 @@ export type SwapInfo = { pool: string, assetIn: string, assetOut: string, - factory: Exchange + factory: string } export type CallParams = { From 8ea9cdfbd49db5caf3e9a04d8f685162810d1142 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 10 Aug 2023 15:29:22 +0300 Subject: [PATCH 16/38] upd version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b95422..187a515 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.1", + "version": "0.19.48-dev.2", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From df38bfdc2cf3cdd89e6f34f060c19b4d9937bcd8 Mon Sep 17 00:00:00 2001 From: Dmitriy Pavlov Date: Thu, 10 Aug 2023 18:46:10 +0300 Subject: [PATCH 17/38] add eps types to services --- package.json | 2 +- src/Unit/Exchange/generateSwapCalldata.ts | 4 +--- src/services/Aggregator/ws/index.ts | 6 ++++++ src/types.ts | 8 ++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1f69f76..8cc0e0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.2-rc-0", + "version": "0.19.48-dev.2-rc-1", "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 15f7399..b525e12 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -125,8 +125,6 @@ export default async function generateSwapCalldata({ return { swapDescription, calldata } } - - export async function generateUni2Calls( exchangeAddress: string, path: SafeArray @@ -306,4 +304,4 @@ function addCallParams( async function generateCalls(calls: BytesLike[]) { const executorInterface = SwapExecutor__factory.createInterface() return "0x" + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.constants.AddressZero, calls]).slice(74) -} \ No newline at end of file +} diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index c014e6c..9aed556 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -500,6 +500,12 @@ class AggregatorWS { minAmountIn: json.ma, path: json.ps, exchanges: json.e, + exchangeContractPath: json.eps.map((path) => ({ + poolAddress: path.p, + assetIn: path.ai, + assetOut: path.ao, + factory: path.f, + })), poolOptimal: json.po, ...(json.oi) && { orderInfo: { diff --git a/src/types.ts b/src/types.ts index 0c19b6c..3d8e434 100644 --- a/src/types.ts +++ b/src/types.ts @@ -164,6 +164,13 @@ export type SwapInfoAlternative = { availableAmountOut?: number | undefined } +type ExchangeContractPath = { + poolAddress: string + assetIn: string + assetOut: string + factory: string +} + export type SwapInfoBase = { swapRequestId: string assetIn: string @@ -174,6 +181,7 @@ export type SwapInfoBase = { minAmountOut: number path: string[] + exchangeContractPath: ExchangeContractPath[] exchanges?: string[] | undefined poolOptimal: boolean From 12bd1bc069a77d3989e41f86f81aead967c10e51 Mon Sep 17 00:00:00 2001 From: Dmitriy Pavlov Date: Thu, 10 Aug 2023 19:05:40 +0300 Subject: [PATCH 18/38] change type names --- package.json | 2 +- src/services/Aggregator/ws/index.ts | 2 +- src/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8cc0e0a..1303923 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.2-rc-1", + "version": "0.19.48-dev.2-rc-2", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index 9aed556..f6e6729 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -501,7 +501,7 @@ class AggregatorWS { path: json.ps, exchanges: json.e, exchangeContractPath: json.eps.map((path) => ({ - poolAddress: path.p, + pool: path.p, assetIn: path.ai, assetOut: path.ao, factory: path.f, diff --git a/src/types.ts b/src/types.ts index 3d8e434..78c6f8b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -165,7 +165,7 @@ export type SwapInfoAlternative = { } type ExchangeContractPath = { - poolAddress: string + pool: string assetIn: string assetOut: string factory: string From c118230818c60e4768673ac750f1c0ae95bc6394 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Mon, 21 Aug 2023 09:15:29 +0300 Subject: [PATCH 19/38] Convert ZeroAddress to WETH in path --- package.json | 2 +- src/Unit/Exchange/generateSwapCalldata.ts | 31 ++++++++++------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 187a515..4c48708 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.2", + "version": "0.19.48-dev.3", "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 15f7399..2d866c8 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -64,14 +64,11 @@ export default async function generateSwapCalldata({ const wethAddress = safeGet(unit.contracts, "WETH") const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry") const {assetToAddress, swapExecutorContractAddress, exchangeContractAddress} = await simpleFetch(unit.blockchainService.getInfo)(); - - const path = SafeArray.from(path_).map((swapInfo) => { - const swapInfoWithAddresses: SwapInfo = swapInfo - swapInfoWithAddresses.assetIn = safeGet(assetToAddress, swapInfo.assetIn); - swapInfoWithAddresses.assetOut = safeGet(assetToAddress, swapInfo.assetOut); - return swapInfoWithAddresses; - }); - + let path = SafeArray.from(path_).map((swapInfo) => { + swapInfo.assetIn = safeGet(assetToAddress, swapInfo.assetIn); + swapInfo.assetOut = safeGet(assetToAddress, swapInfo.assetOut); + return swapInfo; + }) const factory = path.first().factory if (!path.every(swapInfo => swapInfo.factory === factory)) { throw new Error(`Supporting only swaps with single factory`); @@ -86,6 +83,12 @@ export default async function generateSwapCalldata({ minReturnAmount: minReturnAmount, flags: 0 } + path = SafeArray.from(path_).map((swapInfo) => { + if (swapInfo.assetIn == ethers.constants.AddressZero) swapInfo.assetIn = wethAddress + if (swapInfo.assetOut == ethers.constants.AddressZero) swapInfo.assetOut = wethAddress + return swapInfo; + }); + let calldata: string switch (exchangeToType[factory]) { @@ -100,11 +103,11 @@ export default async function generateSwapCalldata({ break; } case "UniswapV3": { - calldata = await generateUni3Calls(amount, exchangeContractAddress, wethAddress, path, unit.provider) + calldata = await generateUni3Calls(amount, exchangeContractAddress, path, unit.provider) break; } case "OrionV3": { - calldata = await generateOrion3Calls(amount, exchangeContractAddress, wethAddress, path, unit.provider) + calldata = await generateOrion3Calls(amount, exchangeContractAddress, path, unit.provider) break; } case "Curve": { @@ -163,7 +166,6 @@ export async function generateUni2Calls( async function generateUni3Calls( amount: string, exchangeContractAddress: string, - weth: string, path: SafeArray, provider: ethers.providers.JsonRpcProvider ) { @@ -173,9 +175,6 @@ async function generateUni3Calls( const token0 = await pool.token0() const zeroForOne = token0 === swap.assetIn const unwrapWETH = swap.assetOut === ethers.constants.AddressZero - if (unwrapWETH) { - swap.assetOut = weth - } let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address]) encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) @@ -196,7 +195,6 @@ async function generateUni3Calls( async function generateOrion3Calls( amount: string, exchangeContractAddress: string, - weth: string, path: SafeArray, provider: ethers.providers.JsonRpcProvider ) { @@ -206,9 +204,6 @@ async function generateOrion3Calls( const token0 = await pool.token0() const zeroForOne = token0 === swap.assetIn const unwrapWETH = swap.assetOut === ethers.constants.AddressZero - if (unwrapWETH) { - swap.assetOut = weth - } let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address]) encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) From d4466492aa8c6312f113613747019fc2b8c527aa Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Tue, 22 Aug 2023 14:29:02 +0400 Subject: [PATCH 20/38] Fix Ethereum rpc address --- src/config/chains.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/chains.json b/src/config/chains.json index d5de68c..9f84f31 100644 --- a/src/config/chains.json +++ b/src/config/chains.json @@ -5,7 +5,7 @@ "label": "Ethereum Mainnet", "shortName": "ETH", "code": "eth", - "rpc": "https://trade.orionprotocol.io/rpc", + "rpc": "https://trade.orionprotocol.io/eth-mainnet/rpc", "baseCurrencyName": "ETH" }, "56": { @@ -116,4 +116,4 @@ "rpc": "testnet.1d.rip", "explorer": "https://explorer-testnet.1d.rip/" } -} +} \ No newline at end of file From 98d1d0f704bca52bccd61a735cc7f96c748f2333 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Tue, 22 Aug 2023 17:01:19 +0400 Subject: [PATCH 21/38] Fix RPCs --- src/config/chains.json | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/config/chains.json b/src/config/chains.json index 9f84f31..83eaa10 100644 --- a/src/config/chains.json +++ b/src/config/chains.json @@ -23,18 +23,9 @@ "label": "Binance Smart Chain Testnet", "shortName": "BSC-Testnet", "code": "bsc", - "rpc": "https://bsc-stage.node.orionprotocol.io/", + "rpc": "https://data-seed-prebsc-1-s1.bnbchain.org:8545/", "baseCurrencyName": "BNB" }, - "3": { - "chainId": "3", - "explorer": "https://ropsten.etherscan.io/", - "label": "Ropsten", - "shortName": "ETH-Ropsten", - "code": "eth", - "rpc": "https://testing.orionprotocol.io/eth-ropsten/rpc", - "baseCurrencyName": "ETH" - }, "5": { "chainId": "5", "explorer": "https://goerli.etherscan.io/", @@ -59,7 +50,7 @@ "label": "Fantom Testnet", "shortName": "FTM-Testnet", "code": "ftm", - "rpc": "https://testing.orionprotocol.io/ftm-testnet/rpc", + "rpc": "https://rpc.testnet.fantom.network/", "baseCurrencyName": "FTM" }, "250": { From 5c81ca55c763a0c3b2c2e3d5e6a412c639d6db21 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 15:44:20 +0400 Subject: [PATCH 22/38] makeAtomicSwap method in bridge --- package.json | 2 +- src/Orion/bridge/index.ts | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 32255a1..06ec8aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.57", + "version": "0.19.58", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index 3ecc29f..7e7a039 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -1,4 +1,4 @@ -import type { ethers } from 'ethers'; +import { ethers } from 'ethers'; import { type AtomicSwap, type SupportedChainId, type Unit, INTERNAL_PROTOCOL_PRECISION @@ -7,7 +7,10 @@ import getHistoryExt from './getHistory.js'; import swapExt from './swap.js'; import { BigNumber } from 'bignumber.js'; +import generateSecret from '../../utils/generateSecret.js'; +export const SECONDS_IN_DAY = 60 * 60 * 24; +export const EXPIRATION_DAYS = 4; export default class Bridge { constructor( private readonly unitsArray: Unit[], @@ -71,6 +74,32 @@ export default class Bridge { }); } + makeAtomicSwap = ( + walletAddress: string, + networkFrom: SupportedChainId, + networkTo: SupportedChainId, + amount: string, + asset: string, + env?: string | undefined, + ) => { + const secret = generateSecret(); + const secretHash = ethers.utils.keccak256(secret); + const lockExpiration = Date.now() + SECONDS_IN_DAY * EXPIRATION_DAYS * 1000; + + return { + sourceChainId: networkFrom, + targetChainId: networkTo, + amount, + walletAddress, + secret, + secretHash, + assetName: asset, + creationDate: Date.now(), + lockExpiration, + env, + }; + } + getHistory(address: string, limit = 1000) { return getHistoryExt(this.unitsArray, address, limit); } From 859cca326bdb102fb26082e0a08eef78657c9fea Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 16:26:24 +0400 Subject: [PATCH 23/38] Fix syntax --- package.json | 2 +- src/Orion/bridge/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 06ec8aa..1cd2a78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.58", + "version": "0.19.59", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index 7e7a039..c539a53 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -74,14 +74,14 @@ export default class Bridge { }); } - makeAtomicSwap = ( + makeAtomicSwap( walletAddress: string, networkFrom: SupportedChainId, networkTo: SupportedChainId, amount: string, asset: string, env?: string | undefined, - ) => { + ) { const secret = generateSecret(); const secretHash = ethers.utils.keccak256(secret); const lockExpiration = Date.now() + SECONDS_IN_DAY * EXPIRATION_DAYS * 1000; From 35713407a7aff7275f826dc35102c1e872f1bab8 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 17:46:43 +0400 Subject: [PATCH 24/38] Added combineLocalAndExternalData to bridge --- package.json | 2 +- src/Orion/bridge/getHistory.ts | 12 +-- src/Orion/bridge/index.ts | 172 ++++++++++++++++++++++++++++++++- src/types.ts | 79 +++++++++++---- src/utils/invariant.ts | 18 ++++ 5 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 src/utils/invariant.ts diff --git a/package.json b/package.json index 1cd2a78..a7bb329 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.59", + "version": "0.19.60", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/getHistory.ts b/src/Orion/bridge/getHistory.ts index b0cc1db..402a163 100644 --- a/src/Orion/bridge/getHistory.ts +++ b/src/Orion/bridge/getHistory.ts @@ -130,19 +130,19 @@ const getHistory = async (units: Unit[], address: string, limit = 1000) => { asset: string sender: string secretHash: string - receiver: string | undefined - secret: string | undefined + receiver?: string | undefined + secret?: string | undefined timestamp: TargetItem['timestamp'] & SourceItem['timestamp'] expiration: TargetItem['expiration'] & SourceItem['expiration'] transactions: TargetItem['transactions'] & SourceItem['transactions'] - lockOrder: AggItem['lockOrder'] | undefined - redeemOrder: AggItem['redeemOrder'] | undefined + lockOrder?: AggItem['lockOrder'] | undefined + redeemOrder?: AggItem['redeemOrder'] | undefined amountToReceive: SourceItem['amountToReceive'] amountToSpend: SourceItem['amountToSpend'] status: { source: SourceItem['state'] - target: TargetItem['state'] | undefined - aggregator: AggItem['status'] | undefined + target?: TargetItem['state'] | undefined + aggregator?: AggItem['status'] | undefined } } diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index c539a53..8dce712 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -1,16 +1,43 @@ import { ethers } from 'ethers'; -import { - type AtomicSwap, type SupportedChainId, - type Unit, INTERNAL_PROTOCOL_PRECISION +import type { + Orion, Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo } from '../../index.js'; +import { INTERNAL_PROTOCOL_PRECISION, TxStatus, TxType } from '../../index.js'; import getHistoryExt from './getHistory.js'; import swapExt from './swap.js'; import { BigNumber } from 'bignumber.js'; import generateSecret from '../../utils/generateSecret.js'; +import { isPresent } from 'ts-is-present'; +import { invariant } from '../../utils/invariant.js'; export const SECONDS_IN_DAY = 60 * 60 * 24; export const EXPIRATION_DAYS = 4; + +type BridgeHistory = Awaited>; + +type BridgeHistoryItem = NonNullable; + +export type AtomicSwap = Partial< + Omit +> & Partial< + Omit +> & { + sourceChainId: SupportedChainId + targetChainId: SupportedChainId + lockExpiration: number + secretHash: string + walletAddress: string + secret?: string | undefined + + creationDate?: number | undefined + redeemExpired?: boolean | undefined + + lockTx?: TransactionInfo | undefined + redeemTx?: TransactionInfo | undefined + refundTx?: TransactionInfo | undefined + liquidityMigrationTx?: TransactionInfo | undefined +} export default class Bridge { constructor( private readonly unitsArray: Unit[], @@ -74,6 +101,145 @@ export default class Bridge { }); } + async combineLocalAndExternalData( + walletAddress: string, + localAtomicSwaps: AtomicSwapLocal[], + transactions: TransactionInfo[], + ) { + // Prepare transactions data + const byTxHashMap = new Map(); + type BridgeTxs = { + lockTx?: TransactionInfo | undefined + redeemTx?: TransactionInfo | undefined + refundTx?: TransactionInfo | undefined + }; + const bySecretHashMap = new Map(); + transactions.forEach((tx) => { + if (tx.hash !== undefined) byTxHashMap.set(tx.hash, tx); + if (tx.payload) { + const { type, data } = tx.payload; + if (type === TxType.BRIDGE_LOCK || type === TxType.BRIDGE_REDEEM || type === TxType.BRIDGE_REFUND) { + const item = bySecretHashMap.get(data.secretHash); + bySecretHashMap.set(data.secretHash, { + ...item, + lockTx: type === TxType.BRIDGE_LOCK ? tx : item?.lockTx, + redeemTx: type === TxType.BRIDGE_REDEEM ? tx : item?.redeemTx, + refundTx: type === TxType.BRIDGE_REFUND ? tx : item?.refundTx, + }); + } + } + }); + + // Combine local data and external data + const bridgeHistory = await this.getHistory(walletAddress); + const atomicSwapsMap = new Map(); + Object.values(bridgeHistory) + .filter(isPresent) + .forEach((atomicHistoryItem) => { + const { lock, redeem, refund } = atomicHistoryItem.transactions ?? {}; + const lockTx = lock !== undefined + ? { + hash: lock, + status: TxStatus.SETTLED, + } + : undefined; + const redeemTx = redeem !== undefined + ? { + hash: redeem, + status: TxStatus.SETTLED, + } + : undefined; + const refundTx = refund !== undefined + ? { + hash: refund, + status: TxStatus.SETTLED, + } + : undefined; + + let redeemExpired = false; + + // If redeem order is expired + if (atomicHistoryItem.redeemOrder) { + const currentTime = Date.now(); + if (currentTime > atomicHistoryItem.redeemOrder.expiration) redeemExpired = true; + } + + // const assetName = combinedAddressToAsset[atomicHistoryItem.asset]?.[atomicHistoryItem.sourceChainId]; + const assetName = 'asdf'; + + const amount = atomicHistoryItem.amountToReceive ?? atomicHistoryItem.amountToSpend; + + invariant(atomicHistoryItem.expiration?.lock, 'Lock expiration must be defined'); + atomicSwapsMap.set(atomicHistoryItem.secretHash, { + ...atomicHistoryItem, + walletAddress: atomicHistoryItem.sender, + creationDate: atomicHistoryItem.creationDate.getTime(), + assetName, + lockTx, + amount: amount !== undefined ? amount.toString() : undefined, + redeemTx, + refundTx, + lockExpiration: atomicHistoryItem.expiration.lock, + redeemExpired, + }); + }); + localAtomicSwaps.forEach((atomic) => { + const atomicInMap = atomicSwapsMap.get(atomic.secretHash); + + const { liquidityMigrationTxHash, redeemSettlement, secretHash } = atomic; + + const secretHashTxs = bySecretHashMap.get(secretHash); + let redeemTx: TransactionInfo | undefined; + if (redeemSettlement) { + if (redeemSettlement.type === 'own_tx') { + redeemTx = secretHashTxs?.redeemTx; + } else if (redeemSettlement.result) { + redeemTx = { + status: redeemSettlement.result.value === 'success' ? TxStatus.SETTLED : TxStatus.FAILED, + } + } else if (redeemSettlement.requestedAt !== undefined) { + redeemTx = { + status: TxStatus.PENDING, + } + } + } + const liquidityMigrationTx = liquidityMigrationTxHash !== undefined ? byTxHashMap.get(liquidityMigrationTxHash) : undefined; + const amount = atomic.amount !== undefined + ? new BigNumber(atomic.amount).div(10 ** INTERNAL_PROTOCOL_PRECISION).toString() + : undefined; + if (atomicInMap) { // Merge local and backend data + atomicSwapsMap.set(atomic.secretHash, { + ...atomicInMap, + ...atomic, + lockExpiration: atomicInMap.lockExpiration, + targetChainId: atomicInMap.targetChainId, + sourceChainId: atomicInMap.sourceChainId, + amount: atomicInMap.amount ?? amount, + lockTx: atomicInMap.lockTx ?? secretHashTxs?.lockTx, + redeemTx: atomicInMap.redeemTx ?? redeemTx, + refundTx: atomicInMap.refundTx ?? secretHashTxs?.refundTx, + liquidityMigrationTx: atomicInMap.liquidityMigrationTx ?? liquidityMigrationTx, + }); + } else { + invariant(atomic.targetChainId, 'Target chain id is not defined'); + invariant(atomic.sourceChainId, 'Source chain id is not defined'); + invariant(atomic.lockExpiration, 'Lock expiration is not defined'); + + atomicSwapsMap.set(atomic.secretHash, { + ...atomic, + sourceChainId: atomic.sourceChainId, + targetChainId: atomic.targetChainId, + lockExpiration: atomic.lockExpiration, + lockTx: secretHashTxs?.lockTx, + redeemTx, + refundTx: secretHashTxs?.refundTx, + liquidityMigrationTx, + }); + } + }); + return atomicSwapsMap; + } + makeAtomicSwap( walletAddress: string, networkFrom: SupportedChainId, diff --git a/src/types.ts b/src/types.ts index 0c19b6c..db51432 100644 --- a/src/types.ts +++ b/src/types.ts @@ -283,36 +283,73 @@ export type RedeemOrder = { claimReceiver: string } -export type AtomicSwap = { +export interface AtomicSwapLocal { secret: string secretHash: string - walletAddress: string env?: string | undefined - sourceNetwork?: SupportedChainId - targetNetwork?: SupportedChainId + sourceChainId?: SupportedChainId | undefined + targetChainId?: SupportedChainId | undefined - amount?: string - asset?: string + amount?: string | undefined + assetName?: string | undefined - creationDate?: number - expiration?: number + liquidityMigrationTxHash?: string | undefined + lockTransactionHash?: string | undefined + refundTransactionHash?: string | undefined - lockTransactionHash?: string - redeemTransactionHash?: string - refundTransactionHash?: string - liquidityMigrationTxHash?: string - - redeemOrder?: RedeemOrder + creationDate?: number | undefined + lockExpiration?: number | undefined + placingOrderError?: string | undefined + redeemSettlement?: { + type: 'own_tx' + } | { + type: 'orion_tx' + requestedAt?: number + result?: { + timestamp: number + value: 'success' | 'failed' + } + } | undefined } -export type ExternalStorage = { - bridge: { - getAtomicSwaps: () => AtomicSwap[] - setAtomicSwaps: (atomics: AtomicSwap[]) => void - addAtomicSwap: (atomic: AtomicSwap) => void - updateAtomicSwap: (secretHash: string, atomic: Partial) => void - removeAtomicSwaps: (secretHashes: string[]) => void +export enum TxStatus { + PENDING = 'pending', + FAILED = 'failed', + SETTLED = 'settled', +} + +export enum TxType { + BRIDGE_LOCK = 'BRIDGE_LOCK', + BRIDGE_REDEEM = 'BRIDGE_REDEEM', + BRIDGE_REFUND = 'BRIDGE_REFUND', +} + +type BridgeRedeemTxPayload = { + type: TxType.BRIDGE_REDEEM + data: { + secretHash: string } } + +type BridgeLockTxPayload = { + type: TxType.BRIDGE_LOCK + data: { + secretHash: string + } +} + +type BridgeRefundTxPayload = { + type: TxType.BRIDGE_REFUND + data: { + secretHash: string + } +} + +export type TransactionInfo = { + id?: string + status?: TxStatus + hash?: string + payload?: BridgeLockTxPayload | BridgeRedeemTxPayload | BridgeRefundTxPayload +} diff --git a/src/utils/invariant.ts b/src/utils/invariant.ts new file mode 100644 index 0000000..d637c9f --- /dev/null +++ b/src/utils/invariant.ts @@ -0,0 +1,18 @@ +export function invariant( + condition: T, + errorMessage?: ((condition: T) => string) | string, +): asserts condition { + if (condition) { + return; + } + + if (typeof errorMessage === 'undefined') { + throw new Error('Invariant failed'); + } + + if (typeof errorMessage === 'string') { + throw new Error(errorMessage); + } + + throw new Error(errorMessage(condition)); +} From f5a21ced67a65db5fa82f47099f9c10e4966b784 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 18:03:32 +0400 Subject: [PATCH 25/38] Fix types --- package.json | 2 +- src/Orion/bridge/index.ts | 26 +------------------------- src/types.ts | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index a7bb329..2fd1522 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.60", + "version": "0.19.61", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index 8dce712..d34f93a 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import type { - Orion, Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo + Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo, AtomicSwap } from '../../index.js'; import { INTERNAL_PROTOCOL_PRECISION, TxStatus, TxType } from '../../index.js'; import getHistoryExt from './getHistory.js'; @@ -14,30 +14,6 @@ import { invariant } from '../../utils/invariant.js'; export const SECONDS_IN_DAY = 60 * 60 * 24; export const EXPIRATION_DAYS = 4; -type BridgeHistory = Awaited>; - -type BridgeHistoryItem = NonNullable; - -export type AtomicSwap = Partial< - Omit -> & Partial< - Omit -> & { - sourceChainId: SupportedChainId - targetChainId: SupportedChainId - lockExpiration: number - secretHash: string - walletAddress: string - secret?: string | undefined - - creationDate?: number | undefined - redeemExpired?: boolean | undefined - - lockTx?: TransactionInfo | undefined - redeemTx?: TransactionInfo | undefined - refundTx?: TransactionInfo | undefined - liquidityMigrationTx?: TransactionInfo | undefined -} export default class Bridge { constructor( private readonly unitsArray: Unit[], diff --git a/src/types.ts b/src/types.ts index db51432..0494c39 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import type { BigNumber } from 'bignumber.js'; import type subOrderStatuses from './constants/subOrderStatuses.js'; import type positionStatuses from './constants/positionStatuses.js'; import type { knownEnvs } from './config/schemas/index.js'; +import type getHistory from './Orion/bridge/getHistory.js'; export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; @@ -315,9 +316,16 @@ export interface AtomicSwapLocal { } export enum TxStatus { + QUEUED = 'queued', + SIGN_FAILED = 'sign_failed', + GAS_ESTIMATING = 'gas_estimating', + ESTIMATE_GAS_FAILED = 'estimate_gas_failed', + CANCELLED = 'cancelled', PENDING = 'pending', FAILED = 'failed', SETTLED = 'settled', + SIGNING = 'signing', + UNKNOWN = 'unknown', } export enum TxType { @@ -353,3 +361,28 @@ export type TransactionInfo = { hash?: string payload?: BridgeLockTxPayload | BridgeRedeemTxPayload | BridgeRefundTxPayload } + +type BridgeHistory = Awaited>; + +type BridgeHistoryItem = NonNullable; + +export type AtomicSwap = Partial< + Omit +> & Partial< + Omit +> & { + sourceChainId: SupportedChainId + targetChainId: SupportedChainId + lockExpiration: number + secretHash: string + walletAddress: string + secret?: string | undefined + + creationDate?: number | undefined + redeemExpired?: boolean | undefined + + lockTx?: TransactionInfo | undefined + redeemTx?: TransactionInfo | undefined + refundTx?: TransactionInfo | undefined + liquidityMigrationTx?: TransactionInfo | undefined +} From 9e0325b7267587f7a79e241911f8fccd977ef5d6 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 18:19:22 +0400 Subject: [PATCH 26/38] Added more tx types --- package.json | 2 +- src/Orion/bridge/index.ts | 4 +-- src/types.ts | 62 +++++++++++++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 2fd1522..e0a017f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.61", + "version": "0.19.62", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index d34f93a..b9b7009 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -3,7 +3,7 @@ import type { Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo, AtomicSwap } from '../../index.js'; import { INTERNAL_PROTOCOL_PRECISION, TxStatus, TxType } from '../../index.js'; -import getHistoryExt from './getHistory.js'; +import getHistory from './getHistory.js'; import swapExt from './swap.js'; import { BigNumber } from 'bignumber.js'; @@ -243,7 +243,7 @@ export default class Bridge { } getHistory(address: string, limit = 1000) { - return getHistoryExt(this.unitsArray, address, limit); + return getHistory(this.unitsArray, address, limit); } swap( diff --git a/src/types.ts b/src/types.ts index 0494c39..828fc2a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -329,29 +329,69 @@ export enum TxStatus { } export enum TxType { + SWAP_THROUGH_ORION_POOL = 'SWAP_THROUGH_ORION_POOL', + DEPOSIT = 'DEPOSIT', + WITHDRAW = 'WITHDRAW', BRIDGE_LOCK = 'BRIDGE_LOCK', BRIDGE_REDEEM = 'BRIDGE_REDEEM', BRIDGE_REFUND = 'BRIDGE_REFUND', + LIQUIDITY_MIGRATION = 'LIQUIDITY_MIGRATION', + REDEEM_TWO_ATOMICS = 'REDEEM_TWO_ATOMICS', } -type BridgeRedeemTxPayload = { - type: TxType.BRIDGE_REDEEM +export type TxDepositOrWithdrawPayload = { + type: TxType.DEPOSIT | TxType.WITHDRAW + data: { + asset: string + amount: string + } +}; + +export type TxSwapThroughOrionPoolPayload = { + type: TxType.SWAP_THROUGH_ORION_POOL + data: { + side: 'buy' | 'sell' + assetIn: string + assetOut: string + amount: string + price: string + } +}; + +type TxBridgePayload = { + type: TxType.BRIDGE_LOCK | TxType.BRIDGE_REDEEM | TxType.BRIDGE_REFUND data: { secretHash: string } } -type BridgeLockTxPayload = { - type: TxType.BRIDGE_LOCK +type TxLiquidityMigrationPayload = { + type: TxType.LIQUIDITY_MIGRATION data: { - secretHash: string + source: SupportedChainId + target: SupportedChainId + pair: string + pairAddress: string + assetA: { + amount: string + secretHash: string + secret: string + } + assetB: { + amount: string + secretHash: string + secret: string + } + expiration: number + env?: string } } -type BridgeRefundTxPayload = { - type: TxType.BRIDGE_REFUND +type TxRedeemTwoAtomicsPayload = { + type: TxType.REDEEM_TWO_ATOMICS data: { - secretHash: string + secretHash1: string + secretHash2: string } } @@ -359,7 +399,11 @@ export type TransactionInfo = { id?: string status?: TxStatus hash?: string - payload?: BridgeLockTxPayload | BridgeRedeemTxPayload | BridgeRefundTxPayload + payload?: TxDepositOrWithdrawPayload + | TxSwapThroughOrionPoolPayload + | TxBridgePayload + | TxLiquidityMigrationPayload + | TxRedeemTwoAtomicsPayload } type BridgeHistory = Awaited>; From 44efdd08a9cb4d182119ad625d79253319b94bd4 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 18:27:22 +0400 Subject: [PATCH 27/38] Export one more types --- package.json | 2 +- src/types.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e0a017f..5e4d05d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.62", + "version": "0.19.63", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/types.ts b/src/types.ts index 828fc2a..0314e1c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -358,14 +358,14 @@ export type TxSwapThroughOrionPoolPayload = { } }; -type TxBridgePayload = { +export type TxBridgePayload = { type: TxType.BRIDGE_LOCK | TxType.BRIDGE_REDEEM | TxType.BRIDGE_REFUND data: { secretHash: string } } -type TxLiquidityMigrationPayload = { +export type TxLiquidityMigrationPayload = { type: TxType.LIQUIDITY_MIGRATION data: { source: SupportedChainId @@ -387,7 +387,7 @@ type TxLiquidityMigrationPayload = { } } -type TxRedeemTwoAtomicsPayload = { +export type TxRedeemTwoAtomicsPayload = { type: TxType.REDEEM_TWO_ATOMICS data: { secretHash1: string From 2256c9af9b7ff191072c94e44042c2e544965b05 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 18:43:31 +0400 Subject: [PATCH 28/38] Added combinedAddressToAsset param --- package.json | 2 +- src/Orion/bridge/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5e4d05d..06f36e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.63", + "version": "0.19.64", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index b9b7009..80715e2 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -81,6 +81,7 @@ export default class Bridge { walletAddress: string, localAtomicSwaps: AtomicSwapLocal[], transactions: TransactionInfo[], + combinedAddressToAsset: Partial>>>, ) { // Prepare transactions data const byTxHashMap = new Map(); @@ -140,8 +141,7 @@ export default class Bridge { if (currentTime > atomicHistoryItem.redeemOrder.expiration) redeemExpired = true; } - // const assetName = combinedAddressToAsset[atomicHistoryItem.asset]?.[atomicHistoryItem.sourceChainId]; - const assetName = 'asdf'; + const assetName = combinedAddressToAsset[atomicHistoryItem.asset]?.[atomicHistoryItem.sourceChainId]; const amount = atomicHistoryItem.amountToReceive ?? atomicHistoryItem.amountToSpend; From 837a11a56b6f07dce0898ce401a83cdd5133ab44 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 18:59:44 +0400 Subject: [PATCH 29/38] Added registerRedeemOrder to bridge --- package.json | 2 +- src/Orion/bridge/index.ts | 92 +++++++++++++++------------------------ 2 files changed, 36 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 06f36e2..d149879 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.64", + "version": "0.19.65", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index 80715e2..8a2c0b3 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import type { - Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo, AtomicSwap + Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo, AtomicSwap, RedeemOrder } from '../../index.js'; import { INTERNAL_PROTOCOL_PRECISION, TxStatus, TxType } from '../../index.js'; import getHistory from './getHistory.js'; @@ -14,67 +14,36 @@ import { invariant } from '../../utils/invariant.js'; export const SECONDS_IN_DAY = 60 * 60 * 24; export const EXPIRATION_DAYS = 4; +type ExternalAtomicsData = Awaited>; export default class Bridge { + readonly EXTERNAL_ATOMICS_DATA_CACHE_TIME = 5 * 1000; // 5 seconds + private externalAtomicSwaps: Partial data + lastUpdate: number + data: ExternalAtomicsData + }>> = {}; + constructor( private readonly unitsArray: Unit[], ) {} - async getMergedHistory( - externalStoredAtomicSwaps: AtomicSwap[], - walletAddress: string, + registerRedeemOrder( + secretHash: string, + redeemOrder: RedeemOrder, ) { - const bridgeHistory = await this.getHistory(walletAddress); - - return Object.values(bridgeHistory).map((atomicSwap) => { - if (atomicSwap === undefined) throw new Error('No atomic swap'); - - const { - secretHash, - amountToReceive, - amountToSpend, - targetChainId, - asset, - sourceChainId, - sender, - transactions, - expiration, - creationDate, - } = atomicSwap; - - const localSwap = externalStoredAtomicSwaps.find( - (swap) => secretHash === swap.secretHash, - ); - - const amount = amountToReceive ?? amountToSpend ?? 0; - - // Checking if transaction hash from blockchain is different from the same in local storage - // and changing it to the correct one - - let assetName = asset; - - // LEGACY. Some old atomic swaps have address instead of asset name. Here we handle this case - if (asset.includes('0x')) { - assetName = '—'; // We don't want to display address even if we can't find asset name + const senderCached = this.externalAtomicSwaps[redeemOrder.sender]; + const receiverCached = this.externalAtomicSwaps[redeemOrder.receiver]; + if (senderCached !== undefined) { + const atomic = senderCached.data[secretHash]; + if (atomic !== undefined) { + atomic.redeemOrder = redeemOrder; } - - return { - localSwap, - sourceNetwork: sourceChainId, - targetNetwork: targetChainId, - amount: new BigNumber(amount) - .multipliedBy(new BigNumber(10).pow(INTERNAL_PROTOCOL_PRECISION)) - .toString(), - walletAddress: sender, - secretHash, - lockTransactionHash: transactions?.lock, - refundTransactionHash: transactions?.refund, - asset: assetName, - expiration: - expiration?.lock ?? creationDate.getTime() + 60 * 60 * 24 * 4, // Or default 4 days - creationDate: creationDate.getTime(), - redeemOrder: atomicSwap.redeemOrder, - }; - }); + } + if (receiverCached !== undefined) { + const atomic = receiverCached.data[secretHash]; + if (atomic !== undefined) { + atomic.redeemOrder = redeemOrder; + } + } } async combineLocalAndExternalData( @@ -242,8 +211,17 @@ export default class Bridge { }; } - getHistory(address: string, limit = 1000) { - return getHistory(this.unitsArray, address, limit); + async getHistory(address: string, limit = 1000) { + const cached = this.externalAtomicSwaps[address]; + if (cached !== undefined && Date.now() - cached.lastUpdate < this.EXTERNAL_ATOMICS_DATA_CACHE_TIME) { + return cached.data; + } + const data = await getHistory(this.unitsArray, address, limit); + this.externalAtomicSwaps[address] = { + lastUpdate: Date.now(), + data, + }; + return data; } swap( From 0148be2e132874c40365452bd7902de5b3318d0d Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 19:31:54 +0400 Subject: [PATCH 30/38] Fix cache --- package.json | 2 +- src/Orion/bridge/index.ts | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d149879..e76b4a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.65", + "version": "0.19.66", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index 8a2c0b3..15fba2f 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -16,7 +16,7 @@ export const EXPIRATION_DAYS = 4; type ExternalAtomicsData = Awaited>; export default class Bridge { - readonly EXTERNAL_ATOMICS_DATA_CACHE_TIME = 5 * 1000; // 5 seconds + readonly EXTERNAL_ATOMICS_DATA_CACHE_TIME_MS = 5 * 1000; // 5 seconds private externalAtomicSwaps: Partial data lastUpdate: number data: ExternalAtomicsData @@ -212,15 +212,20 @@ export default class Bridge { } async getHistory(address: string, limit = 1000) { - const cached = this.externalAtomicSwaps[address]; - if (cached !== undefined && Date.now() - cached.lastUpdate < this.EXTERNAL_ATOMICS_DATA_CACHE_TIME) { - return cached.data; + const cachedData = this.externalAtomicSwaps[address]; + let data: ExternalAtomicsData | undefined; + if (cachedData !== undefined) { + const cacheIsExpired = cachedData.lastUpdate + this.EXTERNAL_ATOMICS_DATA_CACHE_TIME_MS < Date.now(); + if (!cacheIsExpired) data = cachedData.data; + } + + if (data === undefined) { + data = await getHistory(this.unitsArray, address, limit); + this.externalAtomicSwaps[address] = { + lastUpdate: Date.now(), + data, + }; } - const data = await getHistory(this.unitsArray, address, limit); - this.externalAtomicSwaps[address] = { - lastUpdate: Date.now(), - data, - }; return data; } From 9e26727400837ad3501a8239058f3b159a58cbd7 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 21:06:33 +0400 Subject: [PATCH 31/38] State optional --- package.json | 2 +- src/Orion/bridge/getHistory.ts | 2 +- src/services/BlockchainService/schemas/atomicHistorySchema.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e76b4a5..d95faaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.66", + "version": "0.19.67", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/getHistory.ts b/src/Orion/bridge/getHistory.ts index 402a163..0cce384 100644 --- a/src/Orion/bridge/getHistory.ts +++ b/src/Orion/bridge/getHistory.ts @@ -140,7 +140,7 @@ const getHistory = async (units: Unit[], address: string, limit = 1000) => { amountToReceive: SourceItem['amountToReceive'] amountToSpend: SourceItem['amountToSpend'] status: { - source: SourceItem['state'] + source?: SourceItem['state'] | undefined target?: TargetItem['state'] | undefined aggregator?: AggItem['status'] | undefined } diff --git a/src/services/BlockchainService/schemas/atomicHistorySchema.ts b/src/services/BlockchainService/schemas/atomicHistorySchema.ts index afe4d40..504145b 100644 --- a/src/services/BlockchainService/schemas/atomicHistorySchema.ts +++ b/src/services/BlockchainService/schemas/atomicHistorySchema.ts @@ -34,7 +34,7 @@ const sourceAtomicHistorySchemaItem = baseAtomicHistoryItem.extend({ expiration: z.object({ lock: z.number().optional(), }).optional(), - state: z.enum(['BEFORE-LOCK', 'LOCKED', 'REFUNDED', 'CLAIMED']), + state: z.enum(['BEFORE-LOCK', 'LOCKED', 'REFUNDED', 'CLAIMED']).optional(), targetChainId: z.number(), transactions: z.object({ lock: z.string().optional(), @@ -51,7 +51,7 @@ const targetAtomicHistorySchemaItem = baseAtomicHistoryItem.extend({ expiration: z.object({ redeem: z.number().optional(), }).optional(), - state: z.enum(['BEFORE-REDEEM', 'REDEEMED']), + state: z.enum(['BEFORE-REDEEM', 'REDEEMED']).optional(), transactions: z.object({ redeem: z.string().optional(), }).optional(), From 6d13e2d162314b7c427233e162f29750f098f807 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 22:10:53 +0400 Subject: [PATCH 32/38] Bridge: addressToAsset --- package.json | 2 +- src/Orion/bridge/index.ts | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d95faaa..c7937e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.67", + "version": "0.19.68", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index 15fba2f..ccadd14 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -10,6 +10,7 @@ import { BigNumber } from 'bignumber.js'; import generateSecret from '../../utils/generateSecret.js'; import { isPresent } from 'ts-is-present'; import { invariant } from '../../utils/invariant.js'; +import { simpleFetch } from 'simple-typed-fetch'; export const SECONDS_IN_DAY = 60 * 60 * 24; export const EXPIRATION_DAYS = 4; @@ -22,6 +23,12 @@ export default class Bridge { data: ExternalAtomicsData }>> = {}; + readonly ADDRESS_TO_ASSET_CACHE_TIME_MS = 5 * 60 * 1000; // 5 minutes + private addressToAsset: { + lastUpdate: number + data: Partial>>> + } = { lastUpdate: 0, data: {} }; + constructor( private readonly unitsArray: Unit[], ) {} @@ -46,11 +53,38 @@ export default class Bridge { } } + async getCombinedAddressToAsset() { + const { lastUpdate, data } = this.addressToAsset; + const cacheIsExpired = lastUpdate + this.EXTERNAL_ATOMICS_DATA_CACHE_TIME_MS < Date.now(); + if (!cacheIsExpired) return data; + const addressToAssetData: Partial>>> = {}; + await Promise.all(this.unitsArray.map(async (unit) => { + const { blockchainService, chainId } = unit; + const { assetToAddress } = await simpleFetch(blockchainService.getInfo)(); + Object.entries(assetToAddress).forEach(([asset, address]) => { + if (address !== undefined) { + const assetRecord = addressToAssetData[address]; + if (assetRecord !== undefined) { + assetRecord[chainId] = asset; + } else { + addressToAssetData[address] = { + [chainId]: asset, + }; + } + } + }); + })); + this.addressToAsset = { + lastUpdate: Date.now(), + data: addressToAssetData, + } + return addressToAssetData; + } + async combineLocalAndExternalData( walletAddress: string, localAtomicSwaps: AtomicSwapLocal[], transactions: TransactionInfo[], - combinedAddressToAsset: Partial>>>, ) { // Prepare transactions data const byTxHashMap = new Map(); @@ -78,6 +112,7 @@ export default class Bridge { // Combine local data and external data const bridgeHistory = await this.getHistory(walletAddress); + const combinedAddressToAsset = await this.getCombinedAddressToAsset(); const atomicSwapsMap = new Map(); Object.values(bridgeHistory) .filter(isPresent) From 7416a6cfbfc86d433f142fd905744edfba2494bd Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Wed, 23 Aug 2023 22:06:44 +0300 Subject: [PATCH 33/38] Correct exchange precision --- package.json | 2 +- src/Unit/Exchange/generateSwapCalldata.ts | 33 ++++++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index d74e240..e5727c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.4", + "version": "0.19.48-dev.5", "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 2d866c8..5f64878 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -1,10 +1,11 @@ import type { ExchangeWithGenericSwap } from '@orionprotocol/contracts/lib/ethers-v5/Exchange.js'; import { UniswapV3Pool__factory, ERC20__factory, SwapExecutor__factory, CurveRegistry__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js'; -import { BigNumber, ethers } from 'ethers'; +import { BigNumber, ethers, type BigNumberish } from 'ethers'; import { concat, defaultAbiCoder, type BytesLike } from 'ethers/lib/utils.js'; import { safeGet, SafeArray } from '../../utils/safeGetters.js'; import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; +import type { PromiseOrValue } from '@orionprotocol/contracts/lib/ethers-v5/common.js'; const EXECUTOR_SWAP_FUNCTION = "func_70LYiww" @@ -43,8 +44,8 @@ export type CallParams = { } export type GenerateSwapCalldataParams = { - amount: string, - minReturnAmount: string, + amount: BigNumberish, + minReturnAmount: BigNumberish, receiverAddress: string, path: ArrayLike, unit: Unit @@ -73,7 +74,7 @@ export default async function generateSwapCalldata({ if (!path.every(swapInfo => swapInfo.factory === factory)) { throw new Error(`Supporting only swaps with single factory`); } - + const swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct = { srcToken: path.first().assetIn, dstToken: path.last().assetOut, @@ -83,6 +84,18 @@ export default async function generateSwapCalldata({ minReturnAmount: minReturnAmount, flags: 0 } + + const exchangeToNativeDecimals = async (token: PromiseOrValue) => { + token = await token + let decimals = 18 + if (token !== ethers.constants.AddressZero) { + const contract = ERC20__factory.connect(token, unit.provider) + decimals = await contract.decimals() + } + return BigNumber.from(amount).mul(BigNumber.from(10).pow(decimals)).div(BigNumber.from(10).pow(8)) + } + const amountNativeDecimals = await exchangeToNativeDecimals(swapDescription.srcToken); + path = SafeArray.from(path_).map((swapInfo) => { if (swapInfo.assetIn == ethers.constants.AddressZero) swapInfo.assetIn = wethAddress if (swapInfo.assetOut == ethers.constants.AddressZero) swapInfo.assetOut = wethAddress @@ -103,16 +116,16 @@ export default async function generateSwapCalldata({ break; } case "UniswapV3": { - calldata = await generateUni3Calls(amount, exchangeContractAddress, path, unit.provider) + calldata = await generateUni3Calls(amountNativeDecimals, exchangeContractAddress, path, unit.provider) break; } case "OrionV3": { - calldata = await generateOrion3Calls(amount, exchangeContractAddress, path, unit.provider) + calldata = await generateOrion3Calls(amountNativeDecimals, exchangeContractAddress, path, unit.provider) break; } case "Curve": { calldata = await generateCurveStableSwapCalls( - amount, + amountNativeDecimals, exchangeContractAddress, swapExecutorContractAddress, path, @@ -164,7 +177,7 @@ export async function generateUni2Calls( } async function generateUni3Calls( - amount: string, + amount: BigNumberish, exchangeContractAddress: string, path: SafeArray, provider: ethers.providers.JsonRpcProvider @@ -193,7 +206,7 @@ async function generateUni3Calls( } async function generateOrion3Calls( - amount: string, + amount: BigNumberish, exchangeContractAddress: string, path: SafeArray, provider: ethers.providers.JsonRpcProvider @@ -222,7 +235,7 @@ async function generateOrion3Calls( } async function generateCurveStableSwapCalls( - amount: string, + amount: BigNumberish, exchangeContractAddress: string, executorAddress: string, path: SafeArray, From 64b61db6a96486b0689f911f225b65db9b40ebbc Mon Sep 17 00:00:00 2001 From: Dmitriy Pavlov Date: Thu, 24 Aug 2023 10:36:48 +0300 Subject: [PATCH 34/38] update deps --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bba9579..ccf76bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.5-rc0", + "version": "0.19.48-dev.6-rc-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.5-rc0", + "version": "0.19.48-dev.6-rc-0", "hasInstallScript": true, "license": "ISC", "dependencies": { From 7671c41013ff524e671681302d295d81f4f8adbd Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 24 Aug 2023 12:33:37 +0300 Subject: [PATCH 35/38] case insensitive address compare --- src/Unit/Exchange/generateSwapCalldata.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index 5f64878..39b14c0 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -186,7 +186,7 @@ async function generateUni3Calls( for (const swap of path) { const pool = UniswapV3Pool__factory.connect(swap.pool, provider) const token0 = await pool.token0() - const zeroForOne = token0 === swap.assetIn + const zeroForOne = token0.toLowerCase() === swap.assetIn.toLowerCase() const unwrapWETH = swap.assetOut === ethers.constants.AddressZero let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address]) @@ -215,7 +215,7 @@ async function generateOrion3Calls( for (const swap of path) { const pool = UniswapV3Pool__factory.connect(swap.pool, provider) const token0 = await pool.token0() - const zeroForOne = token0 === swap.assetIn + const zeroForOne = token0.toLowerCase() === swap.assetIn.toLowerCase() const unwrapWETH = swap.assetOut === ethers.constants.AddressZero let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address]) @@ -288,21 +288,21 @@ function addCallParams( let firstByte = 0 if (callParams) { if (callParams.value !== undefined) { - firstByte += 16 // 00000010 + firstByte += 16 // 00010000 const encodedValue = ethers.utils.solidityPack(["uint128"], [callParams.value]) calldata = ethers.utils.hexlify(ethers.utils.concat([encodedValue, calldata])) } if (callParams.target !== undefined) { - firstByte += 32 // 00000100 + firstByte += 32 // 00100000 const encodedAddress = ethers.utils.solidityPack(["address"], [callParams.target]) calldata = ethers.utils.hexlify(ethers.utils.concat([encodedAddress, calldata])) } if (callParams.gaslimit !== undefined) { - firstByte += 64 // 00000100 + firstByte += 64 // 01000000 const encodedGaslimit = ethers.utils.solidityPack(["uint32"], [callParams.gaslimit]) calldata = ethers.utils.hexlify(ethers.utils.concat([encodedGaslimit, calldata])) } - if (callParams.isMandatory !== undefined) firstByte += 128 // 00001000 + if (callParams.isMandatory !== undefined) firstByte += 128 // 10000000 } const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte]) From d9a08eabb3098f7d6cd728955c7c37eca8b93142 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 24 Aug 2023 12:39:18 +0300 Subject: [PATCH 36/38] version update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 702a92c..b84bfcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.6-rc-0", + "version": "0.19.48-dev.6-rc-1", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From 295e390cbdddc6de8f34fdf7329a806a4d639d1f Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 24 Aug 2023 15:01:29 +0300 Subject: [PATCH 37/38] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f3ba4d..16358d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.48-dev.6", + "version": "v0.19.69", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From 39c2c6eb0fb71c12fb10c819e16ad7cbb4e63e30 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 24 Aug 2023 19:59:03 +0300 Subject: [PATCH 38/38] bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7937e9..500f4ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.68", + "version": "0.19.70", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js",