From 4d17b460250cf3d17305f16b26566002d5de1200 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 27 Mar 2025 02:03:22 +0000 Subject: [PATCH] init --- packages/sushi/src/chain/constants.ts | 2 + packages/sushi/src/chain/generated.ts | 18 ++ .../config/bases-to-check-trades-against.ts | 18 +- packages/sushi/src/config/route-processor.ts | 2 + packages/sushi/src/config/viem.ts | 37 ++++ .../sushi/src/currency/token-addresses.ts | 6 + packages/sushi/src/currency/tokens.ts | 15 ++ packages/sushi/src/router/data-fetcher.ts | 8 + .../liquidity-providers/LiquidityProvider.ts | 8 + .../router/liquidity-providers/ShadowV2.ts | 159 ++++++++++++++ .../router/liquidity-providers/ShadowV3.ts | 198 ++++++++++++++++++ .../src/router/liquidity-providers/SwapXV2.ts | 71 +++++++ .../src/router/liquidity-providers/SwapXV3.ts | 31 +++ 13 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 packages/sushi/src/router/liquidity-providers/ShadowV2.ts create mode 100644 packages/sushi/src/router/liquidity-providers/ShadowV3.ts create mode 100644 packages/sushi/src/router/liquidity-providers/SwapXV2.ts create mode 100644 packages/sushi/src/router/liquidity-providers/SwapXV3.ts diff --git a/packages/sushi/src/chain/constants.ts b/packages/sushi/src/chain/constants.ts index 5c4536859a..dcdf468f2c 100644 --- a/packages/sushi/src/chain/constants.ts +++ b/packages/sushi/src/chain/constants.ts @@ -55,6 +55,7 @@ export const ChainId = { FLARE: 14, MATCHAIN: 698, BERA: 80094, + SONIC: 146, } as const export type ChainId = (typeof ChainId)[keyof typeof ChainId] @@ -136,5 +137,6 @@ export const ChainKey = { [ChainId.FLARE]: 'flare', [ChainId.MATCHAIN]: 'matchain', [ChainId.BERA]: 'berachain', + [ChainId.SONIC]: 'sonic', } as const export type ChainKey = (typeof ChainKey)[keyof typeof ChainKey] diff --git a/packages/sushi/src/chain/generated.ts b/packages/sushi/src/chain/generated.ts index 5961a0931d..915c565af2 100644 --- a/packages/sushi/src/chain/generated.ts +++ b/packages/sushi/src/chain/generated.ts @@ -274,6 +274,24 @@ export default [ name: 'Polygon Mainnet', shortName: 'pol', }, + { + chainId: 146, + explorers: [ + { + name: 'sonic', + url: 'https://explorer.soniclabs.com', + icon: 'sonic', + standard: 'none', + }, + ], + nativeCurrency: { + name: 'Sonic', + symbol: 'S', + decimals: 18, + }, + name: 'Sonic Mainnet', + shortName: 'sonic', + }, { chainId: 199, explorers: [ diff --git a/packages/sushi/src/config/bases-to-check-trades-against.ts b/packages/sushi/src/config/bases-to-check-trades-against.ts index bba1a4780a..3a500f88f5 100644 --- a/packages/sushi/src/config/bases-to-check-trades-against.ts +++ b/packages/sushi/src/config/bases-to-check-trades-against.ts @@ -1,5 +1,13 @@ import { ChainId } from '../chain/index.js' -import { MUSD, Token, USDB, USDe, cUSDX, sFLR } from '../currency/index.js' +import { + MUSD, + Token, + USDB, + USDe, + cUSDX, + sFLR, + scUSD, +} from '../currency/index.js' import { AAVE, BUSD, @@ -499,4 +507,12 @@ export const BASES_TO_CHECK_TRADES_AGAINST: { USDC[ChainId.BERA], USDe, ], + [ChainId.SONIC]: [ + WNATIVE[ChainId.SONIC], + USDC[ChainId.SONIC], + USDT[ChainId.SONIC], + WETH9[ChainId.SONIC], + WBTC[ChainId.SONIC], + scUSD, + ], } diff --git a/packages/sushi/src/config/route-processor.ts b/packages/sushi/src/config/route-processor.ts index e3972fa89f..ca9c7dc21c 100644 --- a/packages/sushi/src/config/route-processor.ts +++ b/packages/sushi/src/config/route-processor.ts @@ -299,6 +299,7 @@ export const ROUTE_PROCESSOR_4_SUPPORTED_CHAIN_IDS = [ ChainId.FLARE, ChainId.MATCHAIN, ChainId.BERA, + ChainId.SONIC, ] as const export type RouteProcessor4ChainId = (typeof ROUTE_PROCESSOR_4_SUPPORTED_CHAIN_IDS)[number] @@ -340,6 +341,7 @@ export const ROUTE_PROCESSOR_4_ADDRESS: Record< [ChainId.FLARE]: '0x4Aa9AEf59C7B63CD5C4B2eDE81F65A4225a99d9d', [ChainId.MATCHAIN]: '0xbD8849759749B4d8506bC851aceF0E19F34EaBEE', [ChainId.BERA]: '0x', // needs deployment + [ChainId.SONIC]: '0x', // needs deployment } as const export const isRouteProcessor4ChainId = ( chainId: ChainId, diff --git a/packages/sushi/src/config/viem.ts b/packages/sushi/src/config/viem.ts index 1014386524..b2d3cabfc8 100644 --- a/packages/sushi/src/config/viem.ts +++ b/packages/sushi/src/config/viem.ts @@ -565,6 +565,37 @@ export const berachain = { }, } as const +export const sonic = { + id: ChainId.SONIC, + name: 'Sonic Mainnet', + network: 'Sonic', + nativeCurrency: { name: 'Sonic', symbol: 'S', decimals: 18 }, + rpcUrls: { + default: { + http: ['https://sonic.drpc.org'], + }, + public: { + http: ['https://rpc.soniclabs.com'], + }, + }, + blockExplorers: { + etherscan: { + name: 'Sonic Explorer', + url: 'https://sonicscan.org', + }, + default: { + name: 'Sonic Explorer', + url: 'https://sonicscan.org', + }, + }, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11' as Address, + blockCreated: 60, + }, + }, +} as const + export const publicTransports: Record = { [ChainId.ARBITRUM_NOVA]: http(arbitrumNova.rpcUrls.default.http[0]), [ChainId.ARBITRUM]: http(arbitrum.rpcUrls.default.http[0]), @@ -604,6 +635,7 @@ export const publicTransports: Record = { [ChainId.BLAST]: http(blast.rpcUrls.default.http[0]), [ChainId.FLARE]: http(flare.rpcUrls.default.http[0]), [ChainId.BERA]: http(berachain.rpcUrls.default.http[0]), + [ChainId.SONIC]: http(sonic.rpcUrls.default.http[0]), /* Testnets */ // TODO: add testnet transports [ChainId.ARBITRUM_TESTNET]: http(arbitrumSepolia.rpcUrls.default.http[0]), [ChainId.AVALANCHE_TESTNET]: http(avalancheFuji.rpcUrls.default.http[0]), @@ -654,6 +686,7 @@ export const publicChains = [ flare as Chain, matchain, berachain, + sonic, /* Testnets */ arbitrumSepolia as Chain, @@ -837,6 +870,10 @@ export const publicClientConfig = { chain: berachain as Chain, transport: publicTransports[ChainId.BERA], }, + [ChainId.SONIC]: { + chain: sonic as Chain, + transport: publicTransports[ChainId.SONIC], + }, /* Testnets */ [ChainId.ARBITRUM_TESTNET]: { diff --git a/packages/sushi/src/currency/token-addresses.ts b/packages/sushi/src/currency/token-addresses.ts index e0bde1c387..d377a0c7da 100644 --- a/packages/sushi/src/currency/token-addresses.ts +++ b/packages/sushi/src/currency/token-addresses.ts @@ -199,6 +199,7 @@ export const WBTC_ADDRESS = { [ChainId.ZETACHAIN]: '0x13a0c5930c028511dc02665e7285134b6d11a5f4', [ChainId.CRONOS]: '0x062E66477Faf219F25D27dCED647BF57C3107d52', [ChainId.BERA]: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c', + [ChainId.SONIC]: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c', } as const export const UNI_ADDRESS = { @@ -314,6 +315,7 @@ export const WETH9_ADDRESS = { [ChainId.BLAST]: '0x4300000000000000000000000000000000000004', [ChainId.FLARE]: '0x1502FA4be69d526124D453619276FacCab275d3D', [ChainId.MATCHAIN]: '0xFF13A7A12fd485BC9687fF88D8Ae1A6b655Ab469', + [ChainId.SONIC]: '0x50c42dEAcD8Fc9773493ED674b675bE577f2634b', } as const export const WNATIVE_ADDRESS = { @@ -373,6 +375,7 @@ export const WNATIVE_ADDRESS = { [ChainId.FLARE]: '0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d', [ChainId.MATCHAIN]: '0x4200000000000000000000000000000000000006', [ChainId.BERA]: '0x6969696969696969696969696969696969696969', + [ChainId.SONIC]: '0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38', } as const export const SUSHI_ADDRESS = { @@ -459,6 +462,7 @@ export const USDC_ADDRESS = { [ChainId.FLARE]: '0xFbDa5F676cB37624f28265A144A48B0d6e87d3b6', [ChainId.MATCHAIN]: '0x97eec1c29f745dC7c267F90292AA663d997a601D', [ChainId.BERA]: '0x549943e04f40284185054145c6E4e9568C1D3241', + [ChainId.SONIC]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', } as const export const USDT_ADDRESS = { @@ -494,6 +498,7 @@ export const USDT_ADDRESS = { [ChainId.ZETACHAIN]: '0x7c8dDa80bbBE1254a7aACf3219EBe1481c6E01d7', [ChainId.FLARE]: '0x0B38e83B86d491735fEaa0a791F65c2B99535396', [ChainId.MATCHAIN]: '0xB6dc6C8b71e88642cEAD3be1025565A9eE74d1C6', + [ChainId.SONIC]: '0x6047828dc181963ba44974801FF68e538dA5eaF9', } as const export const DAI_ADDRESS = { @@ -548,6 +553,7 @@ export const FRAX_ADDRESS = { [ChainId.BOBA]: '0xAb2AF3A98D229b7dAeD7305Bb88aD0BA2c42f9cA', [ChainId.OPTIMISM]: '0x2E3D870790dC77A83DD1d18184Acc7439A53f475', [ChainId.POLYGON_ZKEVM]: '0xFf8544feD5379D9ffa8D47a74cE6b91e632AC44D', + [ChainId.SONIC]: '0x80Eede496655FB9047dd39d9f418d5483ED600df', } as const export const FXS_ADDRESS = { diff --git a/packages/sushi/src/currency/tokens.ts b/packages/sushi/src/currency/tokens.ts index 780ae2b0e7..a35f627142 100644 --- a/packages/sushi/src/currency/tokens.ts +++ b/packages/sushi/src/currency/tokens.ts @@ -705,6 +705,13 @@ export const WNATIVE = { symbol: 'WBERA', name: 'Wrapped Bera', }), + [ChainId.SONIC]: new Token({ + chainId: ChainId.SONIC, + address: WNATIVE_ADDRESS[ChainId.SONIC], + decimals: 18, + symbol: 'WS', + name: 'Wrapped Sonic', + }), } as const export const SUSHI = addressMapToTokenMap( @@ -1261,3 +1268,11 @@ export const weETH = new Token({ symbol: 'weETH', name: 'Wrapped eETH', }) + +export const scUSD = new Token({ + chainId: ChainId.SONIC, + address: '0xd3DCe716f3eF535C5Ff8d041c1A41C3bd89b97aE', + decimals: 6, + symbol: 'scUSD', + name: 'Sonic USD', +}) diff --git a/packages/sushi/src/router/data-fetcher.ts b/packages/sushi/src/router/data-fetcher.ts index d92e7c40ee..39a5d0fd4d 100644 --- a/packages/sushi/src/router/data-fetcher.ts +++ b/packages/sushi/src/router/data-fetcher.ts @@ -63,6 +63,8 @@ import { QuickSwapV3Provider } from './liquidity-providers/QuickswapV3.js' import { RingExchangeV2Provider } from './liquidity-providers/RingExchangeV2.js' import { RingExchangeV3Provider } from './liquidity-providers/RingExchangeV3.js' import { ScribeProvider } from './liquidity-providers/Scribe.js' +import { ShadowV2Provider } from './liquidity-providers/ShadowV2.js' +import { ShadowV3Provider } from './liquidity-providers/ShadowV3.js' import { ShibaSwapProvider } from './liquidity-providers/ShibaSwap.js' import { SolarbeamProvider } from './liquidity-providers/Solarbeam.js' import { SparkDexV2Provider } from './liquidity-providers/SparkDexV2.js' @@ -74,6 +76,8 @@ import { SquadSwapV2Provider } from './liquidity-providers/SquadSwapV2.js' import { SushiSwapV2Provider } from './liquidity-providers/SushiSwapV2.js' import { SushiSwapV3Provider } from './liquidity-providers/SushiSwapV3.js' import { SwapBlastProvider } from './liquidity-providers/SwapBlast.js' +import { SwapxV2Provider } from './liquidity-providers/SwapXV2.js' +import { SwapxV3Provider } from './liquidity-providers/SwapXV3.js' import { SwapsicleProvider } from './liquidity-providers/Swapsicle.js' import { ThrusterV2_1Provider, @@ -243,6 +247,8 @@ export class DataFetcher { RingExchangeV2Provider, RingExchangeV3Provider, ScribeProvider, + ShadowV2Provider, + ShadowV3Provider, ShibaSwapProvider, SolarbeamProvider, SparkDexV2Provider, @@ -255,6 +261,8 @@ export class DataFetcher { SushiSwapV3Provider, SwapBlastProvider, SwapsicleProvider, + SwapxV2Provider, + SwapxV3Provider, ThrusterV2_1Provider, ThrusterV2_3Provider, ThrusterV3Provider, diff --git a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts index f1098feac7..1397e53502 100644 --- a/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts +++ b/packages/sushi/src/router/liquidity-providers/LiquidityProvider.ts @@ -89,6 +89,10 @@ export enum LiquidityProviders { NileV3 = 'NileV3', KodiakV2 = 'KodiakV2', KodiakV3 = 'KodiakV3', + ShadowV2 = 'ShadowV2', + ShadowV3 = 'ShadowV3', + SwapxV2 = 'SwapxV2', + SwapxV3 = 'SwapxV3', } export abstract class LiquidityProvider { @@ -216,6 +220,8 @@ export const UniV2LiquidityProviders: LiquidityProviders[] = [ LiquidityProviders.MMFinance, LiquidityProviders.KodiakV2, LiquidityProviders.NileV2, + LiquidityProviders.SwapxV2, + LiquidityProviders.ShadowV2, ] export const UniV3LiquidityProviders: LiquidityProviders[] = [ @@ -248,4 +254,6 @@ export const UniV3LiquidityProviders: LiquidityProviders[] = [ LiquidityProviders.VelodromeSlipstream, LiquidityProviders.NileV3, LiquidityProviders.KodiakV3, + LiquidityProviders.ShadowV3, + LiquidityProviders.SwapxV3, ] diff --git a/packages/sushi/src/router/liquidity-providers/ShadowV2.ts b/packages/sushi/src/router/liquidity-providers/ShadowV2.ts new file mode 100644 index 0000000000..1321b7179f --- /dev/null +++ b/packages/sushi/src/router/liquidity-providers/ShadowV2.ts @@ -0,0 +1,159 @@ +import { getCreate2Address } from '@ethersproject/address' +import { Address, PublicClient, encodePacked, keccak256 } from 'viem' +import { ChainId } from '../../chain/index.js' +import { Token } from '../../currency/Token.js' +import { ConstantProductRPool } from '../../tines/PrimaryPools.js' +import { RToken } from '../../tines/RPool.js' +import { DataFetcherOptions } from '../data-fetcher.js' +import { getCurrencyCombinations } from '../get-currency-combinations.js' +import { ConstantProductPoolCode } from '../pool-codes/ConstantProductPool.js' +import { PoolCode } from '../pool-codes/PoolCode.js' +import { LiquidityProviders } from './LiquidityProvider.js' +import { StaticPool, UniswapV2BaseProvider } from './UniswapV2Base.js' + +export class ShadowV2Provider extends UniswapV2BaseProvider { + constructor(chainId: ChainId, web3Client: PublicClient) { + const factory = { + [ChainId.SONIC]: '0x2dA25E7446A70D7be65fd4c053948BEcAA6374c8', + } as const + const initCodeHash = { + [ChainId.SONIC]: + '0x4ed7aeec7c0286cad1e282dee1c391719fc17fe923b04fb0775731e413ed3554', + } as const + super(chainId, web3Client, factory, initCodeHash) + } + getType(): LiquidityProviders { + return LiquidityProviders.ShadowV2 + } + getPoolProviderName(): string { + return 'ShadowV2' + } + + override async getOnDemandPools( + t0: Token, + t1: Token, + excludePools?: Set, + options?: DataFetcherOptions, + ): Promise { + let pools = this.getStaticPools(t0, t1) + if (excludePools) pools = pools.filter((p) => !excludePools.has(p.address)) + + if (pools.length === 0) { + return + } + + this.poolsByTrade.set( + this.getTradeId(t0, t1), + pools.map((pool) => pool.address), + ) + + const fees = await this.client + .multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: true, + blockNumber: options?.blockNumber, + contracts: pools.map( + (poolCode) => + ({ + address: poolCode.address, + chainId: this.chainId, + abi: [ + { + inputs: [], + name: 'fee', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'fee', + }) as const, + ), + }) + .catch((e) => { + console.warn( + `${this.getLogPrefix()} - UPDATE: on-demand pools multicall failed, message: ${ + e.message + }`, + ) + return undefined + }) + + const poolCodesToCreate: PoolCode[] = [] + pools.forEach((pool, i) => { + const fee = fees?.[i]?.result + const existingPool = this.onDemandPools.get(pool.address) + if (existingPool === undefined) { + if (fee === undefined) { + return + } + const token0 = pool.token0 as RToken + const token1 = pool.token1 as RToken + + const rPool = new ConstantProductRPool( + pool.address, + token0, + token1, + Number(fee) * 0.0001, + 0n, + 0n, + ) + const pc = new ConstantProductPoolCode( + rPool, + this.getType(), + this.getPoolProviderName(), + ) + poolCodesToCreate.push(pc) + } + }) + + const reserves = await this.getReserves(poolCodesToCreate, options) + this.handleCreatePoolCode(poolCodesToCreate, reserves, 0) + } + + 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) => [ + { + address: this.computePoolAddress( + combination[0]!, + combination[1]!, + true, + ), + token0: combination[0]!, + token1: combination[1]!, + fee: 0, + }, + { + address: this.computePoolAddress( + combination[0]!, + combination[1]!, + false, + ), + token0: combination[0]!, + token1: combination[1]!, + fee: 0, + }, + ]) + } + + computePoolAddress(t1: Token, t2: Token, stable: boolean): 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, stable], + ), + ), + this.initCodeHash[this.chainId as keyof typeof this.initCodeHash]!, + ) as Address + } +} diff --git a/packages/sushi/src/router/liquidity-providers/ShadowV3.ts b/packages/sushi/src/router/liquidity-providers/ShadowV3.ts new file mode 100644 index 0000000000..b9b08087fd --- /dev/null +++ b/packages/sushi/src/router/liquidity-providers/ShadowV3.ts @@ -0,0 +1,198 @@ +import { defaultAbiCoder } from '@ethersproject/abi' +import { getCreate2Address } from '@ethersproject/address' +import { keccak256 } from '@ethersproject/solidity' +import { Address, PublicClient } from 'viem' +import { ChainId } from '../../chain/index.js' +import { Currency, Token, Type } from '../../currency/index.js' +import { getCurrencyCombinations } from '../get-currency-combinations.js' +import { LiquidityProviders } from './LiquidityProvider.js' +import { StaticPoolUniV3, UniswapV3BaseProvider } from './UniswapV3Base.js' + +/** + * The default factory enabled fee amounts, denominated in hundredths of bips. + */ +export enum ShadowV3FeeAmount { + /** 0.01% */ + LOWEST = 100, + /** 0.025% */ + LOWER = 250, + /** 0.05% */ + LOW = 500, + /** 0.3% */ + MEDIUM = 3000, + /** 1% */ + HIGH = 10000, + /** 2% */ + HIGHEST = 20000, +} + +/** + * The default factory tick spacings by fee amount. + */ +export const SHADOW_V3_FEE_SPACING_MAP: Record = { + [ShadowV3FeeAmount.LOWEST]: 1, + [ShadowV3FeeAmount.LOWER]: 5, + [ShadowV3FeeAmount.LOW]: 10, + [ShadowV3FeeAmount.MEDIUM]: 50, + [ShadowV3FeeAmount.HIGH]: 100, + [ShadowV3FeeAmount.HIGHEST]: 200, +} +export const SHADOW_V3_TICK_SPACING_MAP: Record = { + 1: ShadowV3FeeAmount.LOWEST, + 5: ShadowV3FeeAmount.LOWER, + 10: ShadowV3FeeAmount.LOW, + 50: ShadowV3FeeAmount.MEDIUM, + 100: ShadowV3FeeAmount.HIGH, + 200: ShadowV3FeeAmount.HIGHEST, +} + +export class ShadowV3Provider extends UniswapV3BaseProvider { + override FEE = ShadowV3FeeAmount + override TICK_SPACINGS = SHADOW_V3_FEE_SPACING_MAP + mainFactory: Record = {} + constructor(chainId: ChainId, web3Client: PublicClient) { + const poolDeployer = { + [ChainId.SONIC]: '0x8BBDc15759a8eCf99A92E004E0C64ea9A5142d59', + } as const + const initCodeHash = { + [ChainId.SONIC]: + '0xc701ee63862761c31d620a4a083c61bdc1e81761e6b9c9267fd19afd22e0821d', + } as const + const tickLens = { + [ChainId.SONIC]: '0x095bBC37f439EEf5dcF733205B51447d03202E14', + } as const + const factory = { + [ChainId.SONIC]: '0xcD2d0637c94fe77C2896BbCBB174cefFb08DE6d7', + } as const + super(chainId, web3Client, poolDeployer, initCodeHash, tickLens) + this.mainFactory = factory + if (!(chainId in this.mainFactory)) { + throw new Error( + `${this.getType()} cannot be instantiated for chainid ${chainId}, no main factory`, + ) + } + } + getType(): LiquidityProviders { + return LiquidityProviders.ShadowV3 + } + getPoolProviderName(): string { + return 'ShadowV3' + } + + override getStaticPools(t1: Token, t2: Token): StaticPoolUniV3[] { + const tickSpacingList = Object.values(this.TICK_SPACINGS).filter( + (v) => typeof v === 'number', + ) + const currencyCombinations = getCurrencyCombinations(this.chainId, t1, t2) + + const allCurrencyCombinationsWithAllTickSpacings: [Type, Type, number][] = + currencyCombinations.reduce<[Currency, Currency, number][]>( + (list, [tokenA, tokenB]) => { + if (tokenA !== undefined && tokenB !== undefined) { + return list.concat( + tickSpacingList.map((tickspacing) => [ + tokenA, + tokenB, + tickspacing, + ]), + ) + } + return [] + }, + [], + ) + + const filtered: [Token, Token, number][] = [] + allCurrencyCombinationsWithAllTickSpacings.forEach( + ([currencyA, currencyB, tickSpacing]) => { + if (currencyA && currencyB && tickSpacing) { + const tokenA = currencyA.wrapped + const tokenB = currencyB.wrapped + if (tokenA.equals(tokenB)) return + filtered.push( + tokenA.sortsBefore(tokenB) + ? [tokenA, tokenB, tickSpacing] + : [tokenB, tokenA, tickSpacing], + ) + } + }, + ) + return filtered.map(([currencyA, currencyB, tickSpacing]) => ({ + address: this.computeShadowV3PoolAddress( + currencyA.wrapped, + currencyB.wrapped, + tickSpacing, + ) as Address, + token0: currencyA, + token1: currencyB, + fee: SHADOW_V3_TICK_SPACING_MAP[tickSpacing]!, + })) + } + + computeShadowV3PoolAddress( + tokenA: Token, + tokenB: Token, + tickSpacing: number, + ): Address { + const [token0, token1] = tokenA.sortsBefore(tokenB) + ? [tokenA, tokenB] + : [tokenB, tokenA] + return getCreate2Address( + this.factory[this.chainId as keyof typeof this.factory]!, + keccak256( + ['bytes'], + [ + defaultAbiCoder.encode( + ['address', 'address', 'uint24'], + [token0.address, token1.address, tickSpacing], + ), + ], + ), + this.initCodeHash[this.chainId as keyof typeof this.initCodeHash]!, + ) as Address + } + + override async ensureFeeAndTicks(): Promise { + const tickSpacingList = Object.values(this.TICK_SPACINGS).filter( + (v) => typeof v === 'number', + ) + const results = (await this.client.multicall({ + multicallAddress: this.client.chain?.contracts?.multicall3 + ?.address as Address, + allowFailure: false, + contracts: tickSpacingList.map( + (tickSpacing) => + ({ + chainId: this.chainId, + address: this.mainFactory[ + this.chainId as keyof typeof this.mainFactory + ]! as Address, + abi: [ + { + inputs: [ + { internalType: 'int24', name: 'tickSpacing', type: 'int24' }, + ], + name: 'tickSpacingInitialFee', + outputs: [ + { + internalType: 'uint24', + name: 'initialFee', + type: 'uint24', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'tickSpacingInitialFee', + args: [tickSpacing], + }) as const, + ), + })) as number[] + + return results.every( + (v, i) => + this.TICK_SPACINGS[v as ShadowV3FeeAmount] === tickSpacingList[i], + ) + } +} diff --git a/packages/sushi/src/router/liquidity-providers/SwapXV2.ts b/packages/sushi/src/router/liquidity-providers/SwapXV2.ts new file mode 100644 index 0000000000..2ff5b269ea --- /dev/null +++ b/packages/sushi/src/router/liquidity-providers/SwapXV2.ts @@ -0,0 +1,71 @@ +import { getCreate2Address } from '@ethersproject/address' +import { Address, PublicClient, encodePacked, keccak256 } from 'viem' +import { ChainId } from '../../chain/index.js' +import { Token } from '../../currency/Token.js' +import { getCurrencyCombinations } from '../get-currency-combinations.js' +import { LiquidityProviders } from './LiquidityProvider.js' +import { StaticPool, UniswapV2BaseProvider } from './UniswapV2Base.js' + +export class SwapxV2Provider extends UniswapV2BaseProvider { + STABLE_FEE = 0.0001 + VOLATILE_FEE = 0.01 + constructor(chainId: ChainId, web3Client: PublicClient) { + const factory = { + [ChainId.SONIC]: '0x05c1be79d3aC21Cc4B727eeD58C9B2fF757F5663', + } as const + const initCodeHash = { + [ChainId.SONIC]: + '0x6c45999f36731ff6ab43e943fca4b5a700786bbb202116cf6633b32039161e05', + } as const + super(chainId, web3Client, factory, initCodeHash) + } + getType(): LiquidityProviders { + return LiquidityProviders.SwapxV2 + } + getPoolProviderName(): string { + return 'SwapxV2' + } + + 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) => [ + { + address: this.computePoolAddress( + combination[0]!, + combination[1]!, + true, + ), + token0: combination[0]!, + token1: combination[1]!, + fee: this.STABLE_FEE, + }, + { + address: this.computePoolAddress( + combination[0]!, + combination[1]!, + false, + ), + token0: combination[0]!, + token1: combination[1]!, + fee: this.VOLATILE_FEE, + }, + ]) + } + + computePoolAddress(t1: Token, t2: Token, stable: boolean): 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, stable], + ), + ), + this.initCodeHash[this.chainId as keyof typeof this.initCodeHash]!, + ) as Address + } +} diff --git a/packages/sushi/src/router/liquidity-providers/SwapXV3.ts b/packages/sushi/src/router/liquidity-providers/SwapXV3.ts new file mode 100644 index 0000000000..4cb7852947 --- /dev/null +++ b/packages/sushi/src/router/liquidity-providers/SwapXV3.ts @@ -0,0 +1,31 @@ +import { PublicClient } from 'viem' +import { ChainId } from '../../chain/index.js' +import { AlgebraV2BaseProvider } from './AlgebraV2Base.js' +import { LiquidityProviders } from './LiquidityProvider.js' + +export class SwapxV3Provider extends AlgebraV2BaseProvider { + override readonly BASE_FEE = 500 as any + override DEFAULT_TICK_SPACING = 60 + constructor(chainId: ChainId, web3Client: PublicClient) { + const factory = { + [ChainId.SONIC]: '0x8121a3F8c4176E9765deEa0B95FA2BDfD3016794', + } as const + const poolDeployer = { + [ChainId.SONIC]: '0x885229E48987EA4c68F0aA1bCBff5184198A9188', + } as const + const initCodeHash = { + [ChainId.SONIC]: + '0xf96d2474815c32e070cd63233f06af5413efc5dcb430aee4ff18cc29007c562d', + } as const + const tickLens = { + [ChainId.SONIC]: '0x8Fc0a2CF22Bbd0abDAc54413e6678F6FcE6Ff35C', + } as const + super(chainId, web3Client, factory, initCodeHash, tickLens, poolDeployer) + } + getType(): LiquidityProviders { + return LiquidityProviders.SwapxV3 + } + getPoolProviderName(): string { + return 'SwapxV3' + } +}