From bb385626bed331ad6059b362fbff36d43d584d07 Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Fri, 28 Jun 2024 11:58:58 -0400 Subject: [PATCH 1/7] handle native token balances correctly --- .../src/hooks/useGetTokenBalances.ts | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/wormhole-connect/src/hooks/useGetTokenBalances.ts b/wormhole-connect/src/hooks/useGetTokenBalances.ts index b6ec02efe..d3476fdcb 100644 --- a/wormhole-connect/src/hooks/useGetTokenBalances.ts +++ b/wormhole-connect/src/hooks/useGetTokenBalances.ts @@ -82,22 +82,32 @@ const useGetTokenBalances = ( lastUpdated: now, }; + console.log(tokenConfig); + try { let address: string | null = null; - const foreignAddress = await getForeignTokenAddress( - config.sdkConverter.toTokenIdV2(tokenConfig), - chainV2, - ); - - if (foreignAddress) { - address = foreignAddress.toString(); + if ( + tokenConfig.nativeChain === chain && + tokenConfig.tokenId === undefined + ) { + tokenAddresses.push('native'); + tokenIdMapping['native'] = tokenConfig; } else { - console.error('no fa', tokenConfig); - continue; + const foreignAddress = await getForeignTokenAddress( + config.sdkConverter.toTokenIdV2(tokenConfig), + chainV2, + ); + + if (foreignAddress) { + address = foreignAddress.toString(); + } else { + console.error('no fa', tokenConfig); + continue; + } + tokenIdMapping[address] = tokenConfig; + tokenAddresses.push(address); } - tokenIdMapping[address] = tokenConfig; - tokenAddresses.push(address); } catch (e) { // TODO SDKV2 SUI ISNT WORKING console.error(e); From 07fcd1e0e2788d41b4ddb6907f25e3f9b1cd57b5 Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Fri, 28 Jun 2024 11:59:13 -0400 Subject: [PATCH 2/7] toSendResult looks at platform --- wormhole-connect/src/routes/sdkv2/signer.ts | 34 +++++++++++++-------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/wormhole-connect/src/routes/sdkv2/signer.ts b/wormhole-connect/src/routes/sdkv2/signer.ts index 9e472b2ad..964e5bfea 100644 --- a/wormhole-connect/src/routes/sdkv2/signer.ts +++ b/wormhole-connect/src/routes/sdkv2/signer.ts @@ -72,18 +72,28 @@ export class SDKv2Signer } private toSendResult(tx: UnsignedTransaction): SendResult { - let serialized = ethers6.Transaction.from({ - to: tx.transaction.to, - data: tx.transaction.data, - }).unsignedSerialized; - let tx5: ethers5.Transaction = ethers5.utils.parseTransaction(serialized); - let unsignedTx: Deferrable = { - to: tx5.to, - type: tx5.type as number, - chainId: tx5.chainId, - data: tx5.data, - }; - return unsignedTx as SendResult; + const platform = chainToPlatform(tx.chain); + + switch (platform) { + case 'Evm': + // TODO switch multi-provider to ethers 6 + let serialized = ethers6.Transaction.from({ + to: tx.transaction.to, + data: tx.transaction.data, + }).unsignedSerialized; + let tx5: ethers5.Transaction = + ethers5.utils.parseTransaction(serialized); + let unsignedTx: Deferrable = { + to: tx5.to, + type: tx5.type as number, + chainId: tx5.chainId, + data: tx5.data, + }; + return unsignedTx as SendResult; + default: + console.warn(`toSendResult is unimplemented for platform ${platform}`); + return tx as SendResult; + } } chain() { From 4d005e57b59ea3394147ea3792c196df2dacc773 Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Fri, 28 Jun 2024 15:41:00 -0400 Subject: [PATCH 3/7] rip out portico... --- .../src/hooks/useComputeDestinationTokens.ts | 12 +- .../src/hooks/useGetTokenBalances.ts | 3 +- .../src/hooks/usePorticoSwapInfo.ts | 3 +- .../src/routes/abstracts/routeAbstract.ts | 2 +- wormhole-connect/src/routes/mappings.ts | 10 +- wormhole-connect/src/routes/operator.ts | 2 +- .../src/routes/porticoBridge/abis.ts | 12 - .../src/routes/porticoBridge/consts.ts | 9 - .../src/routes/porticoBridge/ethBridge.ts | 12 - .../src/routes/porticoBridge/index.ts | 1 - .../src/routes/porticoBridge/porticoBridge.ts | 1039 ----------------- .../src/routes/porticoBridge/types.ts | 83 -- .../src/routes/porticoBridge/uniswapQuoter.ts | 38 - .../src/routes/porticoBridge/utils.ts | 219 +--- .../src/routes/porticoBridge/wstETHBridge.ts | 12 - wormhole-connect/src/routes/sdkv2/route.ts | 14 +- wormhole-connect/src/utils/errors.ts | 6 +- wormhole-connect/src/utils/sdkv2.ts | 2 +- wormhole-connect/src/views/Bridge/Bridge.tsx | 4 +- wormhole-connect/src/views/Bridge/Send.tsx | 7 +- .../src/views/Redeem/AddToWallet.tsx | 12 +- .../src/views/Redeem/BridgeComplete.tsx | 18 +- .../src/views/Redeem/PorticoSwapFailed.tsx | 2 + wormhole-connect/src/views/Redeem/Stepper.tsx | 6 +- 24 files changed, 63 insertions(+), 1465 deletions(-) delete mode 100644 wormhole-connect/src/routes/porticoBridge/abis.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/consts.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/ethBridge.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/index.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/porticoBridge.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/types.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/uniswapQuoter.ts delete mode 100644 wormhole-connect/src/routes/porticoBridge/wstETHBridge.ts diff --git a/wormhole-connect/src/hooks/useComputeDestinationTokens.ts b/wormhole-connect/src/hooks/useComputeDestinationTokens.ts index c8f57fcab..e2c2b63af 100644 --- a/wormhole-connect/src/hooks/useComputeDestinationTokens.ts +++ b/wormhole-connect/src/hooks/useComputeDestinationTokens.ts @@ -6,18 +6,18 @@ import { setDestToken, setSupportedDestTokens, setAllSupportedDestTokens, - getNativeVersionOfToken, + //getNativeVersionOfToken, } from 'store/transferInput'; import type { Route } from 'config/types'; import type { ChainName } from 'sdklegacy'; -import { isPorticoRoute } from 'routes/porticoBridge/utils'; -import { ETHBridge } from 'routes/porticoBridge/ethBridge'; -import { wstETHBridge } from 'routes/porticoBridge/wstETHBridge'; +//import { isPorticoRoute } from 'routes/porticoBridge/utils'; +//import { ETHBridge } from 'routes/porticoBridge/ethBridge'; +//import { wstETHBridge } from 'routes/porticoBridge/wstETHBridge'; import RouteOperator from 'routes/operator'; -import { getWrappedToken } from 'utils'; +//import { getWrappedToken } from 'utils'; type Props = { sourceChain: ChainName | undefined; @@ -86,6 +86,7 @@ export const useComputeDestinationTokens = (props: Props): void => { } } + /* // If the source token is supported by a Portico bridge route, // then select the native version on the dest chain if (sourceToken && destChain && (!route || isPorticoRoute(route))) { @@ -107,6 +108,7 @@ export const useComputeDestinationTokens = (props: Props): void => { } } } + */ }; computeDestTokens(); diff --git a/wormhole-connect/src/hooks/useGetTokenBalances.ts b/wormhole-connect/src/hooks/useGetTokenBalances.ts index d3476fdcb..634ae332b 100644 --- a/wormhole-connect/src/hooks/useGetTokenBalances.ts +++ b/wormhole-connect/src/hooks/useGetTokenBalances.ts @@ -68,6 +68,7 @@ const useGetTokenBalances = ( needsUpdate.push(token as TokenConfigWithId); } } + if (needsUpdate.length > 0) { try { const wh = await getWormholeContextV2(); @@ -82,8 +83,6 @@ const useGetTokenBalances = ( lastUpdated: now, }; - console.log(tokenConfig); - try { let address: string | null = null; diff --git a/wormhole-connect/src/hooks/usePorticoSwapInfo.ts b/wormhole-connect/src/hooks/usePorticoSwapInfo.ts index ecce9483f..7d3d3d668 100644 --- a/wormhole-connect/src/hooks/usePorticoSwapInfo.ts +++ b/wormhole-connect/src/hooks/usePorticoSwapInfo.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +/*import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import RouteOperator from 'routes/operator'; import { PorticoBridge } from 'routes/porticoBridge'; @@ -88,3 +88,4 @@ export const usePorticoSwapInfo = (): void => { dispatch, ]); }; +*/ diff --git a/wormhole-connect/src/routes/abstracts/routeAbstract.ts b/wormhole-connect/src/routes/abstracts/routeAbstract.ts index fd2b84404..d21314a66 100644 --- a/wormhole-connect/src/routes/abstracts/routeAbstract.ts +++ b/wormhole-connect/src/routes/abstracts/routeAbstract.ts @@ -135,7 +135,7 @@ export abstract class RouteAbstract { public abstract getMaxSendAmount(): number; public abstract send( - token: TokenId | 'native', + token: TokenConfig, amount: string, sendingChain: ChainName | ChainId, senderAddress: string, diff --git a/wormhole-connect/src/routes/mappings.ts b/wormhole-connect/src/routes/mappings.ts index 4ec89f3ea..1211fb3f5 100644 --- a/wormhole-connect/src/routes/mappings.ts +++ b/wormhole-connect/src/routes/mappings.ts @@ -5,11 +5,11 @@ import { routes } from '@wormhole-foundation/sdk'; import { SDKv2Route } from './sdkv2/route'; // Legacy routes -import { RouteAbstract } from './abstracts/routeAbstract'; -import { ETHBridge } from './porticoBridge/ethBridge'; -import { wstETHBridge } from './porticoBridge/wstETHBridge'; +//import { RouteAbstract } from './abstracts/routeAbstract'; +//import { ETHBridge } from './porticoBridge/ethBridge'; +//import { wstETHBridge } from './porticoBridge/wstETHBridge'; -export function getRoute(route: Route): RouteAbstract { +export function getRoute(route: Route): SDKv2Route { switch (route) { // Migrated routes: case Route.Bridge: @@ -18,10 +18,12 @@ export function getRoute(route: Route): RouteAbstract { return new SDKv2Route(routes.AutomaticTokenBridgeRoute, Route.Bridge); // Legacy routes: + /* case Route.ETHBridge: return new ETHBridge(); case Route.wstETHBridge: return new wstETHBridge(); + */ // TODO SDKV2 default: return new SDKv2Route(routes.TokenBridgeRoute, Route.Bridge); diff --git a/wormhole-connect/src/routes/operator.ts b/wormhole-connect/src/routes/operator.ts index 8a8913d86..198b12f54 100644 --- a/wormhole-connect/src/routes/operator.ts +++ b/wormhole-connect/src/routes/operator.ts @@ -428,7 +428,7 @@ export class Operator { async send( route: Route, - token: TokenId | 'native', + token: TokenConfig, amount: string, sendingChain: ChainName | ChainId, senderAddress: string, diff --git a/wormhole-connect/src/routes/porticoBridge/abis.ts b/wormhole-connect/src/routes/porticoBridge/abis.ts deleted file mode 100644 index 5efcc6062..000000000 --- a/wormhole-connect/src/routes/porticoBridge/abis.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ethers } from 'ethers5'; - -export const porticoAbi = new ethers.utils.Interface([ - 'function start((bytes32,address,address,address,address,address,uint256,uint256,uint256,uint256)) returns (address,uint16,uint64)', -]); - -export const porticoSwapFinishedEvent = - '0xc2addcb063016f6dc1647fc8cd7206c3436cc4293c4acffe4feac288459ca7fc'; - -export const uniswapQuoterV2Abi = new ethers.utils.Interface([ - 'function quoteExactInputSingle((address,address,uint256,uint24,uint160)) public view returns (uint256,uint160,uint32,uint256)', -]); diff --git a/wormhole-connect/src/routes/porticoBridge/consts.ts b/wormhole-connect/src/routes/porticoBridge/consts.ts deleted file mode 100644 index 8ea2f2a3a..000000000 --- a/wormhole-connect/src/routes/porticoBridge/consts.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const CREATE_ORDER_API_URL = 'https://thermae.fly.dev/api/order/create'; -export const FEE_TIER = 100; -export const SLIPPAGE_BPS = 15; // 0.15% -export const BPS_PER_HUNDRED_PERCENT = 10000; -export const RELAYER_FEE_API_URL = - 'https://gfx.relayers.xlabs.xyz/api/v1/swap/quote'; -export const OKU_TRADE_BASE_URL = 'https://oku.trade/app'; -export const SWAP_ERROR = - 'This transaction will not succeed due to price movement.'; diff --git a/wormhole-connect/src/routes/porticoBridge/ethBridge.ts b/wormhole-connect/src/routes/porticoBridge/ethBridge.ts deleted file mode 100644 index f99dbec1a..000000000 --- a/wormhole-connect/src/routes/porticoBridge/ethBridge.ts +++ /dev/null @@ -1,12 +0,0 @@ -import config from 'config'; -import { PorticoBridge } from './porticoBridge'; -import { Route } from 'config/types'; - -export class ETHBridge extends PorticoBridge { - readonly TYPE: Route = Route.ETHBridge; - static readonly SUPPORTED_TOKENS: string[] = ['ETH', 'WETH']; - - constructor() { - super(ETHBridge.SUPPORTED_TOKENS, config.ethBridgeMaxAmount); - } -} diff --git a/wormhole-connect/src/routes/porticoBridge/index.ts b/wormhole-connect/src/routes/porticoBridge/index.ts deleted file mode 100644 index 13b7cb251..000000000 --- a/wormhole-connect/src/routes/porticoBridge/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './porticoBridge'; diff --git a/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts b/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts deleted file mode 100644 index eeb09d051..000000000 --- a/wormhole-connect/src/routes/porticoBridge/porticoBridge.ts +++ /dev/null @@ -1,1039 +0,0 @@ -import { BigNumber } from 'ethers5'; -import { - ChainId, - ChainName, - /*EthContext,*/ - TokenId, - //WormholeContext, -} from 'sdklegacy'; -import { TokenConfig } from 'config/types'; -import { - SignedMessage, - UnsignedMessage, - TokenTransferMessage, - SignedTokenTransferMessage, - TransferDisplayData, - TransferInfoBaseParams, - RelayerFee, -} from '../types'; -import { - /*fetchGlobalTx,*/ fetchVaa /*getEmitterAndSequence*/, -} from 'utils/vaa'; -import { hexZeroPad, hexlify, parseUnits } from 'ethers5/lib/utils.js'; -import { BaseRoute } from '../bridge'; -import { PayloadType, /*isEvmChain,*/ toChainId, toChainName } from 'utils/sdk'; -import config from 'config'; -import { - MAX_DECIMALS, - calculateUSDPrice, - getChainConfig, - getDisplayName, - getGasToken, - getTokenById, - getTokenDecimals, - getWrappedToken, - isEqualCaseInsensitive, - toNormalizedDecimals, -} from 'utils'; -import { - //CreateOrderRequest, - //CreateOrderResponse, - PorticoTransferDestInfo, - PorticoDestTxInfo, - RelayerQuoteRequest, - RelayerQuoteResponse, -} from './types'; -import axios from 'axios'; -//import { TransferWallet, signAndSendTransaction } from 'utils/wallet'; -import { porticoSwapFinishedEvent } from './abis'; -import { getQuote } from './uniswapQuoter'; -import { toDecimals } from 'utils/balance'; -import { getForeignTokenAddress } from 'utils/sdkv2'; -import { - BPS_PER_HUNDRED_PERCENT, - //CREATE_ORDER_API_URL, - FEE_TIER, - RELAYER_FEE_API_URL, - SLIPPAGE_BPS, - //SWAP_ERROR, -} from './consts'; -import { adaptParsedMessage } from 'routes/utils'; -import { TransferDestInfoParams } from 'routes/relay'; -import { NO_INPUT } from 'utils/style'; -import { - getCanonicalTokenAddress, - parsePorticoPayload, - parseTradeParameters, - //validateCreateOrderResponse, -} from './utils'; -import { PorticoBridgeState, PorticoSwapAmounts } from 'store/porticoBridge'; -import { TokenPrices } from 'store/tokenPrices'; -import { estimateAverageGasFee } from '../utils'; - -export abstract class PorticoBridge extends BaseRoute { - readonly NATIVE_GAS_DROPOFF_SUPPORTED: boolean = false; - readonly AUTOMATIC_DEPOSIT: boolean = false; - - constructor( - protected supportedTokenSymbols: string[], - protected maxAmount: number, - ) { - super(); - } - - isSupportedChain(chain: ChainName): boolean { - return false; - /* - * TODO SDKV2 - const { portico, uniswapQuoterV2 } = config.wh.getContracts(chain) || {}; - return !!(portico && uniswapQuoterV2); - */ - } - - async isSupportedSourceToken( - token?: TokenConfig, - destToken?: TokenConfig, - sourceChain?: ChainName | ChainId, - destChain?: ChainName | ChainId, - ): Promise { - if (!token || !sourceChain || !this.isSupportedToken(token, sourceChain)) { - return false; - } - if ( - destChain && - destToken && - !this.isSupportedToken(destToken, destChain) - ) { - return false; - } - return true; - } - - async isSupportedDestToken( - token?: TokenConfig, - sourceToken?: TokenConfig, - sourceChain?: ChainName | ChainId, - destChain?: ChainName | ChainId, - ): Promise { - if (!token || !destChain || !this.isSupportedToken(token, destChain)) { - return false; - } - if ( - sourceChain && - sourceToken && - !this.isSupportedToken(sourceToken, sourceChain) - ) { - return false; - } - return true; - } - - isSupportedToken(token: TokenConfig, chain: ChainName | ChainId): boolean { - return ( - this.isSupportedChain(token.nativeChain) && - this.supportedTokenSymbols.includes(token.symbol) && - toChainName(chain) === token.nativeChain - ); - } - - async isRouteSupported( - sourceToken: string, - destToken: string, - amount: string, - sourceChain: ChainName | ChainId, - destChain: ChainName | ChainId, - ): Promise { - if (!config.isMainnet || !config.routes.includes(this.TYPE)) { - return false; - } - const sourceTokenConfig = config.tokens[sourceToken]; - if ( - !sourceTokenConfig || - !this.isSupportedToken(sourceTokenConfig, sourceChain) - ) { - return false; - } - const destTokenConfig = config.tokens[destToken]; - if ( - !destTokenConfig || - !this.isSupportedToken(destTokenConfig, destChain) - ) { - return false; - } - return true; - } - - async computeSwapAmounts( - sendAmount: number, - token: string, - destToken: string, - sendingChain: ChainName | undefined, - recipientChain: ChainName | undefined, - ): Promise { - if (!sendAmount || !destToken || !sendingChain || !recipientChain) { - throw new Error('Invalid params'); - } - const startToken = getWrappedToken(config.tokens[token]); - if (!startToken.tokenId) { - throw new Error('Unable to get start token'); - } - const finishToken = getWrappedToken(config.tokens[destToken]); - if (!finishToken.tokenId) { - throw new Error('Unable to get finish token'); - } - const [startCanonicalToken, finishCanonicalToken] = await Promise.all([ - getCanonicalTokenAddress(startToken), - getCanonicalTokenAddress(finishToken), - ]); - - if (!startCanonicalToken) - throw new Error('Portico: Couldnt resolve start canonical token'); - if (!finishCanonicalToken) - throw new Error('Portico: Couldnt resolve finish canonical token'); - - const startTokenDecimals = getTokenDecimals( - toChainId(sendingChain), - startToken.tokenId, - ); - const parsedSendAmount = parseUnits( - sendAmount.toString(), - startTokenDecimals, - ); - const startQuote = await getQuote( - sendingChain, - startToken.tokenId.address, - startCanonicalToken, - parsedSendAmount, - FEE_TIER, - ); - const startSlippage = startQuote.amountOut - .mul(SLIPPAGE_BPS) - .div(BPS_PER_HUNDRED_PERCENT); - if (startSlippage.gte(startQuote.amountOut)) { - throw new Error('Start slippage too high'); - } - const minAmountStart = startQuote.amountOut.sub(startSlippage); - const finishQuote = await getQuote( - recipientChain, - finishCanonicalToken, - finishToken.tokenId.address, - minAmountStart, - FEE_TIER, - ); - const finishSlippage = finishQuote.amountOut - .mul(SLIPPAGE_BPS) - .div(BPS_PER_HUNDRED_PERCENT); - if (finishSlippage.gte(finishQuote.amountOut)) { - throw new Error('Finish slippage too high'); - } - const minAmountFinish = finishQuote.amountOut.sub(finishSlippage); - const amountFinishQuote = await getQuote( - recipientChain, - finishCanonicalToken, - finishToken.tokenId.address, - startQuote.amountOut, // no slippage - FEE_TIER, - ); - // the expected receive amount is the amount out from the swap - // minus 5bps slippage - const amountFinishSlippage = amountFinishQuote.amountOut - .mul(5) - .div(BPS_PER_HUNDRED_PERCENT); - if (amountFinishSlippage.gte(amountFinishQuote.amountOut)) { - throw new Error('Amount finish slippage too high'); - } - const amountFinish = amountFinishQuote.amountOut.sub(amountFinishSlippage); - if (amountFinish.lte(minAmountFinish)) { - throw new Error('Amount finish too low'); - } - return { - minAmountStart: minAmountStart.toString(), - minAmountFinish: minAmountFinish.toString(), - amountFinish: amountFinish.toString(), - }; - } - - async computeReceiveAmount( - sendAmount: number, - token: string, - destToken: string, - sendingChain: ChainName | undefined, - recipientChain: ChainName | undefined, - routeOptions: PorticoBridgeState, - ): Promise { - return this.computeReceiveAmountInternal( - sendAmount, - token, - destToken, - sendingChain, - recipientChain, - routeOptions, - false, - ); - } - - async computeReceiveAmountInternal( - sendAmount: number, - token: string, - destToken: string, - sendingChain: ChainName | undefined, - recipientChain: ChainName | undefined, - routeOptions: PorticoBridgeState, - includeFees = false, - ): Promise { - if ( - !sendAmount || - !destToken || - !sendingChain || - !recipientChain || - !routeOptions.relayerFee.data || - !routeOptions.swapAmounts.data || - !(await this.isRouteSupported( - token, - destToken, - sendAmount.toString(), - sendingChain, - recipientChain, - )) - ) { - throw new Error('Error computing receive amount'); - } - const finishToken = config.tokens[destToken]; - const minAmountFinish = BigNumber.from( - routeOptions.swapAmounts.data.minAmountFinish, - ); - // the relayer fee is paid out in the finish token - // the min amount finish must be greater than the relayer fee - // to pay it out - const relayerFee = BigNumber.from(routeOptions.relayerFee.data); - if (minAmountFinish.lte(relayerFee)) { - throw new Error(`Min amount too low, try increasing the send amount`); - } - const finishTokenDecimals = getTokenDecimals( - toChainId(recipientChain), - finishToken.tokenId, - ); - let amountFinish = BigNumber.from( - routeOptions.swapAmounts.data.amountFinish, - ); - if (includeFees) { - amountFinish = amountFinish.sub(relayerFee); - } - return Number(toDecimals(amountFinish, finishTokenDecimals, MAX_DECIMALS)); - } - - async computeReceiveAmountWithFees( - sendAmount: number, - token: string, - destToken: string, - sendingChain: ChainName | undefined, - recipientChain: ChainName | undefined, - routeOptions: PorticoBridgeState, - ): Promise { - return this.computeReceiveAmountInternal( - sendAmount, - token, - destToken, - sendingChain, - recipientChain, - routeOptions, - true, - ); - } - - async computeSendAmount( - receiveAmount: number | undefined, - routeOptions: any, - ): Promise { - if (!receiveAmount) return 0; - return receiveAmount; - } - - async validate( - token: TokenId | 'native', - amount: string, - sendingChain: ChainName | ChainId, - senderAddress: string, - recipientChain: ChainName | ChainId, - recipientAddress: string, - routeOptions: any, - ): Promise { - throw new Error('not implemented'); - } - - async estimateSendGas( - token: TokenId | 'native', - amount: string, - sendingChain: ChainName | ChainId, - senderAddress: string, - recipientChain: ChainName | ChainId, - recipientAddress: string, - routeOptions?: any, - ): Promise { - return await estimateAverageGasFee(sendingChain, 150_000); - } - - async estimateClaimGas( - destChain: ChainName | ChainId, - signedMessage?: SignedMessage, - ): Promise { - // Relayer pays gas on the destination chain - return BigNumber.from(0); - } - - getMinSendAmount( - token: TokenId | 'native', - recipientChain: ChainName | ChainId, - routeOptions: PorticoBridgeState, - ): number { - return 0; - } - - getMaxSendAmount(): number { - return this.maxAmount; - } - - async send( - token: TokenId | 'native', - amount: string, - sendingChain: ChainName | ChainId, - senderAddress: string, - recipientChain: ChainName | ChainId, - recipientAddress: string, - destToken: string, - routeOptions: PorticoBridgeState, - ): Promise { - /* - * TODO SDKV2 - if (!isEvmChain(sendingChain) || !isEvmChain(recipientChain)) { - throw new Error('Only EVM chains are supported'); - } - const { swapAmounts, relayerFee } = routeOptions; - if (!swapAmounts.data || swapAmounts.error) { - throw new Error('swapAmounts is required'); - } - if (!relayerFee.data || relayerFee.error) { - throw new Error('relayerFee is required'); - } - const { minAmountStart, minAmountFinish } = swapAmounts.data; - if (!minAmountStart || !minAmountFinish) { - throw new Error('Invalid min swap amount'); - } - if (BigNumber.from(minAmountStart).eq(0)) { - throw new Error('Invalid min swap amount'); - } - if (BigNumber.from(minAmountFinish).eq(0)) { - throw new Error('Invalid min swap amount'); - } - const sourceChainConfig = getChainConfig(sendingChain); - const destChainConfig = getChainConfig(recipientChain); - const sourceGasToken = getGasToken(sendingChain); - const isStartTokenNative = - token === 'native' || getTokenById(token)?.key === sourceGasToken.key; - const startToken = isStartTokenNative - ? getWrappedToken(sourceGasToken) - : getTokenById(token); - if (!startToken?.tokenId) { - throw new Error('Unsupported start token'); - } - const destGasToken = getGasToken(recipientChain); - const isDestTokenNative = - destToken === 'native' || destToken === destGasToken.key; - const finalToken = isDestTokenNative - ? getWrappedToken(destGasToken) - : config.tokens[destToken]; - if (!finalToken?.tokenId) { - throw new Error('Unsupported dest token'); - } - const porticoAddress = config.wh.mustGetContracts(sendingChain).portico; - if (!porticoAddress) { - throw new Error('Portico address not found'); - } - const destinationPorticoAddress = - config.wh.mustGetContracts(recipientChain).portico; - if (!destinationPorticoAddress) { - throw new Error('Destination portico address not found'); - } - const decimals = getTokenDecimals(toChainId(sendingChain), token); - const parsedAmount = parseUnits(amount, decimals); - - // Prevent user from transferring if the output amount is too low - const minThresholdAmountOutSlippage = parsedAmount - .mul(50) - .div(BPS_PER_HUNDRED_PERCENT); - if ( - BigNumber.from(minAmountFinish).lt( - parsedAmount.sub(minThresholdAmountOutSlippage), - ) - ) { - throw new Error('Output amount too low, please try again later.'); - } - - const context = config.wh.getContext( - sendingChain, - ) as WormholeContext; - const core = context.contracts.mustGetCore(sendingChain); - const messageFee = await core.messageFee(); - - // Create the order - const request: CreateOrderRequest = { - startingChainId: Number(sourceChainConfig.chainId), - startingToken: startToken.tokenId.address, - startingTokenAmount: parsedAmount.toString(), - destinationToken: finalToken.tokenId.address, - destinationAddress: recipientAddress, - destinationChainId: Number(destChainConfig.chainId), - relayerFee: relayerFee.data, - feeTierStart: FEE_TIER, - feeTierEnd: FEE_TIER, - minAmountStart, - minAmountEnd: minAmountFinish, - bridgeNonce: new Date().valueOf(), - shouldWrapNative: isStartTokenNative, - shouldUnwrapNative: isDestTokenNative, - porticoAddress, - destinationPorticoAddress, - }; - const response = await axios.post( - CREATE_ORDER_API_URL, - request, - ); - if (response.status !== 200) { - throw new Error(`Error creating order: ${response.statusText}`); - } - - // Validate the response - await validateCreateOrderResponse(response.data, request, startToken); - - // Approve the token if necessary - if (!isStartTokenNative) { - const sendingContext = config.wh.getContext( - sendingChain, - ) as WormholeContext; - await sendingContext.approve( - sendingChain, - porticoAddress, - startToken.tokenId.address, - parsedAmount, - ); - } - */ - - return 'TODO SDKV2'; - - /* - * TODO SDKV2 - // Sign and send the transaction - const signer = config.wh.mustGetSigner(sendingChain); - const transaction = { - to: porticoAddress, - data: response.data.transactionData, - value: messageFee.add(isStartTokenNative ? parsedAmount : 0), - }; - try { - const tx = await signer.sendTransaction(transaction); - const receipt = await tx.wait(); - const txId = await signAndSendTransaction( - toChainName(sendingChain), - receipt, - TransferWallet.SENDING, - ); - return txId; - } catch (e: any) { - let reason = ''; - if (e.reason?.startsWith('execution reverted: ')) { - reason = reason.substr('execution reverted: '.length); - } - const swapErrors = [ - 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT', - 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT', - 'Too little received', - 'Too much requested', - 'STF', - ]; - if (swapErrors.includes(reason)) { - throw new Error(SWAP_ERROR); - } - throw e; - } - */ - } - - async redeem( - destChain: ChainName | ChainId, - message: SignedTokenTransferMessage, - payer: string, - ): Promise { - /* - * TODO SDKV2 - // allow manual redeems in case it wasn't relayed - const signer = config.wh.mustGetSigner(destChain); - const { portico } = config.wh.mustGetContracts(destChain); - if (!portico) { - throw new Error('Portico address not found'); - } - const contract = new ethers.Contract( - portico, - ['function receiveMessageAndSwap(bytes)'], - signer, - ); - const transaction = - await contract.populateTransaction.receiveMessageAndSwap(message.vaa); - const tx = await signer.sendTransaction(transaction); - const receipt = await tx.wait(); - const txId = await signAndSendTransaction( - toChainName(destChain), - receipt, - TransferWallet.RECEIVING, - ); - return txId; - */ - return 'TODO SDKV2'; - } - - async getRelayerFee( - sourceChain: ChainName | ChainId, - destChain: ChainName | ChainId, - token: string, - destToken: string, - ): Promise { - if (!destToken) { - throw new Error('destToken is required'); - } - const startToken = getWrappedToken(config.tokens[token]); - if (!startToken.tokenId) { - throw new Error('Unable to get start token'); - } - const finalToken = getWrappedToken(config.tokens[destToken]); - if (!finalToken.tokenId) { - throw new Error('Unable to get final token'); - } - const destCanonicalTokenAddress = - await getCanonicalTokenAddress(finalToken); - - if (!destCanonicalTokenAddress) - throw new Error('Couldnt resolve destCanonicalTokenAddress'); - - const request: RelayerQuoteRequest = { - targetChain: toChainId(destChain), - sourceToken: hexZeroPad(destCanonicalTokenAddress, 32).slice(2), - targetToken: hexZeroPad(finalToken.tokenId.address, 32).slice(2), - }; - const response = await axios.post( - RELAYER_FEE_API_URL, - request, - ); - if (response.status !== 200) { - throw new Error(`Error getting relayer fee: ${response.statusText}`); - } - return { - fee: BigNumber.from(response.data.fee), - feeToken: finalToken.tokenId, - }; - } - - async getForeignAsset( - token: TokenId, - chain: ChainName | ChainId, - ): Promise { - const addr = await getForeignTokenAddress( - config.sdkConverter.toTokenIdV2(token), - config.sdkConverter.toChainV2(chain), - ); - if (!addr) return null; - return addr.toString(); - } - - async getMessage( - tx: string, - chain: ChainName | ChainId, - ): Promise { - const message = await config.wh.getMessage(tx, chain, false); - const adaptedMessage = await adaptParsedMessage(message); - if (adaptedMessage.payloadID !== PayloadType.Automatic) { - throw new Error('Invalid payload type'); - } - const payloadBuffer = Buffer.from(adaptedMessage.payload!.slice(2), 'hex'); - const { recipientAddress } = parsePorticoPayload(payloadBuffer); - adaptedMessage.recipient = recipientAddress; - const provider = config.wh.mustGetProvider(chain); - /* @ts-ignore */ - const { data } = await provider.getTransaction(tx); - adaptedMessage.inputData = data; - return adaptedMessage; - } - - async getSignedMessage( - message: TokenTransferMessage, - ): Promise { - const vaa = await fetchVaa(message); - - if (!vaa) { - throw new Error('VAA not found'); - } - - return { - ...message, - vaa: hexlify(vaa), - }; - } - - async nativeTokenAmount( - destChain: ChainName | ChainId, - token: TokenId, - amount: BigNumber, - walletAddress: string, - ): Promise { - throw new Error('Not supported'); - } - - async maxSwapAmount( - destChain: ChainName | ChainId, - token: TokenId, - walletAddress: string, - ): Promise { - throw new Error('Not supported'); - } - - async tryFetchRedeemTx(txData: UnsignedMessage): Promise { - return undefined; - /* - * TODO SDKV2 - const redeemTx = await fetchGlobalTx(txData); - if (redeemTx) { - return redeemTx; - } - const { emitterChain, emitterAddress, sequence } = - getEmitterAndSequence(txData); - const context = config.wh.getContext( - txData.toChain, - ) as WormholeContext; - const tokenBridge = context.contracts.mustGetBridge(txData.toChain); - const { maxBlockSearch } = config.chains[txData.toChain]!; - const filter = tokenBridge.filters.TransferRedeemed( - emitterChain, - `0x${emitterAddress}`, - sequence, - ); - try { - const currentBlock = await context.getCurrentBlock(txData.toChain); - const events = await tokenBridge.queryFilter( - filter, - currentBlock - maxBlockSearch, - ); - if (events.length > 0) { - return events[0].transactionHash; - } - } catch (e) { - console.error(e); - } - return undefined; - */ - } - - async getTransferSourceInfo({ - txData, - tokenPrices, - }: TransferInfoBaseParams): Promise { - // skip 0x prefix and the function selector (2 + 8 = 10 bytes) - const inputDataBuffer = Buffer.from(txData.inputData!.slice(10), 'hex'); - const { amountSpecified, startTokenAddress } = - parseTradeParameters(inputDataBuffer); - const payloadBuffer = Buffer.from(txData.payload!.slice(2), 'hex'); - const { - flagSet: { shouldWrapNative, shouldUnwrapNative }, - relayerFee, - finalTokenAddress, - minAmountFinish, - } = parsePorticoPayload(payloadBuffer); - const startToken = getTokenById({ - chain: txData.fromChain, - address: startTokenAddress, - }); - if (!startToken) { - throw new Error('Unable to find start token'); - } - const gasToken = getGasToken(txData.fromChain); - const gasTokenDecimals = getTokenDecimals( - toChainId(gasToken.nativeChain), - 'native', - ); - const tokenSent = shouldWrapNative ? gasToken : startToken; - const finalToken = shouldUnwrapNative - ? getGasToken(txData.toChain) - : getTokenById({ chain: txData.toChain, address: finalTokenAddress }); - if (!finalToken) { - throw new Error('Unable to find dest token'); - } - const startTokenDecimals = getTokenDecimals( - toChainId(txData.fromChain), - startToken.tokenId, - ); - const formattedAmount = toDecimals( - amountSpecified, - startTokenDecimals, - MAX_DECIMALS, - ); - const destTokenDecimals = getTokenDecimals( - toChainId(txData.toChain), - finalToken.tokenId, - ); - const minAmountOut = minAmountFinish.sub(relayerFee); - const formattedMinAmount = toDecimals( - minAmountOut, - destTokenDecimals, - MAX_DECIMALS, - ); - const formattedGasFee = - txData.gasFee && - toDecimals(txData.gasFee, gasTokenDecimals, MAX_DECIMALS); - return [ - { - title: 'Amount', - value: `${formattedAmount} ${getDisplayName(tokenSent)}`, - valueUSD: calculateUSDPrice(formattedAmount, tokenPrices, tokenSent), - }, - { - title: 'Gas fee', - value: formattedGasFee - ? `${formattedGasFee} ${getDisplayName(gasToken)}` - : NO_INPUT, - valueUSD: calculateUSDPrice(formattedGasFee, tokenPrices, gasToken), - }, - { - title: 'Min receive amount', - value: `${formattedMinAmount} ${getDisplayName(finalToken)}`, - valueUSD: calculateUSDPrice( - formattedMinAmount, - tokenPrices, - finalToken, - ), - }, - ]; - } - - async getTransferDestInfo({ - txData, - tokenPrices, - receiveTx, - transferComplete, - }: TransferDestInfoParams): Promise { - if (!receiveTx) { - return { - route: this.TYPE, - displayData: [], - destTxInfo: { receivedTokenKey: '' }, - }; - } - const provider = config.wh.mustGetProvider(txData.toChain); - /* @ts-ignore */ - const receipt = await provider.getTransactionReceipt( - hexlify(receiveTx, { allowMissingPrefix: true }), - ); - const payloadBuffer = Buffer.from(txData.payload!.slice(2), 'hex'); - const { - finalTokenAddress, - flagSet: { shouldUnwrapNative }, - } = parsePorticoPayload(payloadBuffer); - const swapFinishedLog = receipt.logs.find( - (log) => log.topics[0] === porticoSwapFinishedEvent, - ); - if (!swapFinishedLog) { - throw new Error('Swap finished log not found'); - } - // handle the case for when the swap failed - const swapCompleted = swapFinishedLog.data.slice(0, 66).endsWith('1'); - if ( - !swapCompleted && - !isEqualCaseInsensitive(finalTokenAddress, txData.tokenAddress) - ) { - const decimals = getTokenDecimals( - toChainId(txData.toChain), - txData.tokenId, - ); - const formattedAmount = toNormalizedDecimals( - txData.amount, - decimals, - MAX_DECIMALS, - ); - const receivedTokenDisplayName = getDisplayName( - config.tokens[txData.receivedTokenKey], - ); - - const canonicalTokenAddress = await getForeignTokenAddress( - config.sdkConverter.toTokenIdV2(txData.tokenId), - config.sdkConverter.toChainV2(txData.toChain), - ); - - if (!canonicalTokenAddress) { - throw new Error('Canonical token address not found'); - } - const destTxInfo: PorticoDestTxInfo = { - receivedTokenKey: txData.receivedTokenKey, - swapFailed: { - canonicalTokenAddress: canonicalTokenAddress.toString(), - finalTokenAddress, - }, - }; - return { - route: this.TYPE, - displayData: [ - { - title: 'Amount', - value: `${formattedAmount} ${receivedTokenDisplayName}`, - valueUSD: calculateUSDPrice( - formattedAmount, - tokenPrices, - config.tokens[txData.receivedTokenKey], - ), - }, - { - title: 'Relayer fee', - value: NO_INPUT, - }, - ], - destTxInfo, - }; - } - // if we get here then the swap succeeded or did not occur if the destination chain is Ethereum. - // no swap needs to be done on Ethereum since the canonical/bridged token is the final token - const finalUserAmount = BigNumber.from( - `0x${swapFinishedLog.data.slice(66, 130)}`, - ); - const relayerFeeAmount = BigNumber.from( - `0x${swapFinishedLog.data.slice(130, 194)}`, - ); - const finalToken = shouldUnwrapNative - ? getGasToken(txData.toChain) - : getTokenById({ chain: txData.toChain, address: finalTokenAddress })!; - const decimals = getTokenDecimals( - toChainId(txData.toChain), - shouldUnwrapNative ? 'native' : finalToken.tokenId, - ); - const formattedFinalUserAmount = toDecimals( - finalUserAmount, - decimals, - MAX_DECIMALS, - ); - const formattedRelayerFee = toDecimals( - relayerFeeAmount, - decimals, - MAX_DECIMALS, - ); - const finalTokenDisplayName = getDisplayName(finalToken); - const destTxInfo: PorticoDestTxInfo = { - receivedTokenKey: finalToken.key, - }; - return { - route: this.TYPE, - displayData: [ - { - title: 'Amount received', - value: `${formattedFinalUserAmount} ${finalTokenDisplayName}`, - valueUSD: calculateUSDPrice( - formattedFinalUserAmount, - tokenPrices, - finalToken, - ), - }, - { - title: 'Relayer fee', - value: `${formattedRelayerFee} ${finalTokenDisplayName}`, - valueUSD: calculateUSDPrice( - formattedRelayerFee, - tokenPrices, - finalToken, - ), - }, - ], - destTxInfo, - }; - } - - async getPreview( - token: TokenConfig, - destToken: TokenConfig, - amount: number, - sendingChain: ChainName | ChainId, - receipientChain: ChainName | ChainId, - sendingGasEst: string, - claimingGasEst: string, - receiveAmount: string, - tokenPrices: TokenPrices, - routeOptions: PorticoBridgeState, - ): Promise { - const { relayerFee, swapAmounts } = routeOptions; - const sendingChainName = toChainName(sendingChain); - const gasToken = getGasToken(sendingChainName); - const gasTokenDisplayName = getDisplayName(gasToken); - const destTokenDisplayName = getDisplayName(destToken); - const destChainConfig = getChainConfig(receipientChain); - const destTokenDecimals = - destToken.key === destChainConfig.gasToken - ? destChainConfig.nativeTokenDecimals - : getTokenDecimals(toChainId(receipientChain), destToken.tokenId); - - let totalFeesText = ''; - let totalFeesPrice = ''; - let fee = ''; - if (sendingGasEst && relayerFee.data) { - fee = toDecimals(relayerFee.data, destTokenDecimals, MAX_DECIMALS); - totalFeesText = `${sendingGasEst} ${gasTokenDisplayName} & ${fee} ${destTokenDisplayName}`; - totalFeesPrice = `${ - calculateUSDPrice(sendingGasEst, tokenPrices, gasToken) || NO_INPUT - } & ${calculateUSDPrice(fee, tokenPrices, destToken) || NO_INPUT}`; - } - - const feePrice = calculateUSDPrice(fee, tokenPrices, destToken); - const sendingGasEstPrice = calculateUSDPrice( - sendingGasEst, - tokenPrices, - gasToken, - ); - let expectedAmount = ''; - let minimumAmount = ''; - if (relayerFee.data && swapAmounts.data) { - expectedAmount = toDecimals( - BigNumber.from(swapAmounts.data.amountFinish).sub(relayerFee.data), - destTokenDecimals, - MAX_DECIMALS, - ); - minimumAmount = toDecimals( - BigNumber.from(swapAmounts.data.minAmountFinish).sub(relayerFee.data), - destTokenDecimals, - MAX_DECIMALS, - ); - } - return [ - { - title: 'Expected to receive', - value: `${expectedAmount} ${destTokenDisplayName}`, - valueUSD: calculateUSDPrice(expectedAmount, tokenPrices, destToken), - }, - { - title: 'Minimum to receive', - value: `${minimumAmount} ${destTokenDisplayName}`, - valueUSD: calculateUSDPrice(minimumAmount, tokenPrices, destToken), - }, - { - title: 'Total fee estimates', - value: totalFeesText, - valueUSD: totalFeesPrice, - rows: [ - { - title: 'Source chain gas estimate', - value: sendingGasEst - ? `~ ${sendingGasEst} ${gasTokenDisplayName}` - : NO_INPUT, - valueUSD: sendingGasEstPrice, - }, - { - title: 'Relayer fee', - value: fee ? `${fee} ${destTokenDisplayName}` : NO_INPUT, - valueUSD: feePrice, - }, - ], - }, - ]; - } -} diff --git a/wormhole-connect/src/routes/porticoBridge/types.ts b/wormhole-connect/src/routes/porticoBridge/types.ts deleted file mode 100644 index b410b6496..000000000 --- a/wormhole-connect/src/routes/porticoBridge/types.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { BigNumber } from 'ethers5'; -import { TransferDestInfo } from 'routes/types'; - -export interface CreateOrderRequest { - startingChainId: number; - startingToken: string; - startingTokenAmount: string; - destinationToken: string; - destinationAddress: string; - destinationChainId: number; - relayerFee: string; - feeTierStart: number; - feeTierEnd: number; - minAmountStart: string; - minAmountEnd: string; - bridgeNonce: number; - shouldWrapNative: boolean; - shouldUnwrapNative: boolean; - porticoAddress: string; - destinationPorticoAddress: string; -} - -export interface CreateOrderResponse { - transactionData: string; - transactionTarget: string; - transactionValue: string; - startParameters: string[]; - estimatedAmountOut: string; -} - -export interface PorticoTradeParameters { - flagSet: PorticoFlagSet; - startTokenAddress: string; - canonAssetAddress: string; - finalTokenAddress: string; - recipientAddress: string; - destinationPorticoAddress: string; - amountSpecified: BigNumber; - minAmountStart: BigNumber; - minAmountFinish: BigNumber; - relayerFee: BigNumber; -} - -export interface PorticoFlagSet { - recipientChain: number; - bridgeNonce: number; - feeTierStart: number; - feeTierFinish: number; - shouldWrapNative: boolean; - shouldUnwrapNative: boolean; -} - -export interface PorticoPayload { - flagSet: PorticoFlagSet; - finalTokenAddress: string; - recipientAddress: string; - canonAssetAmount: BigNumber; - minAmountFinish: BigNumber; - relayerFee: BigNumber; -} - -export interface RelayerQuoteRequest { - targetChain: number; - sourceToken: string; - targetToken: string; -} - -export interface RelayerQuoteResponse { - fee: string; - validUntil: string; -} - -export type PorticoTransferDestInfo = TransferDestInfo & { - destTxInfo: PorticoDestTxInfo; -}; - -export interface PorticoDestTxInfo { - receivedTokenKey: string; // this is the key of the token that was received (e.g. the token that was swapped for) - swapFailed?: { - canonicalTokenAddress: string; - finalTokenAddress: string; - }; -} diff --git a/wormhole-connect/src/routes/porticoBridge/uniswapQuoter.ts b/wormhole-connect/src/routes/porticoBridge/uniswapQuoter.ts deleted file mode 100644 index 013d762d7..000000000 --- a/wormhole-connect/src/routes/porticoBridge/uniswapQuoter.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ChainId, ChainName } from 'sdklegacy'; -import { BigNumber /*ethers*/ } from 'ethers5'; -//import { uniswapQuoterV2Abi } from './abis'; -//import config from 'config'; - -export interface Quote { - amountOut: BigNumber; -} - -export async function getQuote( - chain: ChainName | ChainId, - tokenIn: string, - tokenOut: string, - amountIn: BigNumber, - fee: number, -): Promise { - /* - * TODO SDKV2 - if (tokenIn === tokenOut) { - return { amountOut: amountIn }; - } - const address = config.wh.mustGetContracts(chain).uniswapQuoterV2; - if (!address) { - throw new Error('Uniswap quoter address not found'); - } - const provider = config.wh.mustGetProvider(chain); - const contract = new ethers.Contract(address, uniswapQuoterV2Abi, provider); - const result = await contract.functions.quoteExactInputSingle([ - tokenIn, - tokenOut, - amountIn, - fee, - 0, - ]); - return { amountOut: result[0] }; - */ - return { amountOut: BigNumber.from(0) }; -} diff --git a/wormhole-connect/src/routes/porticoBridge/utils.ts b/wormhole-connect/src/routes/porticoBridge/utils.ts index 185b2959a..ab30261f9 100644 --- a/wormhole-connect/src/routes/porticoBridge/utils.ts +++ b/wormhole-connect/src/routes/porticoBridge/utils.ts @@ -1,217 +1,2 @@ -import { hexStripZeros, hexZeroPad } from 'ethers5/lib/utils.js'; -import { - CreateOrderRequest, - CreateOrderResponse, - PorticoFlagSet, - PorticoPayload, - PorticoTradeParameters, - PorticoTransferDestInfo, -} from './types'; -import { BigNumber } from 'ethers5'; -import { Route, TokenConfig } from 'config/types'; -import { porticoAbi } from './abis'; -import { - getChainByChainId, - getWrappedToken, - isEqualCaseInsensitive, -} from 'utils'; -import config from 'config'; -import { TransferDestInfo } from 'routes/types'; -import { getForeignTokenAddress } from 'utils/sdkv2'; - -export const parseAddress = (buffer: Buffer): string => { - return hexZeroPad(hexStripZeros(buffer), 20); -}; - -export const parseTradeParameters = ( - buffer: Buffer, -): PorticoTradeParameters => { - return { - flagSet: parseFlagSet(buffer), - startTokenAddress: parseAddress(buffer.slice(32, 64)), - canonAssetAddress: parseAddress(buffer.slice(64, 96)), - finalTokenAddress: parseAddress(buffer.slice(96, 128)), - recipientAddress: parseAddress(buffer.slice(128, 160)), - destinationPorticoAddress: parseAddress(buffer.slice(160, 192)), - amountSpecified: BigNumber.from(buffer.slice(192, 224)), - minAmountStart: BigNumber.from(buffer.slice(224, 256)), - minAmountFinish: BigNumber.from(buffer.slice(256, 288)), - relayerFee: BigNumber.from(buffer.slice(288, 320)), - }; -}; - -export const parsePorticoPayload = (buffer: Buffer): PorticoPayload => { - return { - flagSet: parseFlagSet(buffer), - finalTokenAddress: parseAddress(buffer.slice(32, 64)), - recipientAddress: parseAddress(buffer.slice(64, 96)), - canonAssetAmount: BigNumber.from(buffer.slice(96, 128)), - minAmountFinish: BigNumber.from(buffer.slice(128, 160)), - relayerFee: BigNumber.from(buffer.slice(160, 192)), - }; -}; - -export const parseFlagSet = (buffer: Buffer): PorticoFlagSet => { - return { - recipientChain: buffer.readUInt16LE(0), - bridgeNonce: buffer.readUInt32LE(2), - feeTierStart: buffer.readUintLE(6, 3), - feeTierFinish: buffer.readUintLE(9, 3), - shouldWrapNative: !!(buffer.readUInt8(31) & (1 << 0)), - shouldUnwrapNative: !!(buffer.readUInt8(31) & (1 << 1)), - }; -}; - -/** - * Validates that the response from the order creation API matches the request - * throws an error if there is a mismatch - */ -export const validateCreateOrderResponse = async ( - response: CreateOrderResponse, - request: CreateOrderRequest, - startToken: TokenConfig, -): Promise => { - if ( - !isEqualCaseInsensitive( - request.porticoAddress || '', - response.transactionTarget, - ) - ) { - throw new Error('portico address mismatch'); - } - - const decoded = porticoAbi.decodeFunctionData( - 'start', - response.transactionData, - ); - if (decoded.length !== 1 || decoded[0].length !== 10) { - throw new Error('decoded length mismatch'); - } - - const flagSetBuffer = Buffer.from(decoded[0][0].slice(2), 'hex'); - if (flagSetBuffer.length !== 32) { - throw new Error('flag set length mismatch'); - } - const { - recipientChain, - feeTierStart, - feeTierFinish, - shouldWrapNative, - shouldUnwrapNative, - } = parseFlagSet(flagSetBuffer); - - if (recipientChain !== getChainByChainId(request.destinationChainId)?.id) { - throw new Error('recipient chain mismatch'); - } - - if (feeTierStart !== request.feeTierStart) { - throw new Error('fee tier start mismatch'); - } - - if (feeTierFinish !== request.feeTierEnd) { - throw new Error('fee tier end mismatch'); - } - - if (shouldWrapNative !== request.shouldWrapNative) { - throw new Error('should wrap native mismatch'); - } - - if (shouldUnwrapNative !== request.shouldUnwrapNative) { - throw new Error('should unwrap native mismatch'); - } - - const startTokenAddress: string = decoded[0][1]; - if (!isEqualCaseInsensitive(startTokenAddress, request.startingToken)) { - throw new Error('start token address mismatch'); - } - - const canonicalTokenAddress: string = decoded[0][2]; - if ( - !isEqualCaseInsensitive( - canonicalTokenAddress, - (await getCanonicalTokenAddress(startToken)) || '', // TODO SDKV2 this can be null - ) - ) { - throw new Error('canonical token address mismatch'); - } - - const finalTokenAddress: string = decoded[0][3]; - if (!isEqualCaseInsensitive(finalTokenAddress, request.destinationToken)) { - throw new Error('final token address mismatch'); - } - - const recipientAddress: string = decoded[0][4]; - if (!isEqualCaseInsensitive(recipientAddress, request.destinationAddress)) { - throw new Error('recipient address mismatch'); - } - - const destinationPorticoAddress = decoded[0][5]; - if ( - !isEqualCaseInsensitive( - destinationPorticoAddress, - request.destinationPorticoAddress || '', - ) - ) { - throw new Error('destination portico address mismatch'); - } - - const amountSpecified: BigNumber = decoded[0][6]; - if (amountSpecified.toString() !== request.startingTokenAmount) { - throw new Error('amount mismatch'); - } - - const minAmountStart: BigNumber = decoded[0][7]; - if (minAmountStart.toString() !== request.minAmountStart) { - throw new Error('min amount start mismatch'); - } - - const minAmountFinish: BigNumber = decoded[0][8]; - if (minAmountFinish.toString() !== request.minAmountEnd) { - throw new Error('min amount finish mismatch'); - } - - const relayerFee: BigNumber = decoded[0][9]; - if (relayerFee.toString() !== request.relayerFee) { - throw new Error('relayer fee mismatch'); - } -}; - -/** - * The canonical token address is the foreign asset of the token bridged from Ethereum - */ -export const getCanonicalTokenAddress = async ( - token: TokenConfig, -): Promise => { - const tokenOnEthereum = Object.values(config.tokens).find( - (t) => t.symbol === token.symbol && t.nativeChain === 'ethereum', - ); - if (!tokenOnEthereum) { - throw new Error(`${token.symbol} not found on Ethereum`); - } - const { tokenId } = getWrappedToken(tokenOnEthereum); - if (!tokenId) { - throw new Error('Canonical token not found'); - } - const tokenAddr = await getForeignTokenAddress( - config.sdkConverter.toTokenIdV2(tokenId), - config.sdkConverter.toChainV2(token.nativeChain), - ); - - if (!tokenAddr) return null; - - return tokenAddr.toString(); -}; - -export const isPorticoRoute = (route: Route): boolean => { - switch (route) { - case Route.ETHBridge: - case Route.wstETHBridge: - return true; - default: - return false; - } -}; - -export const isPorticoTransferDestInfo = ( - info: TransferDestInfo | undefined, -): info is PorticoTransferDestInfo => !!(info && isPorticoRoute(info.route)); +export const isPorticoRoute = (a: any) => false; +export const isPorticoTransferDestInfo = (a: any) => false; diff --git a/wormhole-connect/src/routes/porticoBridge/wstETHBridge.ts b/wormhole-connect/src/routes/porticoBridge/wstETHBridge.ts deleted file mode 100644 index 910198ea6..000000000 --- a/wormhole-connect/src/routes/porticoBridge/wstETHBridge.ts +++ /dev/null @@ -1,12 +0,0 @@ -import config from 'config'; -import { PorticoBridge } from './porticoBridge'; -import { Route } from 'config/types'; - -export class wstETHBridge extends PorticoBridge { - readonly TYPE: Route = Route.wstETHBridge; - static readonly SUPPORTED_TOKENS: string[] = ['wstETH']; - - constructor() { - super(wstETHBridge.SUPPORTED_TOKENS, config.wstETHBridgeMaxAmount); - } -} diff --git a/wormhole-connect/src/routes/sdkv2/route.ts b/wormhole-connect/src/routes/sdkv2/route.ts index a0a3e45a6..20c8c2969 100644 --- a/wormhole-connect/src/routes/sdkv2/route.ts +++ b/wormhole-connect/src/routes/sdkv2/route.ts @@ -255,6 +255,7 @@ export class SDKv2Route extends RouteAbstract { options: any, ): Promise<[routes.Route, routes.QuoteResult]> { const wh = await getWormholeContextV2(); + console.log(sourceToken, destToken, sourceChain, destChain); const req = await routes.RouteTransferRequest.create( wh, /* @ts-ignore */ @@ -266,6 +267,8 @@ export class SDKv2Route extends RouteAbstract { destChain, ); + console.log(req); + const route = new this.rc(wh, req); const validationResult = await route.validate({ @@ -288,6 +291,8 @@ export class SDKv2Route extends RouteAbstract { toChainV1: ChainName | undefined, options: any, ): Promise { + console.log(sourceToken, fromChainV1, destToken, toChainV1); + if (isNaN(amountIn)) { return 0; } @@ -387,7 +392,7 @@ export class SDKv2Route extends RouteAbstract { } async send( - sourceToken: TokenIdV1 | 'native', + sourceToken: TokenConfig, amount: string, fromChainV1: ChainName | ChainId, senderAddress: string, @@ -401,10 +406,7 @@ export class SDKv2Route extends RouteAbstract { const fromChainV2 = await this.getV2ChainContext(fromChainV1); const toChainV2 = await this.getV2ChainContext(toChainV1); - const sourceTokenV2 = - sourceToken === 'native' - ? Wormhole.tokenId(config.sdkConverter.toChainV2(fromChainV1), 'native') - : config.sdkConverter.toTokenIdV2(sourceToken); + const sourceTokenV2 = config.sdkConverter.toTokenIdV2(sourceToken); const destTokenV2 = config.sdkConverter.getTokenIdV2ForKey( destToken, @@ -412,6 +414,8 @@ export class SDKv2Route extends RouteAbstract { config.tokens, ); + console.log(sourceTokenV2, destTokenV2); + if (!destTokenV2) throw new Error(`Couldn't find destToken`); const [route, quote] = await this.getQuote( diff --git a/wormhole-connect/src/utils/errors.ts b/wormhole-connect/src/utils/errors.ts index e204a0906..f3821fe4c 100644 --- a/wormhole-connect/src/utils/errors.ts +++ b/wormhole-connect/src/utils/errors.ts @@ -1,14 +1,14 @@ import type { TransferErrorType, TransferError } from 'telemetry/types'; import { ERR_INSUFFICIENT_ALLOWANCE, - ERR_SWAP_FAILED, + //ERR_SWAP_FAILED, ERR_INSUFFICIENT_GAS, ERR_TIMEOUT, ERR_UNKNOWN, ERR_USER_REJECTED, } from 'telemetry/types'; import { ChainName, InsufficientFundsForGasError } from 'sdklegacy'; -import { SWAP_ERROR } from 'routes/porticoBridge/consts'; +//import { SWAP_ERROR } from 'routes/porticoBridge/consts'; // TODO SDKV2 // copied from sdk subpackage @@ -36,9 +36,11 @@ export function interpretTransferError( } else if (e.message.includes('rejected the request')) { uiErrorMessage = 'Transfer rejected in wallet, please try again'; internalErrorCode = ERR_USER_REJECTED; + /* TODO SDKV2 } else if (e.message === SWAP_ERROR) { uiErrorMessage = SWAP_ERROR; internalErrorCode = ERR_SWAP_FAILED; + */ } } diff --git a/wormhole-connect/src/utils/sdkv2.ts b/wormhole-connect/src/utils/sdkv2.ts index bc8c739be..0fbb26b87 100644 --- a/wormhole-connect/src/utils/sdkv2.ts +++ b/wormhole-connect/src/utils/sdkv2.ts @@ -37,7 +37,7 @@ export async function getForeignTokenAddress( return Wormhole.parseAddress(chain, chainToken.address); } } else { - console.error(`Couldn't find token key for ${token}`); + console.error(`Couldn't find token key for`, token); } return null; diff --git a/wormhole-connect/src/views/Bridge/Bridge.tsx b/wormhole-connect/src/views/Bridge/Bridge.tsx index c3fae4af2..22cf8b722 100644 --- a/wormhole-connect/src/views/Bridge/Bridge.tsx +++ b/wormhole-connect/src/views/Bridge/Bridge.tsx @@ -26,7 +26,7 @@ import FooterNavBar from 'components/FooterNavBar'; import { useComputeDestinationTokens } from 'hooks/useComputeDestinationTokens'; import { useComputeReceiveAmount } from 'hooks/useComputeReceiveAmount'; import { useComputeSourceTokens } from 'hooks/useComputeSourceTokens'; -import { usePorticoSwapInfo } from 'hooks/usePorticoSwapInfo'; +//import { usePorticoSwapInfo } from 'hooks/usePorticoSwapInfo'; import { usePorticoRelayerFee } from 'hooks/usePorticoRelayerFee'; import { useFetchTokenPrices } from 'hooks/useFetchTokenPrices'; import { useGasSlider } from 'hooks/useGasSlider'; @@ -124,7 +124,7 @@ function Bridge() { }); // Route specific hooks - usePorticoSwapInfo(); + //usePorticoSwapInfo(); usePorticoRelayerFee(); useFetchTokenPrices(); useConnectToLastUsedWallet(); diff --git a/wormhole-connect/src/views/Bridge/Send.tsx b/wormhole-connect/src/views/Bridge/Send.tsx index 425cbef93..c6d772784 100644 --- a/wormhole-connect/src/views/Bridge/Send.tsx +++ b/wormhole-connect/src/views/Bridge/Send.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Context } from 'sdklegacy'; -import type { TokenId } from 'sdklegacy'; +//import type { TokenId } from 'sdklegacy'; import { makeStyles } from 'tss-react/mui'; import config from 'config'; @@ -130,8 +130,7 @@ function Send(props: { valid: boolean }) { } dispatch(setIsTransactionInProgress(true)); - const tokenConfig = config.tokens[token]!; - const sendToken: TokenId | 'native' = tokenConfig.tokenId ?? 'native'; + const sendToken = config.tokens[token]!; try { const fromConfig = config.chains[fromChain!]; @@ -157,7 +156,7 @@ function Send(props: { valid: boolean }) { console.log('sending'); const sendResult = await RouteOperator.send( route, - sendToken || 'native', + sendToken, `${amount}`, fromChain!, sending.address, diff --git a/wormhole-connect/src/views/Redeem/AddToWallet.tsx b/wormhole-connect/src/views/Redeem/AddToWallet.tsx index 53f93531b..1f8917576 100644 --- a/wormhole-connect/src/views/Redeem/AddToWallet.tsx +++ b/wormhole-connect/src/views/Redeem/AddToWallet.tsx @@ -27,7 +27,7 @@ import ExplorerLink from './ExplorerLink'; import { isGatewayChain } from 'utils/cosmos'; import { isPorticoRoute, - isPorticoTransferDestInfo, + //isPorticoTransferDestInfo, } from 'routes/porticoBridge/utils'; import { getForeignTokenAddress } from 'utils/sdkv2'; @@ -175,12 +175,14 @@ function AddToWallet() { useEffect(() => { const fetchTokenInfo = async () => { if (isGatewayChain(txData.toChain)) return; - const isPorticoTransfer = isPorticoTransferDestInfo(transferDestInfo); + const isPorticoTransfer = false; //isPorticoTransferDestInfo(transferDestInfo); // we need the transfer dest info to get the received token key for portico if (route && isPorticoRoute(route) && !isPorticoTransfer) return; const receivedTokenKey = isPorticoTransfer - ? transferDestInfo.destTxInfo.receivedTokenKey - : txData.receivedTokenKey; + ? /* @ts-ignore */ + transferDestInfo.destTxInfo.receivedTokenKey + : /* @ts-ignore */ + txData.receivedTokenKey; const tokenInfo = config.tokens[receivedTokenKey]; if (!tokenInfo) return; try { @@ -200,7 +202,6 @@ function AddToWallet() { if (txData.toChain === 'sui' && address) { /* - * TODO SDKV2 const context = config.wh.getContext( 'sui', ) as SuiContext; @@ -212,7 +213,6 @@ function AddToWallet() { return; */ } - setTargetAddress(address.toString()); setTargetToken(wrapped); }; diff --git a/wormhole-connect/src/views/Redeem/BridgeComplete.tsx b/wormhole-connect/src/views/Redeem/BridgeComplete.tsx index d79e61137..1af1147a3 100644 --- a/wormhole-connect/src/views/Redeem/BridgeComplete.tsx +++ b/wormhole-connect/src/views/Redeem/BridgeComplete.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch /*, useSelector*/ } from 'react-redux'; import config from 'config'; import { setRoute } from 'store/router'; @@ -8,18 +8,20 @@ import Button from 'components/Button'; import InputContainer from 'components/InputContainer'; import Spacer from 'components/Spacer'; import AddToWallet from './AddToWallet'; -import { RootState } from 'store'; -import PorticoSwapFailed from './PorticoSwapFailed'; -import { isPorticoTransferDestInfo } from 'routes/porticoBridge/utils'; -import { useTheme } from '@mui/material/styles'; -import { OPACITY } from 'utils/style'; +//import { RootState } from 'store'; +//import PorticoSwapFailed from './PorticoSwapFailed'; +//import { isPorticoTransferDestInfo } from 'routes/porticoBridge/utils'; +//import { useTheme } from '@mui/material/styles'; +//import { OPACITY } from 'utils/style'; function BridgeComplete() { const dispatch = useDispatch(); + /* const transferDestInfo = useSelector( (state: RootState) => state.redeem.transferDestInfo, ); - const theme: any = useTheme(); + */ + //const theme: any = useTheme(); const toLink = () => { if (typeof window !== 'undefined') { window.location.href = config.cta!.link; @@ -35,6 +37,7 @@ function BridgeComplete() { The bridge is now complete. ); + /* if ( isPorticoTransferDestInfo(transferDestInfo) && transferDestInfo.destTxInfo.swapFailed @@ -42,6 +45,7 @@ function BridgeComplete() { containerBg = theme.palette.warning[500] + OPACITY[25]; component = ; } + */ return (
diff --git a/wormhole-connect/src/views/Redeem/PorticoSwapFailed.tsx b/wormhole-connect/src/views/Redeem/PorticoSwapFailed.tsx index 8401fb2ba..c8f84bd60 100644 --- a/wormhole-connect/src/views/Redeem/PorticoSwapFailed.tsx +++ b/wormhole-connect/src/views/Redeem/PorticoSwapFailed.tsx @@ -1,3 +1,4 @@ +/* import React from 'react'; import { OKU_TRADE_BASE_URL } from 'routes/porticoBridge/consts'; import { PorticoDestTxInfo } from 'routes/porticoBridge/types'; @@ -55,3 +56,4 @@ const PorticoSwapFailed = ({ }; export default PorticoSwapFailed; +*/ diff --git a/wormhole-connect/src/views/Redeem/Stepper.tsx b/wormhole-connect/src/views/Redeem/Stepper.tsx index 73bac39e9..6f6c8891f 100644 --- a/wormhole-connect/src/views/Redeem/Stepper.tsx +++ b/wormhole-connect/src/views/Redeem/Stepper.tsx @@ -8,7 +8,7 @@ import Stepper from 'components/Stepper/Stepper'; import SendFrom from './SendFrom'; import SendTo from './SendTo'; import BridgeComplete from './BridgeComplete'; -import { isPorticoTransferDestInfo } from 'routes/porticoBridge/utils'; +//import { isPorticoTransferDestInfo } from 'routes/porticoBridge/utils'; export default function MilestoneStepper() { const signedMessage: UnsignedMessage | undefined = useSelector( @@ -17,13 +17,17 @@ export default function MilestoneStepper() { const transferComplete = useSelector( (state: RootState) => state.redeem.transferComplete, ); + /* const transferDestInfo = useSelector( (state: RootState) => state.redeem.transferDestInfo, ); + */ let showWarning = false; + /* if (isPorticoTransferDestInfo(transferDestInfo)) { showWarning = !!transferDestInfo.destTxInfo.swapFailed; } + */ const activeStep = transferComplete ? 4 : signedMessage ? 2 : 1; const steps = [ From 071434149223a5da378c0c4686db9dc8092b7f5c Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Sun, 30 Jun 2024 20:02:40 -0400 Subject: [PATCH 4/7] solana signing working, getting rid of SendResult --- wormhole-connect/src/config/converter.ts | 27 +++++++++--- wormhole-connect/src/routes/sdkv2/route.ts | 9 ++-- wormhole-connect/src/routes/sdkv2/signer.ts | 3 ++ wormhole-connect/src/utils/wallet/evm.ts | 12 +++--- wormhole-connect/src/utils/wallet/index.ts | 48 +++++++++++---------- wormhole-connect/src/utils/wallet/solana.ts | 25 ++++++----- wormhole-connect/src/utils/wallet/types.ts | 17 ++++++++ 7 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 wormhole-connect/src/utils/wallet/types.ts diff --git a/wormhole-connect/src/config/converter.ts b/wormhole-connect/src/config/converter.ts index 9f97057b6..73970436f 100644 --- a/wormhole-connect/src/config/converter.ts +++ b/wormhole-connect/src/config/converter.ts @@ -64,12 +64,29 @@ export class SDKConverter { } } - toTokenIdV2(token: v1.TokenId | TokenConfigV1): v2.TokenId { + toTokenIdV2( + token: v1.TokenId | TokenConfigV1, + chain?: v1.ChainName, + ): v2.TokenId { if (this.isTokenConfigV1(token)) { - return v2.Wormhole.tokenId( - this.toChainV2(token.nativeChain), - token.tokenId?.address ?? 'native', - ); + if (chain && chain != token.nativeChain) { + // Getting foreign address + let foreignAsset = token.foreignAssets?.[chain]; + if (foreignAsset) { + return v2.Wormhole.tokenId( + this.toChainV2(chain), + foreignAsset.address, + ); + } else { + throw new Error('no foreign asset'); + } + } else { + // Getting native address + return v2.Wormhole.tokenId( + this.toChainV2(token.nativeChain), + token.tokenId?.address ?? 'native', + ); + } } else { return v2.Wormhole.tokenId(this.toChainV2(token.chain), token.address); } diff --git a/wormhole-connect/src/routes/sdkv2/route.ts b/wormhole-connect/src/routes/sdkv2/route.ts index 20c8c2969..cd79e564a 100644 --- a/wormhole-connect/src/routes/sdkv2/route.ts +++ b/wormhole-connect/src/routes/sdkv2/route.ts @@ -394,7 +394,7 @@ export class SDKv2Route extends RouteAbstract { async send( sourceToken: TokenConfig, amount: string, - fromChainV1: ChainName | ChainId, + fromChainV1: ChainName, senderAddress: string, toChainV1: ChainName | ChainId, recipientAddress: string, @@ -406,7 +406,10 @@ export class SDKv2Route extends RouteAbstract { const fromChainV2 = await this.getV2ChainContext(fromChainV1); const toChainV2 = await this.getV2ChainContext(toChainV1); - const sourceTokenV2 = config.sdkConverter.toTokenIdV2(sourceToken); + const sourceTokenV2 = config.sdkConverter.toTokenIdV2( + sourceToken, + fromChainV1, + ); const destTokenV2 = config.sdkConverter.getTokenIdV2ForKey( destToken, @@ -414,7 +417,7 @@ export class SDKv2Route extends RouteAbstract { config.tokens, ); - console.log(sourceTokenV2, destTokenV2); + console.log(sourceToken, sourceTokenV2, destTokenV2); if (!destTokenV2) throw new Error(`Couldn't find destToken`); diff --git a/wormhole-connect/src/routes/sdkv2/signer.ts b/wormhole-connect/src/routes/sdkv2/signer.ts index 964e5bfea..412a822db 100644 --- a/wormhole-connect/src/routes/sdkv2/signer.ts +++ b/wormhole-connect/src/routes/sdkv2/signer.ts @@ -77,6 +77,7 @@ export class SDKv2Signer switch (platform) { case 'Evm': // TODO switch multi-provider to ethers 6 + // and remove this ethers5-to-6 conversion let serialized = ethers6.Transaction.from({ to: tx.transaction.to, data: tx.transaction.data, @@ -90,6 +91,8 @@ export class SDKv2Signer data: tx5.data, }; return unsignedTx as SendResult; + case 'Solana': + return tx.transaction.transaction; default: console.warn(`toSendResult is unimplemented for platform ${platform}`); return tx as SendResult; diff --git a/wormhole-connect/src/utils/wallet/evm.ts b/wormhole-connect/src/utils/wallet/evm.ts index dea465cd9..6279f0f32 100644 --- a/wormhole-connect/src/utils/wallet/evm.ts +++ b/wormhole-connect/src/utils/wallet/evm.ts @@ -1,12 +1,12 @@ import { Wallet } from '@xlabs-libs/wallet-aggregator-core'; -import { SendResult } from 'sdklegacy'; -import { TransactionRequest } from '@ethersproject/abstract-provider'; -import { Deferrable } from '@ethersproject/properties'; import { EVMWallet, InjectedWallet, WalletConnectWallet, } from '@kev1n-peters/wallet-aggregator-evm'; + +import { SignRequestEvm } from 'utils/wallet/types'; + import config from 'config'; export const wallets = { @@ -44,7 +44,7 @@ export async function switchChain(w: Wallet, chainId: number | string) { } export async function signAndSendTransaction( - transaction: SendResult, + request: SignRequestEvm, w: Wallet, chainName: string, options: any, // TODO ?!?!!?!? @@ -53,9 +53,7 @@ export async function signAndSendTransaction( const signer = config.wh.getSigner(chainName); if (!signer) throw new Error('No signer found for chain' + chainName); - const tx = await signer.sendTransaction( - transaction as Deferrable, - ); + const tx = await signer.sendTransaction(request.transaction); let result = await tx.wait(); // TODO move all this to ethers 6 diff --git a/wormhole-connect/src/utils/wallet/index.ts b/wormhole-connect/src/utils/wallet/index.ts index d1a27d9e2..b93892ede 100644 --- a/wormhole-connect/src/utils/wallet/index.ts +++ b/wormhole-connect/src/utils/wallet/index.ts @@ -1,10 +1,4 @@ -import { - ChainId, - ChainName, - Context, - SendResult, - ChainConfig, -} from 'sdklegacy'; +import { ChainId, ChainName, Context, ChainConfig } from 'sdklegacy'; import { NotSupported, Wallet, @@ -25,6 +19,8 @@ import { Dispatch } from 'redux'; import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { SignRequest } from './types'; + export enum TransferWallet { SENDING = 'sending', RECEIVING = 'receiving', @@ -208,7 +204,7 @@ export const watchAsset = async (asset: AssetInfo, type: TransferWallet) => { export const signAndSendTransaction = async ( chain: ChainName, - transaction: SendResult, + request: SignRequest, walletType: TransferWallet, options: any = {}, ): Promise => { @@ -219,45 +215,51 @@ export const signAndSendTransaction = async ( throw new Error('wallet is undefined'); } + if (chainConfig.context === Context.ETH && request.platform === 'Evm') { + const { signAndSendTransaction } = await import('utils/wallet/evm'); + const tx = await signAndSendTransaction(request, wallet, chain, options); + return tx; + } else if ( + chainConfig.context === Context.SOLANA && + request.platform === 'Solana' + ) { + const { signAndSendTransaction } = await import('utils/wallet/solana'); + const tx = await signAndSendTransaction(request, wallet, options); + return tx.id; + } else { + throw new Error('unimplemented'); + } + + /* switch (chainConfig.context) { case Context.ETH: { - const { signAndSendTransaction } = await import('utils/wallet/evm'); - const tx = await signAndSendTransaction( - transaction, - wallet, - chain, - options, - ); - return tx; } case Context.SOLANA: { - const { signAndSendTransaction } = await import('utils/wallet/solana'); - const tx = await signAndSendTransaction(transaction, wallet, options); - return tx.id; } case Context.SUI: { const { signAndSendTransaction } = await import('utils/wallet/sui'); - const tx = await signAndSendTransaction(transaction, wallet); + const tx = await signAndSendTransaction(request, wallet); return tx.id; } case Context.APTOS: { const { signAndSendTransaction } = await import('utils/wallet/aptos'); - const tx = await signAndSendTransaction(transaction, wallet); + const tx = await signAndSendTransaction(request, wallet); return tx.id; } case Context.SEI: { const { signAndSendTransaction } = await import('utils/wallet/sei'); - const tx = await signAndSendTransaction(transaction, wallet); + const tx = await signAndSendTransaction(request, wallet); return tx.id; } case Context.COSMOS: { const { signAndSendTransaction } = await import('utils/wallet/cosmos'); - const tx = await signAndSendTransaction(transaction, wallet); + const tx = await signAndSendTransaction(request, wallet); return tx.id; } default: throw new Error(`Invalid context ${chainConfig.context}`); } + */ }; export const postVaa = async ( diff --git a/wormhole-connect/src/utils/wallet/solana.ts b/wormhole-connect/src/utils/wallet/solana.ts index bc1536895..74f83f001 100644 --- a/wormhole-connect/src/utils/wallet/solana.ts +++ b/wormhole-connect/src/utils/wallet/solana.ts @@ -1,4 +1,3 @@ -import { SendResult } from 'sdklegacy'; import { WalletAdapterNetwork as SolanaNetwork } from '@solana/wallet-adapter-base'; import { Wallet } from '@xlabs-libs/wallet-aggregator-core'; @@ -12,12 +11,9 @@ import { WalletConnectWalletAdapter, } from '@solana/wallet-adapter-wallets'; -import { - clusterApiUrl, - ConfirmOptions, - Connection, - Transaction, -} from '@solana/web3.js'; +import { clusterApiUrl, ConfirmOptions, Connection } from '@solana/web3.js'; + +import { SignRequestSolana } from 'utils/wallet/types'; import { SolanaWallet, @@ -34,10 +30,13 @@ export function fetchOptions() { const connection = new Connection(config.rpcs.solana || clusterApiUrl(tag)); return { - ...getSolanaStandardWallets(connection).reduce((acc, w) => { - acc[getWalletName(w)] = w; - return acc; - }, {} as Record), + ...getSolanaStandardWallets(connection).reduce( + (acc, w) => { + acc[getWalletName(w)] = w; + return acc; + }, + {} as Record, + ), bitget: new SolanaWallet(new BitgetWalletAdapter(), connection), clover: new SolanaWallet(new CloverWalletAdapter(), connection), coin98: new SolanaWallet(new Coin98WalletAdapter(), connection), @@ -63,7 +62,7 @@ export function fetchOptions() { } export async function signAndSendTransaction( - transaction: SendResult, + request: SignRequestSolana, wallet: Wallet | undefined, options?: ConfirmOptions, ) { @@ -72,7 +71,7 @@ export async function signAndSendTransaction( } return await (wallet as SolanaWallet).signAndSendTransaction({ - transaction: transaction as Transaction, + transaction: request.transaction, options, }); } diff --git a/wormhole-connect/src/utils/wallet/types.ts b/wormhole-connect/src/utils/wallet/types.ts new file mode 100644 index 000000000..e43798693 --- /dev/null +++ b/wormhole-connect/src/utils/wallet/types.ts @@ -0,0 +1,17 @@ +import { Transaction as SolanaTransaction } from '@solana/web3.js'; + +import { TransactionRequest as EvmTransactionRequest } from '@ethersproject/abstract-provider'; +import { Deferrable } from '@ethersproject/properties'; + +// These types use sdkv2's Platform type for platform +export interface SignRequestEvm { + platform: 'Evm'; + transaction: Deferrable; +} + +export interface SignRequestSolana { + platform: 'Solana'; + transaction: SolanaTransaction; +} + +export type SignRequest = SignRequestEvm | SignRequestSolana; From c6a191d73c5e5b719a8712c5cf5d175bf6fb1be5 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Sat, 29 Jun 2024 21:33:33 -0500 Subject: [PATCH 5/7] Added RouteContext and useTrackTransfer hook --- wormhole-connect/src/AppRouter.tsx | 5 +- wormhole-connect/src/WormholeConnect.tsx | 5 +- .../src/contexts/RouteContext.tsx | 44 +++++ .../src/hooks/useTrackTransfer.ts | 57 ++++++ wormhole-connect/src/routes/operator.ts | 4 +- wormhole-connect/src/routes/sdkv2/route.ts | 35 ++-- wormhole-connect/src/views/Bridge/Send.tsx | 58 +++++- wormhole-connect/src/views/Redeem/Redeem.tsx | 167 +++++++++--------- wormhole-connect/src/views/Redeem/SendTo.tsx | 37 +++- wormhole-connect/src/views/Redeem/Stepper.tsx | 31 ++-- wormhole-connect/vite.config.ts | 1 + 11 files changed, 318 insertions(+), 126 deletions(-) create mode 100644 wormhole-connect/src/contexts/RouteContext.tsx create mode 100644 wormhole-connect/src/hooks/useTrackTransfer.ts diff --git a/wormhole-connect/src/AppRouter.tsx b/wormhole-connect/src/AppRouter.tsx index 7944a4240..6970a2147 100644 --- a/wormhole-connect/src/AppRouter.tsx +++ b/wormhole-connect/src/AppRouter.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useContext, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { makeStyles } from 'tss-react/mui'; @@ -24,6 +24,7 @@ import { useExternalSearch } from 'hooks/useExternalSearch'; import internalConfig from 'config'; import BridgeV2 from 'views/v2/Bridge'; +import { RouteContext } from 'contexts/RouteContext'; const useStyles = makeStyles()((theme: any) => ({ appContent: { @@ -49,6 +50,7 @@ interface Props { function AppRouter(props: Props) { const { classes } = useStyles(); const dispatch = useDispatch(); + const routeContext = useContext(RouteContext); // We update the global config once when WormholeConnect is first mounted, if a custom // config was provided. @@ -80,6 +82,7 @@ function AppRouter(props: Props) { if (prevRoute === redeemRoute && route !== redeemRoute) { dispatch(clearRedeem()); dispatch(clearWallets()); + routeContext.clear(); internalConfig.wh.registerProviders(); // reset providers that may have been set during transfer } // reset transfer state on leave diff --git a/wormhole-connect/src/WormholeConnect.tsx b/wormhole-connect/src/WormholeConnect.tsx index 699959663..4d58c6113 100644 --- a/wormhole-connect/src/WormholeConnect.tsx +++ b/wormhole-connect/src/WormholeConnect.tsx @@ -9,6 +9,7 @@ import { getDesignTokens, dark } from './theme'; import ErrorBoundary from './components/ErrorBoundary'; import { WormholeConnectConfig } from './config/types'; import { WormholeConnectPartialTheme } from 'theme'; +import { RouteProvider } from './contexts/RouteContext'; export interface WormholeConnectProps { // theme can be updated at any time to change the colors of Connect @@ -32,7 +33,9 @@ export default function WormholeConnect({ - + + + diff --git a/wormhole-connect/src/contexts/RouteContext.tsx b/wormhole-connect/src/contexts/RouteContext.tsx new file mode 100644 index 000000000..d2c127672 --- /dev/null +++ b/wormhole-connect/src/contexts/RouteContext.tsx @@ -0,0 +1,44 @@ +import React, { useCallback } from 'react'; +import { Network, routes } from '@wormhole-foundation/sdk'; + +interface RouteContextType { + route: routes.Route | null; + receipt: routes.Receipt | null; + setRoute: (route: routes.Route) => void; + setReceipt: (receipt: routes.Receipt) => void; + clear: () => void; +} + +export const RouteContext = React.createContext({ + route: null, + receipt: null, + setRoute: () => { + // Keep the empty function for initial context value + }, + setReceipt: () => { + // Keep the empty function for initial context value + }, + clear: () => { + // Keep the empty function for initial context value + }, +}); + +export const RouteProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [route, setRoute] = React.useState | null>(null); + const [receipt, setReceipt] = React.useState(null); + + const clear = useCallback(() => { + setRoute(null); + setReceipt(null); + }, []); + + return ( + + {children} + + ); +}; diff --git a/wormhole-connect/src/hooks/useTrackTransfer.ts b/wormhole-connect/src/hooks/useTrackTransfer.ts new file mode 100644 index 000000000..edbccf7d4 --- /dev/null +++ b/wormhole-connect/src/hooks/useTrackTransfer.ts @@ -0,0 +1,57 @@ +import { isCompleted } from '@wormhole-foundation/sdk'; +import { RouteContext } from 'contexts/RouteContext'; +import { useContext, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { setTransferComplete } from 'store/redeem'; +import { sleep } from 'utils'; + +const TRACK_TIMEOUT = 120 * 1000; + +// TODO: document this hook, especially since it sets and depends on the receipt state +const useTrackTransfer = () => { + const dispatch = useDispatch(); + + const routeContext = useContext(RouteContext); + + useEffect(() => { + let isActive = true; + + const track = async () => { + const { route, receipt } = routeContext; + if (!route || !receipt) { + return; + } + while (isActive && !isCompleted(receipt)) { + try { + // TODO: the timeout may be longer for chains with slower finality times + // but we will retry so maybe it doesn't matter + const result = await route.track(receipt, TRACK_TIMEOUT).next(); + if (result.done || !isActive) { + break; + } + const currentReceipt = result.value; + if (currentReceipt.state !== receipt.state) { + routeContext.setReceipt(currentReceipt); + if (isCompleted(currentReceipt)) { + dispatch(setTransferComplete(true)); + } + break; + } + } catch (e) { + console.error('Error tracking transfer:', e); + } + // retry + // TODO: exponential backoff depending on the current state? + await sleep(5000); + } + }; + + track(); + + return () => { + isActive = false; + }; + }, [routeContext]); +}; + +export default useTrackTransfer; diff --git a/wormhole-connect/src/routes/operator.ts b/wormhole-connect/src/routes/operator.ts index 198b12f54..6af72d465 100644 --- a/wormhole-connect/src/routes/operator.ts +++ b/wormhole-connect/src/routes/operator.ts @@ -16,7 +16,7 @@ import { } from './types'; import { TokenPrices } from 'store/tokenPrices'; -import { routes } from '@wormhole-foundation/sdk'; +import { Network, routes } from '@wormhole-foundation/sdk'; import { getRoute } from './mappings'; @@ -436,7 +436,7 @@ export class Operator { recipientAddress: string, destToken: string, routeOptions: any, - ): Promise { + ): Promise<[routes.Route, routes.Receipt]> { const r = this.getRoute(route); return await r.send( token, diff --git a/wormhole-connect/src/routes/sdkv2/route.ts b/wormhole-connect/src/routes/sdkv2/route.ts index cd79e564a..3e9c7994b 100644 --- a/wormhole-connect/src/routes/sdkv2/route.ts +++ b/wormhole-connect/src/routes/sdkv2/route.ts @@ -38,10 +38,7 @@ export class SDKv2Route extends RouteAbstract { NATIVE_GAS_DROPOFF_SUPPORTED = false; AUTOMATIC_DEPOSIT = false; - constructor( - readonly rc: routes.RouteConstructor, - routeType: Route, - ) { + constructor(readonly rc: routes.RouteConstructor, routeType: Route) { super(); this.TYPE = routeType; } @@ -401,7 +398,10 @@ export class SDKv2Route extends RouteAbstract { destToken: string, options: any, ): Promise< - SourceInitiatedTransferReceipt | SourceFinalizedTransferReceipt + [ + routes.Route, + SourceInitiatedTransferReceipt | SourceFinalizedTransferReceipt, + ] > { const fromChainV2 = await this.getV2ChainContext(fromChainV1); const toChainV2 = await this.getV2ChainContext(toChainV1); @@ -460,14 +460,14 @@ export class SDKv2Route extends RouteAbstract { receipt.state == TransferState.SourceInitiated || receipt.state == TransferState.SourceFinalized ) { - return receipt; + return [route, receipt]; } } throw new Error('Never got a SourceInitiate state in receipt'); } - public redeem( + public async redeem( destChain: ChainName | ChainId, messageInfo: SignedMessage, recipient: string, @@ -496,16 +496,29 @@ export class SDKv2Route extends RouteAbstract { ]; } - public getTransferSourceInfo( + async getTransferSourceInfo( params: T, ): Promise { - throw new Error('Method not implemented.'); + return [ + { + title: 'test', + value: 'testvalue', + }, + ]; } - public getTransferDestInfo( + async getTransferDestInfo( params: T, ): Promise { - throw new Error('Method not implemented.'); + return { + route: this.TYPE, + displayData: [ + { + title: 'test', + value: 'testvalue', + }, + ], + }; } async getRelayerFee( diff --git a/wormhole-connect/src/views/Bridge/Send.tsx b/wormhole-connect/src/views/Bridge/Send.tsx index c6d772784..620e69333 100644 --- a/wormhole-connect/src/views/Bridge/Send.tsx +++ b/wormhole-connect/src/views/Bridge/Send.tsx @@ -1,4 +1,10 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Context } from 'sdklegacy'; //import type { TokenId } from 'sdklegacy'; @@ -12,7 +18,13 @@ import { setSendTx, setRoute as setRedeemRoute, } from 'store/redeem'; -import { displayWalletAddress, sleep } from 'utils'; +import { + displayWalletAddress, + getTokenDecimals, + getWrappedToken, + getWrappedTokenId, + sleep, +} from 'utils'; import { LINK } from 'utils/style'; import { registerWalletSigner, @@ -38,6 +50,7 @@ import { useDebounce } from 'use-debounce'; import { isPorticoRoute } from 'routes/porticoBridge/utils'; import { interpretTransferError } from 'utils/errors'; import { getTokenDetails } from 'telemetry'; +import { RouteContext } from 'contexts/RouteContext'; const useStyles = makeStyles()((theme) => ({ body: { @@ -91,6 +104,8 @@ function Send(props: { valid: boolean }) { route, ); + const routeContext = useContext(RouteContext); + async function send() { setSendError(''); await validate( @@ -204,13 +219,48 @@ function Send(props: { valid: boolean }) { // TODO THERE IS NO ANALOGUE OF THIS FOR SDKv2 AHHHH dispatch(setTxDetails(message)); - } else if (typeof sendResult.state === 'number') { + } else { // SDKv2 Receipt // SDKv2Route handles waiting for completion internally so there's nothing else to do here - const receipt = sendResult; + const [sdkRoute, receipt] = sendResult; if ('originTxs' in receipt) { txId = receipt.originTxs[receipt.originTxs.length - 1].txid; } + // TODO: SDKV2 set the tx details using on-chain data + // because they might be different than what we have in memory (relayer fee) + // or we may not have all the data (e.g. block) + dispatch( + setTxDetails({ + sendTx: txId, + sender: sending.address, + amount, + payloadID: sdkRoute.IS_AUTOMATIC ? 1 : 3, // TODO: don't need this + recipient: receiving.address, + toChain: config.sdkConverter.toChainNameV1(receipt.to), + fromChain: config.sdkConverter.toChainNameV1(receipt.from), + tokenAddress: getWrappedToken(sendToken).tokenId!.address, + tokenChain: config.sdkConverter.toChainNameV1(receipt.from), + tokenId: getWrappedTokenId(sendToken), + tokenKey: sendToken.key, + tokenDecimals: getTokenDecimals( + config.wh.toChainId(fromChain!), + getWrappedTokenId(sendToken), + ), + receivedTokenKey: config.tokens[destToken].key!, // TODO: wrong + emitterAddress: undefined, + sequence: undefined, + block: 0, + gasFee: undefined, + payload: undefined, + inputData: undefined, + relayerPayloadId: undefined, + to: undefined, + relayerFee: undefined, + toNativeTokenAmount: undefined, + }), + ); + routeContext.setRoute(sdkRoute); + routeContext.setReceipt(receipt); } dispatch(setIsTransactionInProgress(false)); diff --git a/wormhole-connect/src/views/Redeem/Redeem.tsx b/wormhole-connect/src/views/Redeem/Redeem.tsx index aaa8580b4..4fe211544 100644 --- a/wormhole-connect/src/views/Redeem/Redeem.tsx +++ b/wormhole-connect/src/views/Redeem/Redeem.tsx @@ -12,7 +12,6 @@ import { import { sleep } from 'utils'; import { fetchIsVAAEnqueued } from 'utils/vaa'; import { SignedMessage, isNttRoute } from 'routes'; -import RouteOperator from 'routes/operator'; import { ParsedMessage, ParsedRelayerMessage } from 'utils/sdk'; import PageHeader from 'components/PageHeader'; @@ -25,9 +24,8 @@ import useDeliveryStatus from 'hooks/useDeliveryStatus'; import useCheckInboundQueuedTransfer from 'hooks/useCheckInboundQueuedTransfer'; import useConfirmBeforeLeaving from 'utils/confirmBeforeLeaving'; -import { INVALID_VAA_MESSAGE } from 'utils/repairVaa'; -import { getTokenDetails } from 'telemetry'; +import useTrackTransfer from 'hooks/useTrackTransfer'; function Redeem({ setSignedMessage, @@ -85,91 +83,92 @@ function Redeem({ }; }, [txData, signedMessage, route, setIsVaaEnqueued]); - // fetch the VAA - useEffect(() => { - if (!route || !txData?.sendTx || transferComplete) { - return; - } - let cancelled = false; - (async () => { - let i = 0; - let signed: SignedMessage | undefined; - while (signed === undefined && !cancelled) { - try { - signed = await RouteOperator.getSignedMessage(route, txData); - } catch (e: any) { - if (e?.message === INVALID_VAA_MESSAGE) { - console.error(e); - setInvalidVaa(true); - cancelled = true; - } - signed = undefined; - } - if (cancelled) { - return; - } - if (signed !== undefined) { - setSignedMessage(signed); - } else { - await sleep(i < 10 ? 3000 : 30000); - } - i++; - } - })(); - return () => { - cancelled = true; - }; - }, [txData, route, setSignedMessage, transferComplete]); + //// fetch the VAA + //useEffect(() => { + // if (!route || !txData?.sendTx || transferComplete) { + // return; + // } + // let cancelled = false; + // (async () => { + // let i = 0; + // let signed: SignedMessage | undefined; + // while (signed === undefined && !cancelled) { + // try { + // signed = await RouteOperator.getSignedMessage(route, txData); + // } catch (e: any) { + // if (e?.message === INVALID_VAA_MESSAGE) { + // console.error(e); + // setInvalidVaa(true); + // cancelled = true; + // } + // signed = undefined; + // } + // if (cancelled) { + // return; + // } + // if (signed !== undefined) { + // setSignedMessage(signed); + // } else { + // await sleep(i < 10 ? 3000 : 30000); + // } + // i++; + // } + // })(); + // return () => { + // cancelled = true; + // }; + //}, [txData, route, setSignedMessage, transferComplete]); - // check if VAA has been redeemed - useEffect(() => { - if (!route || !txData?.toChain || !signedMessage || transferComplete) { - return; - } - let cancelled = false; - (async () => { - let i = 0; - let isComplete = false; - while (!isComplete && !cancelled) { - try { - isComplete = await RouteOperator.isTransferCompleted( - route, - txData.toChain, - signedMessage, - ); - } catch (e) { - console.error(e); - } - if (cancelled) { - return; - } - if (isComplete) { - setTransferComplete(); - if (!isResumeTx) { - config.triggerEvent({ - type: 'transfer.success', - details: { - route, - fromToken: getTokenDetails(txData.tokenKey), - toToken: getTokenDetails(txData.receivedTokenKey), - fromChain: txData.fromChain, - toChain: txData.toChain, - }, - }); - } - } else { - await sleep(i < 10 ? 3000 : 30000); - } - i++; - } - })(); - return () => { - cancelled = true; - }; - }, [route, txData, transferComplete, setTransferComplete, signedMessage]); + //// check if VAA has been redeemed + //useEffect(() => { + // if (!route || !txData?.toChain || !signedMessage || transferComplete) { + // return; + // } + // let cancelled = false; + // (async () => { + // let i = 0; + // let isComplete = false; + // while (!isComplete && !cancelled) { + // try { + // isComplete = await RouteOperator.isTransferCompleted( + // route, + // txData.toChain, + // signedMessage, + // ); + // } catch (e) { + // console.error(e); + // } + // if (cancelled) { + // return; + // } + // if (isComplete) { + // setTransferComplete(); + // if (!isResumeTx) { + // config.triggerEvent({ + // type: 'transfer.success', + // details: { + // route, + // fromToken: getTokenDetails(txData.tokenKey), + // toToken: getTokenDetails(txData.receivedTokenKey), + // fromChain: txData.fromChain, + // toChain: txData.toChain, + // }, + // }); + // } + // } else { + // await sleep(i < 10 ? 3000 : 30000); + // } + // i++; + // } + // })(); + // return () => { + // cancelled = true; + // }; + //}, [route, txData, transferComplete, setTransferComplete, signedMessage]); useCheckInboundQueuedTransfer(); useDeliveryStatus(); + useTrackTransfer(); return txData?.fromChain ? (
{ setWalletModal(true); }; @@ -225,16 +236,28 @@ function SendTo() { await switchChain(chainConfig.chainId, TransferWallet.RECEIVING); registerWalletSigner(txData.toChain, TransferWallet.RECEIVING); } - if (!signedMessage) { - throw new Error('failed to get vaa, cannot redeem'); - } - txId = await RouteOperator.redeem( - routeName, + //txId = await RouteOperator.redeem( + // routeName, + // txData.toChain, + // receipt, + // wallet.address, + //); + + const route = routeContext.route!; + if (!routes.isManual(route)) { + throw new Error('Route is not manual'); + } + const signer = await SDKv2Signer.fromChainV1( txData.toChain, - signedMessage, wallet.address, + {}, ); + const result = await route.complete(signer, routeContext.receipt!); + if (!isRedeemed(result)) { + throw new Error('Transfer not redeemed'); + } + txId = result.destinationTxs?.[0]?.txid || ''; config.triggerEvent({ type: 'transfer.redeem.start', diff --git a/wormhole-connect/src/views/Redeem/Stepper.tsx b/wormhole-connect/src/views/Redeem/Stepper.tsx index 6f6c8891f..e2eba2581 100644 --- a/wormhole-connect/src/views/Redeem/Stepper.tsx +++ b/wormhole-connect/src/views/Redeem/Stepper.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { useSelector } from 'react-redux'; import { RootState } from 'store'; -import { UnsignedMessage } from 'routes'; import Stepper from 'components/Stepper/Stepper'; import SendFrom from './SendFrom'; @@ -10,25 +9,25 @@ import SendTo from './SendTo'; import BridgeComplete from './BridgeComplete'; //import { isPorticoTransferDestInfo } from 'routes/porticoBridge/utils'; +import { isAttested } from '@wormhole-foundation/sdk'; +import { RouteContext } from 'contexts/RouteContext'; + export default function MilestoneStepper() { - const signedMessage: UnsignedMessage | undefined = useSelector( - (state: RootState) => state.redeem.signedMessage, - ); + const routeContext = React.useContext(RouteContext); + const attested = routeContext.receipt + ? isAttested(routeContext.receipt) + : false; const transferComplete = useSelector( (state: RootState) => state.redeem.transferComplete, ); - /* - const transferDestInfo = useSelector( - (state: RootState) => state.redeem.transferDestInfo, - ); - */ - let showWarning = false; - /* - if (isPorticoTransferDestInfo(transferDestInfo)) { - showWarning = !!transferDestInfo.destTxInfo.swapFailed; - } - */ - const activeStep = transferComplete ? 4 : signedMessage ? 2 : 1; + //const transferDestInfo = useSelector( + // (state: RootState) => state.redeem.transferDestInfo, + //); + const showWarning = false; + //if (isPorticoTransferDestInfo(transferDestInfo)) { + // showWarning = !!transferDestInfo.destTxInfo.swapFailed; + //} + const activeStep = transferComplete ? 4 : attested ? 2 : 1; const steps = [ { diff --git a/wormhole-connect/vite.config.ts b/wormhole-connect/vite.config.ts index 7401ddb59..2834ede43 100644 --- a/wormhole-connect/vite.config.ts +++ b/wormhole-connect/vite.config.ts @@ -31,6 +31,7 @@ const resolve = { utils: path.resolve(__dirname, './src/utils'), config: path.resolve(__dirname, './src/config'), components: path.resolve(__dirname, './src/components'), + contexts: path.resolve(__dirname, './src/contexts'), // This was originally called "events" and that breaks some NPM dependency // so do not rename it "events": telemetry: path.resolve(__dirname, './src/telemetry'), From aa798dd8d6a0af58c1344c6fb79c20c0963204fd Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Tue, 2 Jul 2024 20:13:49 -0400 Subject: [PATCH 6/7] flesh out signing for aptos, sui, cosmos --- wormhole-connect/src/routes/sdkv2/signer.ts | 43 +++++++++++++--- wormhole-connect/src/utils/wallet/aptos.ts | 10 ++-- wormhole-connect/src/utils/wallet/cosmos.ts | 54 +++++++++++---------- wormhole-connect/src/utils/wallet/sui.ts | 6 +-- wormhole-connect/src/utils/wallet/types.ts | 29 ++++++++++- 5 files changed, 98 insertions(+), 44 deletions(-) diff --git a/wormhole-connect/src/routes/sdkv2/signer.ts b/wormhole-connect/src/routes/sdkv2/signer.ts index 412a822db..df8650d9b 100644 --- a/wormhole-connect/src/routes/sdkv2/signer.ts +++ b/wormhole-connect/src/routes/sdkv2/signer.ts @@ -8,7 +8,7 @@ import { TxHash, } from '@wormhole-foundation/sdk'; //import { EvmUnsignedTransaction } from '@wormhole-foundation/sdk-evm'; -import { ChainId, ChainName, SendResult } from 'sdklegacy'; +import { ChainId, ChainName } from 'sdklegacy'; import config, { getWormholeContextV2 } from 'config'; import { signAndSendTransaction, TransferWallet } from 'utils/wallet'; import * as ethers5 from 'ethers5'; @@ -17,6 +17,8 @@ import * as ethers6 from 'ethers'; import { TransactionRequest } from '@ethersproject/abstract-provider'; import { Deferrable } from '@ethersproject/properties'; +import { SignRequest } from 'utils/wallet/types'; + // Utility class that bridges between legacy Connect signer interface and SDKv2 signer interface export class SDKv2Signer implements SignAndSendSigner @@ -57,11 +59,11 @@ export class SDKv2Signer let txHashes: TxHash[] = []; for (let tx of txs) { - let sendResult: SendResult = this.toSendResult(tx); + let request: SignRequest = this.createSignRequest(tx); let txId = await signAndSendTransaction( this._chainNameV1, - sendResult, + request, TransferWallet.SENDING, this._options, ); @@ -71,7 +73,8 @@ export class SDKv2Signer return txHashes; } - private toSendResult(tx: UnsignedTransaction): SendResult { + // This takes an SDKv2 UnsignedTransaction and prepares it for use by xlabs-wallet-adapter + private createSignRequest(tx: UnsignedTransaction): SignRequest { const platform = chainToPlatform(tx.chain); switch (platform) { @@ -90,12 +93,36 @@ export class SDKv2Signer chainId: tx5.chainId, data: tx5.data, }; - return unsignedTx as SendResult; + return { + platform, + transaction: unsignedTx, + }; case 'Solana': - return tx.transaction.transaction; + return { + platform, + transaction: tx.transaction.transaction, + }; + case 'Cosmwasm': + debugger; + return { + platform, + transaction: tx, + }; + case 'Sui': + return { + platform, + transaction: tx, + }; + case 'Aptos': + return { + platform, + transaction: tx.transaction, + }; default: - console.warn(`toSendResult is unimplemented for platform ${platform}`); - return tx as SendResult; + throw new Error( + `toSendResult is unimplemented for platform ${platform}`, + ); + //return tx as SendResult; } } diff --git a/wormhole-connect/src/utils/wallet/aptos.ts b/wormhole-connect/src/utils/wallet/aptos.ts index abb2530e3..5b3e1b430 100644 --- a/wormhole-connect/src/utils/wallet/aptos.ts +++ b/wormhole-connect/src/utils/wallet/aptos.ts @@ -1,8 +1,3 @@ -import { - //AptosContext, - SendResult, - //WormholeContext, -} from 'sdklegacy'; import { Wallet } from '@xlabs-libs/wallet-aggregator-core'; import { AptosSnapAdapter, @@ -21,6 +16,7 @@ import { AptosWallet } from '@xlabs-libs/wallet-aggregator-aptos'; import { Types } from 'aptos'; import config from 'config'; +import { SignRequestAptos } from './types'; const aptosWallets = { aptos: new AptosWallet(new AptosWalletAdapter()), @@ -45,11 +41,11 @@ export function fetchOptions() { } export async function signAndSendTransaction( - transaction: SendResult, + request: SignRequestAptos, wallet: Wallet | undefined, ) { // The wallets do not handle Uint8Array serialization - const payload = transaction as Types.EntryFunctionPayload; + const payload = request.transaction as Types.EntryFunctionPayload; if (payload.arguments) { payload.arguments = payload.arguments.map((a: any) => a instanceof Uint8Array ? Array.from(a) : a, diff --git a/wormhole-connect/src/utils/wallet/cosmos.ts b/wormhole-connect/src/utils/wallet/cosmos.ts index 65dfb0df6..b4f15802b 100644 --- a/wormhole-connect/src/utils/wallet/cosmos.ts +++ b/wormhole-connect/src/utils/wallet/cosmos.ts @@ -1,26 +1,26 @@ import { Wallet } from '@xlabs-libs/wallet-aggregator-core'; -import { - CosmosTransaction, - CosmosWallet, - getWallets, -} from '@xlabs-libs/wallet-aggregator-cosmos'; +import { CosmosWallet, getWallets } from '@xlabs-libs/wallet-aggregator-cosmos'; import { CosmosEvmWallet, getWallets as getEvmWallets, } from '@xlabs-libs/wallet-aggregator-cosmos-evm'; import config from 'config'; -import { ChainName, Context, SendResult, ChainResourceMap } from 'sdklegacy'; +import { ChainName, Context, ChainResourceMap } from 'sdklegacy'; +import { SignRequestCosmos } from './types'; const getCosmosWalletsEndpointsMap = () => { const prepareMap = (map: ChainResourceMap) => - Object.keys(map).reduce((acc, k) => { - const conf = config.chains[k as ChainName]; - if (conf?.chainId && conf?.context === Context.COSMOS) { - acc[conf.chainId] = map[k as ChainName]!; - } - return acc; - }, {} as Record); + Object.keys(map).reduce( + (acc, k) => { + const conf = config.chains[k as ChainName]; + if (conf?.chainId && conf?.context === Context.COSMOS) { + acc[conf.chainId] = map[k as ChainName]!; + } + return acc; + }, + {} as Record, + ); const rpcs = prepareMap(config.rpcs); const rests = prepareMap(config.rest); @@ -32,20 +32,26 @@ const buildCosmosEvmWallets = () => { const { rests, rpcs } = getCosmosWalletsEndpointsMap(); const wallets: CosmosEvmWallet[] = getEvmWallets(rpcs, rests); - return wallets.reduce((acc, w: CosmosEvmWallet) => { - acc[w.getName()] = w; - return acc; - }, {} as Record); + return wallets.reduce( + (acc, w: CosmosEvmWallet) => { + acc[w.getName()] = w; + return acc; + }, + {} as Record, + ); }; const buildCosmosWallets = () => { const { rests, rpcs } = getCosmosWalletsEndpointsMap(); const wallets: CosmosWallet[] = getWallets(rpcs, rests); - return wallets.reduce((acc, w: CosmosWallet) => { - acc[w.getName()] = w; - return acc; - }, {} as Record); + return wallets.reduce( + (acc, w: CosmosWallet) => { + acc[w.getName()] = w; + return acc; + }, + {} as Record, + ); }; export const wallets = { @@ -54,13 +60,11 @@ export const wallets = { }; export async function signAndSendTransaction( - transaction: SendResult, + request: SignRequestCosmos, wallet: Wallet | undefined, ) { const cosmosWallet = wallet as CosmosWallet; - const result = await cosmosWallet.signAndSendTransaction( - transaction as CosmosTransaction, - ); + const result = await cosmosWallet.signAndSendTransaction(request.transaction); if (result.data?.code) { throw new Error( diff --git a/wormhole-connect/src/utils/wallet/sui.ts b/wormhole-connect/src/utils/wallet/sui.ts index cef0784d4..334b26507 100644 --- a/wormhole-connect/src/utils/wallet/sui.ts +++ b/wormhole-connect/src/utils/wallet/sui.ts @@ -1,7 +1,7 @@ import { TransactionBlock } from '@mysten/sui.js'; -import { SendResult } from 'sdklegacy'; import { SuiWallet, getWallets } from '@xlabs-libs/wallet-aggregator-sui'; import { Wallet } from '@xlabs-libs/wallet-aggregator-core'; +import { SignRequestSui } from './types'; export async function fetchOptions() { const suiWallets = await getWallets({ timeout: 0 }); @@ -12,7 +12,7 @@ export async function fetchOptions() { } export const signAndSendTransaction = async ( - transaction: SendResult, + request: SignRequestSui, wallet: Wallet, ) => { if (!wallet || !wallet.signAndSendTransaction) { @@ -20,6 +20,6 @@ export const signAndSendTransaction = async ( } return await wallet.signAndSendTransaction({ - transactionBlock: transaction as TransactionBlock, + transactionBlock: request.transaction as TransactionBlock, }); }; diff --git a/wormhole-connect/src/utils/wallet/types.ts b/wormhole-connect/src/utils/wallet/types.ts index e43798693..d5e0a4202 100644 --- a/wormhole-connect/src/utils/wallet/types.ts +++ b/wormhole-connect/src/utils/wallet/types.ts @@ -3,6 +3,8 @@ import { Transaction as SolanaTransaction } from '@solana/web3.js'; import { TransactionRequest as EvmTransactionRequest } from '@ethersproject/abstract-provider'; import { Deferrable } from '@ethersproject/properties'; +import { Types as AptosTypes } from 'aptos'; + // These types use sdkv2's Platform type for platform export interface SignRequestEvm { platform: 'Evm'; @@ -14,4 +16,29 @@ export interface SignRequestSolana { transaction: SolanaTransaction; } -export type SignRequest = SignRequestEvm | SignRequestSolana; +export interface SignRequestCosmos { + platform: 'Cosmwasm'; + // TODO using CosmosTransaction here would require importing all of + // wallet-adapter-cosmos, which we don't want to do here because + // it would bloat the bundle. + transaction: any; +} + +export interface SignRequestSui { + platform: 'Sui'; + // TODO + transaction: any; +} + +export interface SignRequestAptos { + platform: 'Aptos'; + // TODO + transaction: AptosTypes.EntryFunctionPayload; +} + +export type SignRequest = + | SignRequestEvm + | SignRequestSolana + | SignRequestCosmos + | SignRequestSui + | SignRequestAptos; From 2f291068107434434975b7895e6ff3c3f68f185e Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Tue, 2 Jul 2024 20:27:29 -0400 Subject: [PATCH 7/7] fix lint errors --- wormhole-connect/src/config/converter.ts | 2 +- wormhole-connect/src/routes/sdkv2/signer.ts | 95 +++++++++---------- wormhole-connect/src/sdklegacy/README.md | 40 ++++---- wormhole-connect/src/sdklegacy/utils.ts | 4 +- wormhole-connect/src/sdklegacy/wormhole.ts | 16 +++- wormhole-connect/src/utils/wallet/evm.ts | 2 +- .../src/views/Redeem/BridgeComplete.tsx | 4 +- 7 files changed, 84 insertions(+), 79 deletions(-) diff --git a/wormhole-connect/src/config/converter.ts b/wormhole-connect/src/config/converter.ts index 73970436f..8fcf8f18b 100644 --- a/wormhole-connect/src/config/converter.ts +++ b/wormhole-connect/src/config/converter.ts @@ -71,7 +71,7 @@ export class SDKConverter { if (this.isTokenConfigV1(token)) { if (chain && chain != token.nativeChain) { // Getting foreign address - let foreignAsset = token.foreignAssets?.[chain]; + const foreignAsset = token.foreignAssets?.[chain]; if (foreignAsset) { return v2.Wormhole.tokenId( this.toChainV2(chain), diff --git a/wormhole-connect/src/routes/sdkv2/signer.ts b/wormhole-connect/src/routes/sdkv2/signer.ts index df8650d9b..872854272 100644 --- a/wormhole-connect/src/routes/sdkv2/signer.ts +++ b/wormhole-connect/src/routes/sdkv2/signer.ts @@ -56,12 +56,12 @@ export class SDKv2Signer } async signAndSend(txs: UnsignedTransaction[]): Promise { - let txHashes: TxHash[] = []; + const txHashes: TxHash[] = []; - for (let tx of txs) { - let request: SignRequest = this.createSignRequest(tx); + for (const tx of txs) { + const request: SignRequest = this.createSignRequest(tx); - let txId = await signAndSendTransaction( + const txId = await signAndSendTransaction( this._chainNameV1, request, TransferWallet.SENDING, @@ -77,51 +77,48 @@ export class SDKv2Signer private createSignRequest(tx: UnsignedTransaction): SignRequest { const platform = chainToPlatform(tx.chain); - switch (platform) { - case 'Evm': - // TODO switch multi-provider to ethers 6 - // and remove this ethers5-to-6 conversion - let serialized = ethers6.Transaction.from({ - to: tx.transaction.to, - data: tx.transaction.data, - }).unsignedSerialized; - let tx5: ethers5.Transaction = - ethers5.utils.parseTransaction(serialized); - let unsignedTx: Deferrable = { - to: tx5.to, - type: tx5.type as number, - chainId: tx5.chainId, - data: tx5.data, - }; - return { - platform, - transaction: unsignedTx, - }; - case 'Solana': - return { - platform, - transaction: tx.transaction.transaction, - }; - case 'Cosmwasm': - debugger; - return { - platform, - transaction: tx, - }; - case 'Sui': - return { - platform, - transaction: tx, - }; - case 'Aptos': - return { - platform, - transaction: tx.transaction, - }; - default: - throw new Error( - `toSendResult is unimplemented for platform ${platform}`, - ); + if (platform === 'Evm') { + // TODO switch multi-provider to ethers 6 + // and remove this ethers5-to-6 conversion + const serialized = ethers6.Transaction.from({ + to: tx.transaction.to, + data: tx.transaction.data, + }).unsignedSerialized; + const tx5: ethers5.Transaction = + ethers5.utils.parseTransaction(serialized); + const unsignedTx: Deferrable = { + to: tx5.to, + type: tx5.type as number, + chainId: tx5.chainId, + data: tx5.data, + }; + return { + platform, + transaction: unsignedTx, + }; + } else if (platform === 'Solana') { + return { + platform, + transaction: tx.transaction.transaction, + }; + } else if (platform === 'Cosmwasm') { + //debugger; + return { + platform, + transaction: tx, + }; + } else if (platform === 'Sui') { + return { + platform, + transaction: tx, + }; + } else if (platform === 'Aptos') { + return { + platform, + transaction: tx.transaction, + }; + } else { + throw new Error(`toSendResult is unimplemented for platform ${platform}`); //return tx as SendResult; } } diff --git a/wormhole-connect/src/sdklegacy/README.md b/wormhole-connect/src/sdklegacy/README.md index 8e3930360..4dc600376 100644 --- a/wormhole-connect/src/sdklegacy/README.md +++ b/wormhole-connect/src/sdklegacy/README.md @@ -1,27 +1,25 @@ -Wormhole Connect SDK --------------------- - +## Wormhole Connect SDK An SDK that wraps the core Wormhole SDK and provides a convenient API to interact with the Wormhole Token Bridge protocol. +Here is an example showing how to send a token across chains using this SDK: -Here is an example showing how to send a token across chains using this SDK: ```ts - const context = new WormholeContext('MAINNET'); - - // interact easily with any chain! - // supports EVM, Solana, Terra, etc - const tokenId = { - chain: 'ethereum', - address: '0x123...', - } +const context = new WormholeContext('MAINNET'); + +// interact easily with any chain! +// supports EVM, Solana, Terra, etc +const tokenId = { + chain: 'ethereum', + address: '0x123...', +}; - const receipt = context.send( - tokenId, - '10', // amount - 'ethereum', // sending chain - '0x789...', // sender address - 'moonbeam', // destination chain - '0x789...', // recipient address on destination chain - ) -``` \ No newline at end of file +const receipt = context.send( + tokenId, + '10', // amount + 'ethereum', // sending chain + '0x789...', // sender address + 'moonbeam', // destination chain + '0x789...', // recipient address on destination chain +); +``` diff --git a/wormhole-connect/src/sdklegacy/utils.ts b/wormhole-connect/src/sdklegacy/utils.ts index 3040c3941..45d881edd 100644 --- a/wormhole-connect/src/sdklegacy/utils.ts +++ b/wormhole-connect/src/sdklegacy/utils.ts @@ -61,7 +61,9 @@ export const waitFor = ( clearInterval(interval); resolve(); } - } catch (e) {} + } catch (e) { + console.error(e); + } count++; }, ms); diff --git a/wormhole-connect/src/sdklegacy/wormhole.ts b/wormhole-connect/src/sdklegacy/wormhole.ts index e2f6996b2..44f918b31 100644 --- a/wormhole-connect/src/sdklegacy/wormhole.ts +++ b/wormhole-connect/src/sdklegacy/wormhole.ts @@ -195,7 +195,9 @@ export class WormholeContext extends MultiProvider { // BEGIN stubbed methods for SDKV2 migration // TODO SDKV2 - sign() {} + sign() { + console.log('TODO remove'); + } async approve(a: any, b: any, c: any, d: any): Promise { return true; @@ -206,12 +208,18 @@ export class WormholeContext extends MultiProvider { } /* @ts-ignore */ - mustGetProvider(a: any) {} + mustGetProvider(a: any) { + console.log('TODO remove'); + } get contracts() { return { - mustGetCore(a: any) {}, - mustGetBridge(a: any) {}, + mustGetCore(a: any) { + console.log('TODO remove'); + }, + mustGetBridge(a: any) { + console.log('TODO remove'); + }, }; } } diff --git a/wormhole-connect/src/utils/wallet/evm.ts b/wormhole-connect/src/utils/wallet/evm.ts index 6279f0f32..19a876ede 100644 --- a/wormhole-connect/src/utils/wallet/evm.ts +++ b/wormhole-connect/src/utils/wallet/evm.ts @@ -54,7 +54,7 @@ export async function signAndSendTransaction( if (!signer) throw new Error('No signer found for chain' + chainName); const tx = await signer.sendTransaction(request.transaction); - let result = await tx.wait(); + const result = await tx.wait(); // TODO move all this to ethers 6 /* @ts-ignore */ diff --git a/wormhole-connect/src/views/Redeem/BridgeComplete.tsx b/wormhole-connect/src/views/Redeem/BridgeComplete.tsx index 1af1147a3..42bb7ddfa 100644 --- a/wormhole-connect/src/views/Redeem/BridgeComplete.tsx +++ b/wormhole-connect/src/views/Redeem/BridgeComplete.tsx @@ -31,8 +31,8 @@ function BridgeComplete() { dispatch(setRoute('bridge')); }; - let containerBg: string | undefined = undefined; - let component: React.JSX.Element = ( + const containerBg: string | undefined = undefined; + const component: React.JSX.Element = (
The bridge is now complete.