diff --git a/packages/sushi/src/router/data-fetcher.ts b/packages/sushi/src/router/data-fetcher.ts index 063182136d..a2b5b79ed5 100644 --- a/packages/sushi/src/router/data-fetcher.ts +++ b/packages/sushi/src/router/data-fetcher.ts @@ -24,6 +24,7 @@ import { LiquidityProvider, LiquidityProviders, } from './liquidity-providers/LiquidityProvider.js' +import { LynexV1Provider } from './liquidity-providers/LynexV1Provider.js' import { MonoswapV2Provider } from './liquidity-providers/MonoSwapV2.js' import { MonoswapV3Provider } from './liquidity-providers/MonoSwapV3.js' import { NativeWrapProvider } from './liquidity-providers/NativeWrapProvider.js' @@ -166,6 +167,7 @@ export class DataFetcher { KinetixV2Provider, KinetixV3Provider, LaserSwapV2Provider, + LynexV1Provider, MonoswapV2Provider, MonoswapV3Provider, NetSwapProvider, diff --git a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts index 18bb70f411..c54804f3bb 100644 --- a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts +++ b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts @@ -49,6 +49,7 @@ export enum LiquidityProviders { Camelot = 'Camelot', Enosys = 'Enosys', BlazeSwap = 'BlazeSwap', + LynexV1 = 'LynexV1', } export abstract class LiquidityProvider { @@ -156,6 +157,7 @@ export const UniV2LiquidityProviders: LiquidityProviders[] = [ LiquidityProviders.Camelot, LiquidityProviders.Enosys, LiquidityProviders.BlazeSwap, + LiquidityProviders.LynexV1, ] export const UniV3LiquidityProviders: LiquidityProviders[] = [ diff --git a/packages/sushi/src/router/liquidity-providers/LynexV1Provider.ts b/packages/sushi/src/router/liquidity-providers/LynexV1Provider.ts new file mode 100644 index 0000000000..b9d14e40a3 --- /dev/null +++ b/packages/sushi/src/router/liquidity-providers/LynexV1Provider.ts @@ -0,0 +1,142 @@ +import { getCreate2Address } from '@ethersproject/address' +import { Address, PublicClient, encodePacked, keccak256, parseAbi } from 'viem' +import { ChainId } from '../../chain/index.js' +import { Token } from '../../currency/index.js' +import { DataFetcherOptions } from '../data-fetcher.js' +import { getCurrencyCombinations } from '../get-currency-combinations.js' +import { memoizer } from '../memoizer.js' +import { LiquidityProviders } from './LiquidityProvider.js' +import { StaticPool, UniswapV2BaseProvider } from './UniswapV2Base.js' + +const GetFeesAbi = parseAbi([ + 'function getFee(bool _stable) public view returns(uint256)', +]) + +export class LynexV1Provider extends UniswapV2BaseProvider { + STABLE_FEE = 0.0001 + VOLATILE_FEE = 0.0025 + constructor(chainId: ChainId, web3Client: PublicClient) { + const factory = { + [ChainId.LINEA]: '0xBc7695Fd00E3b32D08124b7a4287493aEE99f9ee', + } as const + const initCodeHash = { + [ChainId.LINEA]: + '0xf40e8808230a29863f9f7f99beb90d28bca2c60094e78d93cca67f746dbfd142', + } as const + super(chainId, web3Client, factory, initCodeHash) + } + getType(): LiquidityProviders { + return LiquidityProviders.LynexV1 + } + getPoolProviderName(): string { + return 'LynexV1' + } + + // overriden to read the fees from the factory, + // LynexV1 can have different fee for pools that are `stable` or not + // so read the stable and non stable fees before generating pools + // addresses and use those fees for fetched pools and then reading the pools reserves + override async fetchPoolsForToken( + t0: Token, + t1: Token, + excludePools?: Set, + options?: DataFetcherOptions, + ): Promise { + // get current fees + const multicallMemoize = await memoizer.fn(this.client.multicall) + const getFeesData = { + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + blockNumber: options?.blockNumber, + contracts: [true, false].map( + (isStable) => + ({ + address: this.factory[ + this.chainId as keyof typeof this.factory + ] as Address, + chainId: this.chainId, + abi: GetFeesAbi, + functionName: 'getFee', + args: [isStable], + }) as const, + ), + } + const fees = options?.memoize + ? await (multicallMemoize(getFeesData) as Promise).catch((e) => { + console.warn( + `${this.getLogPrefix()} - UPDATE: on-demand pools multicall failed, message: ${ + e.message + }`, + ) + return undefined + }) + : await this.client.multicall(getFeesData).catch((e) => { + console.warn( + `${this.getLogPrefix()} - UPDATE: on-demand pools multicall failed, message: ${ + e.message + }`, + ) + return undefined + }) + // convert to % in number + // returned values are represented in 1/100th of a percent + // so returned value of 25 is 0.25%, ie 0.0025 in numeric value + this.STABLE_FEE = Number(fees[0]) * 0.0001 + this.VOLATILE_FEE = Number(fees[1]) * 0.0001 + + // proceed the rest as a normal univ2 based dex + await this.getOnDemandPools(t0, t1, excludePools, options) + } + + // LynexV1 has an extra bool variable in its pool address salt + _getPoolAddresses(t1: Token, t2: Token): Address[] { + return [ + getCreate2Address( + this.factory[this.chainId as keyof typeof this.factory]!, + keccak256( + encodePacked( + ['address', 'address', 'bool'], + [t1.address as Address, t2.address as Address, true], + ), + ), + this.initCodeHash[this.chainId as keyof typeof this.initCodeHash]!, + ) as Address, + getCreate2Address( + this.factory[this.chainId as keyof typeof this.factory]!, + keccak256( + encodePacked( + ['address', 'address', 'bool'], + [t1.address as Address, t2.address as Address, false], + ), + ), + this.initCodeHash[this.chainId as keyof typeof this.initCodeHash]!, + ) as Address, + ] + } + + // same as original getStaticPools() in UniswapV2BaseProvider, but + // just overriden to do flatMap() to flatten array of pool addresses + // per token pair, since LynexV1 also has bool variable in the pool address salt + // also has 2 fees if the pair is `stable` or not + override getStaticPools(t1: Token, t2: Token): StaticPool[] { + const currencyCombination = getCurrencyCombinations( + this.chainId, + t1, + t2, + ).map(([c0, c1]) => (c0.sortsBefore(c1) ? [c0, c1] : [c1, c0])) + return currencyCombination.flatMap((combination) => { + const poolAddresses = this._getPoolAddresses( + combination[0]!, + combination[1]!, + ) + return poolAddresses.map((poolAddress, i) => ({ + address: poolAddress, + token0: combination[0]!, + token1: combination[1]!, + // set fee for stable and volatile + fee: i === 0 ? this.STABLE_FEE : this.VOLATILE_FEE, + })) + }) + } +} diff --git a/protocols/route-processor/test/DataFetcher.test.ts b/protocols/route-processor/test/DataFetcher.test.ts index 5ce9a8fa24..f177fc6234 100644 --- a/protocols/route-processor/test/DataFetcher.test.ts +++ b/protocols/route-processor/test/DataFetcher.test.ts @@ -119,6 +119,7 @@ const excludedChains = [ ChainId.BOBA_AVAX, ChainId.ZKSYNC_ERA, ] + const chainIds = Object.values(ChainId).filter((v) => { if (excludedChains.every((e) => v !== e) && process?.env?.CHAIN) { return v === ChainId[process.env.CHAIN as keyof typeof ChainId]