diff --git a/package.json b/package.json index c4ee2a91d7..49d25bd7f7 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,13 @@ "@ethersproject/networks": "5.7.0", "@ethersproject/providers": "5.7.0", "@ethersproject/solidity": "5.7.0", - "@injectivelabs/sdk-ts": "^1.10.58", "@harmony-js/core": "^0.1.57", "@harmony-js/utils": "^0.1.56", "@improbable-eng/grpc-web": "^0.13.0", - "@pancakeswap/sdk": "^2.4.5", + "@injectivelabs/sdk-ts": "^1.10.58", + "@pancakeswap/sdk": "^4.0.0", + "@pancakeswap/smart-router": "^4.2.1", + "@pancakeswap/tokens": "^0.1.6", "@pancakeswap/swap-sdk-core": "^1.0.0", "@pancakeswap/v3-core": "^1.0.2", "@pancakeswap/v3-periphery": "^1.0.2", @@ -77,6 +79,7 @@ "express": "^4.17.1", "express-winston": "^4.1.0", "fs-extra": "^10.0.0", + "graphql-request": "^6.1.0", "http-status-codes": "2.2.0", "immutable": "^4.2.4", "js-yaml": "^4.1.0", @@ -97,8 +100,8 @@ "web3": "^1.7.3", "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5", - "xsswap-sdk": "^1.0.1", "xrpl": "^2.7.0", + "xsswap-sdk": "^1.0.1", "yarn": "^1.22.17" }, "devDependencies": { diff --git a/src/connectors/pancakeswap/pancakeswap.ts b/src/connectors/pancakeswap/pancakeswap.ts index 0ea4ab6242..cfcb260fda 100644 --- a/src/connectors/pancakeswap/pancakeswap.ts +++ b/src/connectors/pancakeswap/pancakeswap.ts @@ -1,23 +1,24 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { - Fetcher, - Pair, + Currency, + CurrencyAmount, Percent, - Router, - SwapParameters, Token, - TokenAmount, - Trade, + TradeType, } from '@pancakeswap/sdk'; import { BigNumber, - Contract, ContractInterface, ContractTransaction, Transaction, Wallet, } from 'ethers'; import { BinanceSmartChain } from '../../chains/binance-smart-chain/binance-smart-chain'; -import { ExpectedTrade, Uniswapish } from '../../services/common-interfaces'; +import { + ExpectedTrade, + Uniswapish, + UniswapishTrade, +} from '../../services/common-interfaces'; import { percentRegexp } from '../../services/config-manager-v2'; import { InitializationError, @@ -29,7 +30,17 @@ import { logger } from '../../services/logger'; import { isFractionString } from '../../services/validators'; import { PancakeSwapConfig } from './pancakeswap.config'; import routerAbi from './pancakeswap_router_abi.json'; -import { getAddress } from 'ethers/lib/utils'; +import { PublicClient, createPublicClient, http, getAddress } from 'viem'; +import { GraphQLClient } from 'graphql-request'; +import { + Pool, + PoolType, + SmartRouter, + SmartRouterTrade, + SwapRouter, +} from '@pancakeswap/smart-router'; +import { bsc, bscTestnet } from '@wagmi/chains'; +import { MethodParameters } from '@pancakeswap/v3-sdk'; export class PancakeSwap implements Uniswapish { private static _instances: { [name: string]: PancakeSwap }; @@ -41,6 +52,7 @@ export class PancakeSwap implements Uniswapish { private _routerAbi: ContractInterface; private _gasLimitEstimate: number; private _ttl: number; + private _maximumHops: number; private tokenList: Record = {}; private _ready: boolean = false; @@ -52,6 +64,7 @@ export class PancakeSwap implements Uniswapish { this._chain = chain; this._router = config.routerAddress(network); this._ttl = config.ttl; + this._maximumHops = config.maximumHops ?? 1; this._routerAbi = routerAbi.abi; this._gasLimitEstimate = config.gasLimitEstimate; } @@ -130,6 +143,13 @@ export class PancakeSwap implements Uniswapish { return this._ttl; } + /** + * Default maximum number of hops for to go through for a swap transactions. + */ + public get maximumHops(): number { + return this._maximumHops; + } + /** * Gets the allowed slippage percent from the optional parameter or the value * in the configuration. @@ -159,47 +179,38 @@ export class PancakeSwap implements Uniswapish { * @param quoteToken Token input for the transaction * @param baseToken Token output from the transaction * @param amount Amount of `baseToken` desired from the transaction - * @param allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction + * @param _allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction */ async estimateBuyTrade( quoteToken: Token, baseToken: Token, amount: BigNumber, - allowedSlippage?: string + _allowedSlippage?: string ): Promise { - const nativeTokenAmount: TokenAmount = new TokenAmount( - baseToken, - amount.toString() - ); logger.info( `Fetching pair data for ${quoteToken.address}-${baseToken.address}.` ); - const pair: Pair = await Fetcher.fetchPairData( - quoteToken, - baseToken, - this.bsc.provider - ); - const trades: Trade[] = Trade.bestTradeExactOut( - [pair], - quoteToken, - nativeTokenAmount, - { maxHops: 1 } - ); - if (!trades || trades.length === 0) { + + const trade = await this.getBestTrade(baseToken, quoteToken, amount, TradeType.EXACT_OUTPUT); + + if (!trade) { throw new UniswapishPriceError( - `priceSwapOut: no trade pair found for ${quoteToken.address} to ${baseToken.address}.` + `priceSwapOut: no trade pair found for ${baseToken.address} to ${quoteToken.address}.` ); } logger.info( - `Best trade for ${quoteToken.address}-${baseToken.address}: ` + - `${trades[0].executionPrice.invert().toFixed(6)} ` + - `${baseToken.name}.` + `Best trade for ${baseToken.address}-${quoteToken.address}: ` + + `${trade.inputAmount.toExact()}` + + `${baseToken.symbol}.` ); - const expectedAmount = trades[0].maximumAmountIn( - this.getAllowedSlippage(allowedSlippage) - ); - return { trade: trades[0], expectedAmount }; + return { + trade: { + ...trade, + executionPrice: SmartRouter.getExecutionPrice(trade)!, + }, + expectedAmount: trade.inputAmount, + }; } /** @@ -211,47 +222,38 @@ export class PancakeSwap implements Uniswapish { * @param baseToken Token input for the transaction * @param quoteToken Output from the transaction * @param amount Amount of `baseToken` to put into the transaction - * @param allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction + * @param _allowedSlippage (Optional) Fraction in string representing the allowed slippage for this transaction */ async estimateSellTrade( baseToken: Token, quoteToken: Token, amount: BigNumber, - allowedSlippage?: string + _allowedSlippage?: string ): Promise { - const nativeTokenAmount: TokenAmount = new TokenAmount( - baseToken, - amount.toString() - ); logger.info( `Fetching pair data for ${baseToken.address}-${quoteToken.address}.` ); - const pair: Pair = await Fetcher.fetchPairData( - baseToken, - quoteToken, - this.bsc.provider - ); - const trades: Trade[] = Trade.bestTradeExactIn( - [pair], - nativeTokenAmount, - quoteToken, - { maxHops: 1 } - ); - if (!trades || trades.length === 0) { + const trade = await this.getBestTrade(baseToken, quoteToken, amount, TradeType.EXACT_INPUT); + + if (!trade) { throw new UniswapishPriceError( - `priceSwapIn: no trade pair found for ${baseToken} to ${quoteToken}.` + `priceSwapIn: no trade pair found for ${baseToken.address} to ${quoteToken.address}.` ); } logger.info( `Best trade for ${baseToken.address}-${quoteToken.address}: ` + - `${trades[0].executionPrice.toFixed(6)}` + - `${baseToken.name}.` + `${trade.outputAmount.toExact()}` + + `${baseToken.symbol}.` ); - const expectedAmount = trades[0].minimumAmountOut( - this.getAllowedSlippage(allowedSlippage) - ); - return { trade: trades[0], expectedAmount }; + + return { + trade: { + ...trade, + executionPrice: SmartRouter.getExecutionPrice(trade)!, + }, + expectedAmount: trade.outputAmount, + }; } /** @@ -260,9 +262,9 @@ export class PancakeSwap implements Uniswapish { * @param wallet Wallet * @param trade Expected trade * @param gasPrice Base gas price, for pre-EIP1559 transactions - * @param pancakeswapRouter Router smart contract address + * @param pancakeswapRouter Smart Router smart contract address * @param ttl How long the swap is valid before expiry, in seconds - * @param abi Router contract ABI + * @param _abi Router contract ABI * @param gasLimit Gas limit * @param nonce (Optional) EVM transaction nonce * @param maxFeePerGas (Optional) Maximum total fee per gas you want to pay @@ -271,41 +273,48 @@ export class PancakeSwap implements Uniswapish { */ async executeTrade( wallet: Wallet, - trade: Trade, + trade: UniswapishTrade, gasPrice: number, pancakeswapRouter: string, ttl: number, - abi: ContractInterface, + _abi: ContractInterface, gasLimit: number, nonce?: number, maxFeePerGas?: BigNumber, maxPriorityFeePerGas?: BigNumber, allowedSlippage?: string ): Promise { - const result: SwapParameters = Router.swapCallParameters(trade, { - ttl, - recipient: wallet.address, - allowedSlippage: this.getAllowedSlippage(allowedSlippage), - }); + const methodParameters: MethodParameters = SwapRouter.swapCallParameters( + trade as SmartRouterTrade, + { + deadlineOrPreviousBlockhash: Math.floor(Date.now() / 1000 + ttl), + recipient: getAddress(wallet.address), + slippageTolerance: this.getAllowedSlippage(allowedSlippage), + } + ); - const contract: Contract = new Contract(pancakeswapRouter, abi, wallet); if (nonce === undefined) { nonce = await this.bsc.nonceManager.getNextNonce(wallet.address); } + let tx: ContractTransaction; - if (maxFeePerGas || maxPriorityFeePerGas) { - tx = await contract[result.methodName](...result.args, { - gasLimit: gasLimit, - value: result.value, + if (maxFeePerGas !== undefined || maxPriorityFeePerGas !== undefined) { + tx = await wallet.sendTransaction({ + data: methodParameters.calldata, + to: pancakeswapRouter, + gasLimit: gasLimit.toFixed(0), + value: methodParameters.value, nonce: nonce, maxFeePerGas, maxPriorityFeePerGas, }); } else { - tx = await contract[result.methodName](...result.args, { + tx = await wallet.sendTransaction({ + data: methodParameters.calldata, + to: pancakeswapRouter, gasPrice: (gasPrice * 1e9).toFixed(0), gasLimit: gasLimit.toFixed(0), - value: result.value, + value: methodParameters.value, nonce: nonce, }); } @@ -314,4 +323,111 @@ export class PancakeSwap implements Uniswapish { await this.bsc.nonceManager.commitNonce(wallet.address, nonce); return tx; } + + async getPools(currencyA: Currency, currencyB: Currency): Promise { + const v3SubgraphClient = new GraphQLClient( + 'https://api.thegraph.com/subgraphs/name/pancakeswap/exchange-v3-bsc' + ); + const v2SubgraphClient = new GraphQLClient( + 'https://proxy-worker-api.pancakeswap.com/bsc-exchange' + ); + + const pairs = SmartRouter.getPairCombinations(currencyA, currencyB); + + // Create v2 candidate pool fetcher with your own on-chain fetcher + const getV2PoolsByCommonTokenPrices = + SmartRouter.createV2PoolsProviderByCommonTokenPrices( + SmartRouter.getCommonTokenPricesBySubgraph + ); + const getV2CandidatePools = SmartRouter.createGetV2CandidatePools( + getV2PoolsByCommonTokenPrices + ); + + // Define v3 pool on-chain fetcher with customized tvl references + const getV3CandidatePools = SmartRouter.createGetV3CandidatePools( + // Use your customized v3 pool fetcher by default + SmartRouter.getV3PoolsWithTvlFromOnChain, + { + fallbacks: [], + // In millisecond + // Will try fallback fetcher if the default doesn't respond in 2s + fallbackTimeout: 1500, + } + ); + + const allPools = await Promise.allSettled([ + // @ts-ignore + SmartRouter.getStablePoolsOnChain(pairs, () => this.createPublicClient()), + getV2CandidatePools({ + // @ts-ignore + onChainProvider: () => this.createPublicClient(), + // @ts-ignore + v2SubgraphProvider: () => v2SubgraphClient, + // @ts-ignore + v3SubgraphProvider: () => v3SubgraphClient, + currencyA, + currencyB, + }), + getV3CandidatePools({ + // @ts-ignore + onChainProvider: () => this.createPublicClient(), + // @ts-ignore + subgraphProvider: () => v3SubgraphClient, + currencyA, + currencyB, + subgraphCacheFallback: true, + }), + ]); + + const fulfilledPools = allPools.reduce((acc, pool) => { + if (pool.status === 'fulfilled') { + return [...acc, ...pool.value]; + } + return acc; + }, [] as Pool[]); + + return fulfilledPools.flat(); + } + + async getBestTrade(baseToken: Token, quoteToken: Token, amount: BigNumber, tradeType: TradeType): Promise | null> { + const baseTokenAmount: CurrencyAmount = + CurrencyAmount.fromRawAmount(baseToken, amount.toString()); + + const quoteProvider = SmartRouter.createQuoteProvider({ + // @ts-ignore + onChainProvider: () => this.createPublicClient(), + }); + const pools = await this.getPools(baseToken, quoteToken); + + const trade = await SmartRouter.getBestTrade( + baseTokenAmount, + quoteToken, + tradeType, + { + gasPriceWei: () => this.createPublicClient().getGasPrice(), + maxHops: this._maximumHops, + maxSplits: 1, + poolProvider: SmartRouter.createStaticPoolProvider(pools), + quoteProvider, + quoterOptimization: true, + allowedPoolTypes: [PoolType.V2, PoolType.V3, PoolType.STABLE], + } + ); + + return trade; + } + + private createPublicClient(): PublicClient { + const transportUrl = this.bsc.rpcUrl; + + return createPublicClient({ + chain: this.chainId === 56 ? bsc : bscTestnet, + transport: http(transportUrl), + batch: { + multicall: { + batchSize: 1024 * 200, + }, + }, + }); + } } diff --git a/src/connectors/ref/ref.controllers.ts b/src/connectors/ref/ref.controllers.ts index b82e104760..fd64d2027c 100644 --- a/src/connectors/ref/ref.controllers.ts +++ b/src/connectors/ref/ref.controllers.ts @@ -187,7 +187,7 @@ export async function trade( logger.info( `Expected execution price is ${estimatedPrice}, ` + - `limit price is ${limitPrice}.` + `limit price is ${limitPrice}.` ); if (req.side === 'BUY') { diff --git a/src/services/common-interfaces.ts b/src/services/common-interfaces.ts index b42d0fc16d..25e8ac78ce 100644 --- a/src/services/common-interfaces.ts +++ b/src/services/common-interfaces.ts @@ -81,9 +81,13 @@ import { import { Token as PancakeSwapToken, CurrencyAmount as PancakeSwapCurrencyAmount, + TradeType as PancakeSwapTradeType, Trade as PancakeSwapTrade, Fraction as PancakeSwapFraction, + Currency as PancakeSwapCurrency, + Price as PancakeSwapPrice, } from '@pancakeswap/sdk'; +import { SmartRouterTrade as PancakeSwapSmartRouterTrade } from '@pancakeswap/smart-router'; import { Token as TokenXsswap, CurrencyAmount as CurrencyAmountXsswap, @@ -141,7 +145,17 @@ export type UniswapishTrade = | TradeTraderjoe | SushiswapTrade | TradeUniswap - | PancakeSwapTrade + | PancakeSwapTrade< + PancakeSwapCurrency, + PancakeSwapCurrency, + PancakeSwapTradeType + > + | (PancakeSwapSmartRouterTrade & { + executionPrice: PancakeSwapPrice< + PancakeSwapCurrency, + PancakeSwapCurrency + >; + }) | MMFTrade | VVSTrade | TradeXsswap @@ -163,7 +177,7 @@ export type UniswapishAmount = | UniswapCoreCurrencyAmount | CurrencyAmountTraderjoe | SushiCurrencyAmount - | PancakeSwapCurrencyAmount + | PancakeSwapCurrencyAmount | CurrencyAmountMMF | CurrencyAmountVVS | CurrencyAmountXsswap diff --git a/src/templates/binance-smart-chain.yml b/src/templates/binance-smart-chain.yml index 7de9f60477..91a5c4646b 100644 --- a/src/templates/binance-smart-chain.yml +++ b/src/templates/binance-smart-chain.yml @@ -2,7 +2,7 @@ networks: mainnet: chainID: 56 - nodeURL: 'https://rpc.ankr.com/bsc' + nodeURL: 'https://binance.llamarpc.com' tokenListType: FILE tokenListSource: /home/gateway/conf/lists/bep20_tokens_mainnet.json nativeCurrencySymbol: 'BNB' diff --git a/src/templates/pancakeswap.yml b/src/templates/pancakeswap.yml index d0f730c2d1..ec0a198055 100644 --- a/src/templates/pancakeswap.yml +++ b/src/templates/pancakeswap.yml @@ -25,12 +25,12 @@ feeTier: 'MEDIUM' contractAddresses: mainnet: - routerAddress: '0x10ED43C718714eb63d5aA57B78B54704E256024E' + routerAddress: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4' pancakeswapV3SmartOrderRouterAddress: '0x1b81D678ffb9C0263b24A97847620C99d213eB14' pancakeswapV3NftManagerAddress: '0x46A15B0b27311cedF172AB29E4f4766fbE7F4364' pancakeswapV3QuoterV2ContractAddress: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997' testnet: - routerAddress: '0xdc4904b5f716Ff30d8495e35dC99c109bb5eCf81' + routerAddress: '0x9a489505a00cE272eAa5e07Dba6491314CaE3796' pancakeswapV3SmartOrderRouterAddress: '0x1b81D678ffb9C0263b24A97847620C99d213eB14' pancakeswapV3NftManagerAddress: '0x427bF5b37357632377eCbEC9de3626C71A5396c1' pancakeswapV3QuoterV2ContractAddress: '0xbC203d7f83677c7ed3F7acEc959963E7F4ECC5C2' diff --git a/test/connectors/pancakeswap/pancakeswap.routes.test.ts b/test/connectors/pancakeswap/pancakeswap.routes.test.ts index 632bb73ee8..9cb341d4a0 100644 --- a/test/connectors/pancakeswap/pancakeswap.routes.test.ts +++ b/test/connectors/pancakeswap/pancakeswap.routes.test.ts @@ -9,10 +9,10 @@ let bsc: BinanceSmartChain; let pancakeswap: PancakeSwap; beforeAll(async () => { - bsc = BinanceSmartChain.getInstance('testnet'); + bsc = BinanceSmartChain.getInstance('mainnet'); patchEVMNonceManager(bsc.nonceManager); await bsc.init(); - pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'testnet'); + pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'mainnet'); await pancakeswap.init(); }); @@ -42,17 +42,17 @@ const patchStoredTokenList = () => { patch(bsc, 'tokenList', () => { return [ { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }, { - chainId: 97, + chainId: 56, name: 'DAI', symbol: 'DAI', - address: '0x8a9424745056Eb399FD19a0EC26A14316684e274', + address: '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', decimals: 18, }, ]; @@ -63,18 +63,18 @@ const patchGetTokenBySymbol = () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { return { - chainId: 97, + chainId: 56, name: 'DAI', symbol: 'DAI', - address: '0x8a9424745056Eb399FD19a0EC26A14316684e274', + address: '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', decimals: 18, }; } @@ -84,10 +84,10 @@ const patchGetTokenBySymbol = () => { const patchGetTokenByAddress = () => { patch(pancakeswap, 'getTokenByAddress', () => { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; }); @@ -156,7 +156,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -185,7 +185,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -206,10 +206,10 @@ describe('POST /amm/price', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -222,7 +222,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DOGE', base: 'WBNB', @@ -239,10 +239,10 @@ describe('POST /amm/price', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -255,7 +255,7 @@ describe('POST /amm/price', () => { .post(`/amm/price`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'SHIBA', @@ -284,7 +284,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -306,7 +306,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -324,7 +324,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -355,7 +355,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -377,7 +377,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -397,7 +397,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -414,10 +414,10 @@ describe('POST /amm/trade', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -429,7 +429,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'WBNB', base: 'BITCOIN', @@ -449,10 +449,10 @@ describe('POST /amm/trade', () => { patch(bsc, 'getTokenBySymbol', (symbol: string) => { if (symbol === 'WBNB') { return { - chainId: 97, + chainId: 56, name: 'WBNB', symbol: 'WBNB', - address: '0xae13d989dac2f0debff460ac112a837c89baa7cd', + address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, }; } else { @@ -464,7 +464,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'BITCOIN', base: 'WBNB', @@ -485,7 +485,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -505,7 +505,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -525,7 +525,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', @@ -545,7 +545,7 @@ describe('POST /amm/trade', () => { .post(`/amm/trade`) .send({ chain: 'binance-smart-chain', - network: 'testnet', + network: 'mainnet', connector: 'pancakeswap', quote: 'DAI', base: 'WBNB', diff --git a/test/connectors/pancakeswap/pancakeswap.test.ts b/test/connectors/pancakeswap/pancakeswap.test.ts index 61816d954e..342443a1ad 100644 --- a/test/connectors/pancakeswap/pancakeswap.test.ts +++ b/test/connectors/pancakeswap/pancakeswap.test.ts @@ -1,12 +1,8 @@ jest.useFakeTimers(); import { - Fetcher, - Pair, Percent, - Route, Token, - TokenAmount, - Trade, + CurrencyAmount, TradeType, } from '@pancakeswap/sdk'; import { BigNumber } from 'ethers'; @@ -20,23 +16,23 @@ let bsc: BinanceSmartChain; let pancakeswap: PancakeSwap; const WBNB = new Token( - 97, - '0xae13d989dac2f0debff460ac112a837c89baa7cd', + 56, + '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB' ); const DAI = new Token( - 97, - '0x8a9424745056Eb399FD19a0EC26A14316684e274', + 56, + '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', 18, 'DAI' ); beforeAll(async () => { - bsc = BinanceSmartChain.getInstance('testnet'); + bsc = BinanceSmartChain.getInstance('mainnet'); patchEVMNonceManager(bsc.nonceManager); await bsc.init(); - pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'testnet'); + pancakeswap = PancakeSwap.getInstance('binance-smart-chain', 'mainnet'); await pancakeswap.init(); }); @@ -48,37 +44,38 @@ afterAll(async () => { await bsc.close(); }); -const patchFetchPairData = () => { - patch(Fetcher, 'fetchPairData', () => { - return new Pair( - new TokenAmount(WBNB, '2000000000000000000'), - new TokenAmount(DAI, '1000000000000000000') - ); - }); -}; - -const patchTrade = (key: string, error?: Error) => { - patch(Trade, key, () => { - if (error) return []; - const WBNB_DAI = new Pair( - new TokenAmount(WBNB, '2000000000000000000'), - new TokenAmount(DAI, '1000000000000000000') - ); - const DAI_TO_WBNB = new Route([WBNB_DAI], DAI); - return [ - new Trade( - DAI_TO_WBNB, - new TokenAmount(DAI, '1000000000000000'), - TradeType.EXACT_INPUT - ), - ]; - }); -}; +// const patchFetchPairData = () => { +// patch(Fetcher, 'fetchPairData', () => { +// return new Pair( +// CurrencyAmount.fromRawAmount(WBNB, '2000000000000000000'), +// CurrencyAmount.fromRawAmount(DAI, '1000000000000000000') +// ); +// }); +// }; + +// const patchTrade = (key: string, error?: Error) => { +// patch(Trade, key, () => { +// if (error) return []; +// const WBNB_DAI = new Pair( +// CurrencyAmount.fromRawAmount(WBNB, '2000000000000000000'), +// CurrencyAmount.fromRawAmount(DAI, '1000000000000000000') +// ); +// const DAI_TO_WBNB = new Route([WBNB_DAI], DAI, WBNB); +// return [ +// new Trade( +// DAI_TO_WBNB, +// CurrencyAmount.fromRawAmount(DAI, '1000000000000000'), +// TradeType.EXACT_INPUT +// ), +// ]; +// }); +// }; describe('verify PancakeSwap estimateSellTrade', () => { it('Should return an ExpectedTrade when available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactIn'); + patch(pancakeswap, "getBestTrade", async () => { + return { tradeType: TradeType.EXACT_INPUT, inputAmount: CurrencyAmount.fromRawAmount(WBNB, "10000"), outputAmount: CurrencyAmount.fromRawAmount(DAI, "100000"), routes: [] }; + }) const expectedTrade = await pancakeswap.estimateSellTrade( WBNB, @@ -90,8 +87,10 @@ describe('verify PancakeSwap estimateSellTrade', () => { }); it('Should throw an error if no pair is available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactIn', new Error('error getting trade')); + + patch(pancakeswap, "getBestTrade", async () => { + return null; + }) await expect(async () => { await pancakeswap.estimateSellTrade(WBNB, DAI, BigNumber.from(1)); @@ -101,8 +100,9 @@ describe('verify PancakeSwap estimateSellTrade', () => { describe('verify PancakeSwap estimateBuyTrade', () => { it('Should return an ExpectedTrade when available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactOut'); + patch(pancakeswap, "getBestTrade", async () => { + return { tradeType: TradeType.EXACT_OUTPUT, inputAmount: CurrencyAmount.fromRawAmount(WBNB, "10000"), outputAmount: CurrencyAmount.fromRawAmount(DAI, "100000"), routes: [] }; + }) const expectedTrade = await pancakeswap.estimateBuyTrade( WBNB, @@ -114,8 +114,9 @@ describe('verify PancakeSwap estimateBuyTrade', () => { }); it('Should return an error if no pair is available', async () => { - patchFetchPairData(); - patchTrade('bestTradeExactOut', new Error('error getting trade')); + patch(pancakeswap, "getBestTrade", async () => { + return null; + }) await expect(async () => { await pancakeswap.estimateBuyTrade(WBNB, DAI, BigNumber.from(1)); diff --git a/yarn.lock b/yarn.lock index 6c5493e1fe..4968c37727 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1131,7 +1131,7 @@ "@ethersproject-xdc/keccak256" "file:vendor/@ethersproject-xdc/keccak256" "@ethersproject-xdc/logger" "file:vendor/@ethersproject-xdc/logger" "@ethersproject-xdc/properties" "file:vendor/@ethersproject-xdc/properties" - "@ethersproject-xdc/strings" "file:vendor/@ethersproject-xdc/strings" + "@ethersproject-xdc/strings" "file:vendor/@ethersproject-xdc/strings" "@ethersproject-xdc/abstract-provider@file:vendor/@ethersproject-xdc/abstract-provider": version "5.7.0" @@ -1875,7 +1875,7 @@ "@graphql-typed-document-node/core" "^3.1.1" tslib "^2.4.0" -"@graphql-typed-document-node/core@^3.1.1": +"@graphql-typed-document-node/core@^3.1.1", "@graphql-typed-document-node/core@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== @@ -2832,6 +2832,28 @@ resolved "https://registry.yarnpkg.com/@pancakeswap/chains/-/chains-0.3.0.tgz#e509dfd9c8387f76b893e3a0a5b835331752b32c" integrity sha512-a4U8pzfxQsnS0nUpvi8qL2X6l2C/IUTFJcmt3UNAQv2L2CPUSryt1rLarG91O1Bb/UMfv90UCPrXJcHWpaYSMg== +"@pancakeswap/multicall@3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@pancakeswap/multicall/-/multicall-3.3.2.tgz#deaf60cfa0c25b76fbbd0c38333cee5b0110849a" + integrity sha512-H4DOyTYEXs49tgR0QrxkjJmDImxSu5rTJyaRc2kEDluU1kDsFffPiX4nrnewEmmvYFxAfin5jn9sp6D7dV7jEg== + dependencies: + "@pancakeswap/chains" "0.3.0" + "@pancakeswap/sdk" "5.7.3" + viem "1.19.9" + +"@pancakeswap/sdk@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-5.1.0.tgz#ab15c565c081d62cf0d186d18c7fcac114a6b192" + integrity sha512-/vFAR8zzyWT9n0twC0MdnkV7QOr40IAgC48wkbL2S07Anjgkd38dkRfP8X3dYCsY8aGDT+c9cCJmGBdte82Lng== + dependencies: + "@pancakeswap/swap-sdk-core" "1.0.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + viem "^1.2.9" + "@pancakeswap/sdk@5.7.3": version "5.7.3" resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-5.7.3.tgz#973ac77f0ac9920dea4c711f2bea396a88efdeb5" @@ -2846,18 +2868,41 @@ toformat "^2.0.0" viem "1.19.9" -"@pancakeswap/sdk@^2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-2.4.5.tgz#783c02efc7ca89d2297b0b07040639d90c3e610d" - integrity sha512-qfHOPGXitDQ5y1dmYloe6UQe/0Ki0enow4MLtr5W49Jl7ZXr8oB6XV7usYtT63R7vqLSKY83rd8Wyw18vxP7dA== +"@pancakeswap/sdk@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-4.0.0.tgz#6068bf3c1b031ef13f4b223ee3cbeff7ddbb5d96" + integrity sha512-gUQr3svj/gk85ef7Dztu1itK0HqfUYDki8d4WM4SpkIeqebo3r9DFuBJ7G6tlQaefNthQF0Loi1HB+wLP+2o3w== dependencies: + "@pancakeswap/swap-sdk-core" "1.0.0" big.js "^5.2.2" decimal.js-light "^2.5.0" - jsbi "^3.1.4" tiny-invariant "^1.1.0" tiny-warning "^1.0.3" toformat "^2.0.0" +"@pancakeswap/smart-router@^4.2.1": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@pancakeswap/smart-router/-/smart-router-4.11.1.tgz#e5a1055c5427f0e41bc3de454efd1044e0798826" + integrity sha512-9iqyk+5GaHLB3CqXtCYbJ6OpDfROcI2yukojzXKyngTR6IGfpUGh9Tg2Ggh6TOgwDCEqD3dhDc1BooAvkceOIA== + dependencies: + "@pancakeswap/chains" "0.3.0" + "@pancakeswap/multicall" "3.3.2" + "@pancakeswap/sdk" "5.7.3" + "@pancakeswap/swap-sdk-core" "1.0.0" + "@pancakeswap/token-lists" "0.0.9" + "@pancakeswap/tokens" "0.5.6" + "@pancakeswap/v3-sdk" "3.7.2" + async-retry "^1.3.1" + debug "^4.3.4" + graphql "^16.8.1" + graphql-request "5.0.0" + lodash "^4.17.21" + mnemonist "^0.38.3" + stats-lite "^2.2.0" + tiny-invariant "^1.3.0" + viem "1.19.9" + zod "^3.22.3" + "@pancakeswap/swap-sdk-core@1.0.0", "@pancakeswap/swap-sdk-core@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@pancakeswap/swap-sdk-core/-/swap-sdk-core-1.0.0.tgz#30ab333943ecca5484cd10cbc493841fb553728d" @@ -2869,6 +2914,15 @@ tiny-warning "^1.0.3" toformat "^2.0.0" +"@pancakeswap/token-lists@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@pancakeswap/token-lists/-/token-lists-0.0.8.tgz#a168cb8cdc7bf224812cc0e7f0382c46f3b93166" + integrity sha512-KTq31adfPqwqpHBLZ/rRkVlFpVIehB8UcmsuZpY1hjXqdNXUf5lO/Kad4tIxYtIlx9nrWM6R1laYxfISGzAMYQ== + dependencies: + "@pancakeswap/swap-sdk-core" "1.0.0" + ajv "^6.12.3" + lodash "^4.17.21" + "@pancakeswap/token-lists@0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/@pancakeswap/token-lists/-/token-lists-0.0.9.tgz#ffa2e7eb4e8d0ef1b8984b22d8b2fb529787693f" @@ -2887,6 +2941,14 @@ "@pancakeswap/sdk" "5.7.3" "@pancakeswap/token-lists" "0.0.9" +"@pancakeswap/tokens@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/tokens/-/tokens-0.1.6.tgz#c6f59c246bdc9f37322101acc2be700aee9b584b" + integrity sha512-JrHNVyBIxSRD62Prn+yeMbxcZdLs5yFY1Y1Xaex6+kCxDz/WNAKI+/Q9WnJZ7m/jsfuB/vjJN89gqCxGAc/VVw== + dependencies: + "@pancakeswap/sdk" "5.1.0" + "@pancakeswap/token-lists" "0.0.8" + "@pancakeswap/v3-core@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@pancakeswap/v3-core/-/v3-core-1.0.2.tgz#f5abf6a98535f33edebbfc11ab40b4fdcee51420" @@ -2903,7 +2965,7 @@ "@uniswap/v2-core" "1.0.1" base64-sol "1.0.1" -"@pancakeswap/v3-sdk@^3.7.0": +"@pancakeswap/v3-sdk@3.7.2", "@pancakeswap/v3-sdk@^3.7.0": version "3.7.2" resolved "https://registry.yarnpkg.com/@pancakeswap/v3-sdk/-/v3-sdk-3.7.2.tgz#ce475303802ada6c9b7d6781529b0c23033f638c" integrity sha512-8uSMUaDo69zx+PhODH1TiTdPvuaTlLzyyudpODV8Ig6LKWO2owVIRuhXwQHKXE8N3/0j7JN6ejJrM/sbY3kadA== @@ -8577,6 +8639,16 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphql-request@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.0.0.tgz#7504a807d0e11be11a3c448e900f0cc316aa18ef" + integrity sha512-SpVEnIo2J5k2+Zf76cUkdvIRaq5FMZvGQYnA4lUWYbc99m+fHh4CZYRRO/Ff4tCLQ613fzCm3SiDT64ubW5Gyw== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" + graphql-request@^3.4.0: version "3.7.0" resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.7.0.tgz#c7406e537084f8b9788541e3e6704340ca13055b" @@ -8586,6 +8658,14 @@ graphql-request@^3.4.0: extract-files "^9.0.0" form-data "^3.0.0" +graphql-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f" + integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw== + dependencies: + "@graphql-typed-document-node/core" "^3.2.0" + cross-fetch "^3.1.5" + graphql-tag@^2.11.0, graphql-tag@^2.12.6: version "2.12.6" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" @@ -8603,6 +8683,11 @@ graphql@^16.3.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +graphql@^16.8.1: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== + growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" @@ -14585,6 +14670,20 @@ viem@^0.3.x: isomorphic-ws "5.0.0" ws "8.12.0" +viem@^1.2.9: + version "1.19.15" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.19.15.tgz#0f2307632fa0ef10dfab2d8fdd71fbb842a0a4f5" + integrity sha512-rc87AkyrUUsoOAgMNYP+X/wN4GYwbhP87DkmsqQCYKxxQyzTX0+yliKs6Bxljbjr8ybU72GOb12Oyus6393AjQ== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "0.9.8" + isows "1.0.3" + ws "8.13.0" + vlq@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/vlq/-/vlq-2.0.4.tgz#6057b85729245b9829e3cc7755f95b228d4fe041" @@ -15520,3 +15619,8 @@ zen-observable@0.8.15: version "0.8.15" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== + +zod@^3.22.3: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==