diff --git a/wormhole-connect/src/routes/ntt/nttBase.ts b/wormhole-connect/src/routes/ntt/nttBase.ts index 6d3f8eeb0..83864ff6d 100644 --- a/wormhole-connect/src/routes/ntt/nttBase.ts +++ b/wormhole-connect/src/routes/ntt/nttBase.ts @@ -38,7 +38,7 @@ import { WormholeTransceiver, getMessageEvm } from './chains/evm'; import { NttManagerSolana, getMessageSolana } from './chains/solana'; import { formatGasFee } from 'routes/utils'; import { NO_INPUT } from 'utils/style'; -import { estimateAverageGasFee } from 'utils/gas'; +import { estimateAverageGasFee } from '../utils'; import config from 'config'; import { getNttGroupKey, diff --git a/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts b/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts index 0256948b6..820ce5ea8 100644 --- a/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts +++ b/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts @@ -65,7 +65,7 @@ import { } from './utils'; import { PorticoBridgeState, PorticoSwapAmounts } from 'store/porticoBridge'; import { TokenPrices } from 'store/tokenPrices'; -import { estimateAverageGasFee } from 'utils/gas'; +import { estimateAverageGasFee } from '../utils'; export abstract class PorticoBridge extends BaseRoute { readonly NATIVE_GAS_DROPOFF_SUPPORTED: boolean = false; diff --git a/wormhole-connect/src/routes/utils.ts b/wormhole-connect/src/routes/utils.ts index 8da44cce3..b725453cc 100644 --- a/wormhole-connect/src/routes/utils.ts +++ b/wormhole-connect/src/routes/utils.ts @@ -5,7 +5,7 @@ import { ParsedMessage as SdkParsedMessage, ParsedRelayerMessage as SdkParsedRelayerMessage, } from '@wormhole-foundation/wormhole-connect-sdk'; -import { BigNumber, utils } from 'ethers'; +import { BigNumber, BigNumberish, utils } from 'ethers'; import config from 'config'; import { toFixedDecimals } from 'utils/balance'; import { @@ -122,3 +122,13 @@ export const isIlliquidDestToken = ( export const isNttRoute = (route?: Route) => { return route === Route.NttManual || route === Route.NttRelay; }; + +export const estimateAverageGasFee = async ( + chain: ChainName | ChainId, + gasLimit: BigNumberish, +): Promise => { + const provider = config.wh.mustGetProvider(chain); + const gasPrice = await provider.getGasPrice(); + // This is a naive estimate 30% higher than what the oracle says + return gasPrice.mul(gasLimit).mul(130).div(100); +}; diff --git a/wormhole-connect/src/utils/gas.ts b/wormhole-connect/src/utils/gas.ts index eab549e93..726f40eb5 100644 --- a/wormhole-connect/src/utils/gas.ts +++ b/wormhole-connect/src/utils/gas.ts @@ -1,4 +1,4 @@ -import { BigNumber, BigNumberish, utils } from 'ethers'; +import { BigNumber, utils } from 'ethers'; import { ChainName, ChainId, @@ -78,13 +78,3 @@ export const estimateClaimGas = async ( if (!gas) throw new Error('could not estimate send gas'); return formatGasFee(destChain, gas); }; - -export const estimateAverageGasFee = async ( - chain: ChainName | ChainId, - gasLimit: BigNumberish, -): Promise => { - const provider = config.wh.mustGetProvider(chain); - const gasPrice = await provider.getGasPrice(); - // This is a naive estimate 30% higher than what the oracle says - return gasPrice.mul(gasLimit).mul(130).div(100); -}; diff --git a/wormhole-connect/tests/ci/routes.test.ts b/wormhole-connect/tests/ci/routes.test.ts new file mode 100644 index 000000000..423a8dfdf --- /dev/null +++ b/wormhole-connect/tests/ci/routes.test.ts @@ -0,0 +1,95 @@ +import { ChainId, ChainName } from '@wormhole-foundation/wormhole-connect-sdk'; +import { describe, expect, test } from 'vitest'; +import { setConfig } from 'config'; +import { Route, WormholeConnectConfig } from 'config/types'; +import { getRouteImpls } from 'routes/mappings'; + +describe('supported routes', () => { + type testCase = [ + sourceToken: string, + destToken: string, + sourceChain: ChainName | ChainId, + destChain: ChainName | ChainId, + route: Route[], + ]; + + setConfig({ env: 'mainnet' }); + + const testCases: testCase[] = [ + // Token bridge + ['ETH', 'WETH', 'ethereum', 'bsc', [Route.Bridge, Route.Relay]], + ['DAI', 'DAI', 'ethereum', 'polygon', [Route.Bridge, Route.Relay]], + ['WSOL', 'WSOL', 'solana', 'ethereum', [Route.Bridge]], + ['WSOL', 'WSOL', 'ethereum', 'solana', [Route.Bridge]], + ['ETH', 'WETH', 'ethereum', 'avalanche', [Route.Bridge, Route.Relay]], + ['BNB', 'WBNB', 'bsc', 'polygon', [Route.Bridge, Route.Relay]], + ['FTM', 'WFTM', 'fantom', 'polygon', [Route.Bridge, Route.Relay]], + ['CELO', 'CELO', 'celo', 'polygon', [Route.Bridge, Route.Relay]], + // ETH bridge + ['ETH', 'WETHbsc', 'ethereum', 'bsc', [Route.ETHBridge]], + ['ETH', 'WETHpolygon', 'ethereum', 'polygon', [Route.ETHBridge]], + ['WETHarbitrum', 'WETHpolygon', 'arbitrum', 'polygon', [Route.ETHBridge]], + ['WETHpolygon', 'WETHarbitrum', 'polygon', 'arbitrum', [Route.ETHBridge]], + // wstETHBridge + ['wstETH', 'wstETHpolygon', 'ethereum', 'polygon', [Route.wstETHBridge]], + ['wstETH', 'wstETHarbitrum', 'ethereum', 'arbitrum', [Route.wstETHBridge]], + // NTT + [ + 'USDCeth', + 'USDCfantom', + 'ethereum', + 'fantom', + [Route.NttManual, Route.NttRelay], + ], + // CCTP + [ + 'USDCeth', + 'USDCarbitrum', + 'ethereum', + 'arbitrum', + [Route.CCTPManual, Route.CCTPRelay], + ], + [ + 'USDCarbitrum', + 'USDCpolygon', + 'arbitrum', + 'polygon', + [Route.CCTPManual, Route.CCTPRelay], + ], + ['USDCeth', 'USDCsol', 'ethereum', 'solana', [Route.CCTPManual]], + ['USDCavax', 'USDCsol', 'avalanche', 'solana', [Route.CCTPManual]], + // TBTC + ['tBTC', 'tBTCpolygon', 'ethereum', 'polygon', [Route.TBTC]], + ['tBTCoptimism', 'tBTC', 'optimism', 'ethereum', [Route.TBTC]], + ['tBTCpolygon', 'tBTCoptimism', 'polygon', 'optimism', [Route.TBTC]], + ['tBTCarbitrum', 'tBTCoptimism', 'arbitrum', 'optimism', [Route.TBTC]], + // Cosmos Gateway + ['CELO', 'CELO', 'osmosis', 'celo', [Route.CosmosGateway]], + ['CELO', 'CELO', 'osmosis', 'moonbeam', [Route.CosmosGateway]], + ['GLMR', 'WGLMR', 'moonbeam', 'kujira', [Route.CosmosGateway]], + ]; + + for (let [ + sourceToken, + destToken, + sourceChain, + destChain, + routes, + ] of testCases) { + for (let route of routes) { + test(`${route} ${sourceChain}:${sourceToken} -> ${destChain}:${destToken}`, async () => { + const r = getRouteImpls(route).v1; + + const isSupported = await r.isRouteSupported( + sourceToken, + destToken, + '1.0', + sourceChain, + destChain, + ); + + expect(isSupported).toBeTruthy(); + }); + } + } +});