From 1f82f3eea4dbb3468178d2a6fadcccaa62bc320a Mon Sep 17 00:00:00 2001 From: Todd Kao Date: Thu, 10 Oct 2024 15:47:57 -0400 Subject: [PATCH 01/10] Add background to nextjs example (#353) --- examples/nextjs/pages/gobg-dark.svg | 33 +++++++++++++++++++++++++ examples/nextjs/pages/gobg-light.svg | 28 +++++++++++++++++++++ examples/nextjs/pages/index.tsx | 37 ++++++++++++++++++++++------ 3 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 examples/nextjs/pages/gobg-dark.svg create mode 100644 examples/nextjs/pages/gobg-light.svg diff --git a/examples/nextjs/pages/gobg-dark.svg b/examples/nextjs/pages/gobg-dark.svg new file mode 100644 index 00000000..6eb0d070 --- /dev/null +++ b/examples/nextjs/pages/gobg-dark.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/nextjs/pages/gobg-light.svg b/examples/nextjs/pages/gobg-light.svg new file mode 100644 index 00000000..5931a2e3 --- /dev/null +++ b/examples/nextjs/pages/gobg-light.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/nextjs/pages/index.tsx b/examples/nextjs/pages/index.tsx index e9bb847a..d0029bd0 100644 --- a/examples/nextjs/pages/index.tsx +++ b/examples/nextjs/pages/index.tsx @@ -1,15 +1,18 @@ import { SwapWidget } from '@skip-go/widget'; import { defaultTheme, lightTheme } from '@skip-go/widget'; +import darkbg from './gobg-dark.svg'; +import lightbg from './gobg-light.svg'; + import { useState } from 'react'; const Home = () => { - const [theme, setTheme] = useState<"dark" | "light">("dark"); + const [theme, setTheme] = useState<'dark' | 'light'>('dark'); const toggleTheme = () => { - if (theme === "dark") { - setTheme("light"); + if (theme === 'dark') { + setTheme('light'); } else { - setTheme("dark"); + setTheme('dark'); } }; @@ -18,12 +21,30 @@ const Home = () => { style={{ display: 'flex', flexDirection: 'row', - padding: 20 + width: '100vw', + height: '100vh', + alignItems: 'center', + justifyContent: 'center', + backgroundImage: `url('${theme === 'dark' ? darkbg.src : lightbg.src + }')`, + backgroundSize: 'cover', + backgroundPosition: 'center', }} > - -
- + +
+
); From 7939518dfdb332b3abadd3801042efbcb128b4e4 Mon Sep 17 00:00:00 2001 From: Todd Kao Date: Thu, 10 Oct 2024 21:46:05 -0400 Subject: [PATCH 02/10] FRE-1063, FRE-1103, FRE-1105 (#321) Co-authored-by: Nur Fikri --- .../src/components/RenderWalletList.tsx | 29 +++-- packages/widget-v2/src/constants/graz.ts | 17 +-- .../widget-v2/src/hooks/useAutoSetAddress.ts | 3 +- .../src/hooks/useCreateCosmosWallets.tsx | 27 ++--- packages/widget-v2/src/hooks/useGetAccount.ts | 7 -- .../ErrorPage/ErrorPageTransactionFailed.tsx | 3 +- .../SwapExecutionPage/SwapExecutionPage.tsx | 41 +++---- .../SwapExecutionPageRouteDetailed.tsx | 27 +++-- .../SwapExecutionPageRouteDetailedRow.tsx | 56 +++++---- .../SwapExecutionPageRouteSimple.tsx | 36 +++--- .../SwapExecutionPageRouteSimpleRow.tsx | 12 +- .../SwapExecutionPage/useBroadcastedTxs.ts | 75 ++++++++++++ .../useFetchTransactionStatus.tsx | 80 ++++++------- .../widget-v2/src/pages/SwapPage/SwapPage.tsx | 37 +++++- .../TransactionHistoryPageHistoryItem.tsx | 2 +- .../widget-v2/src/state/swapExecutionPage.ts | 29 ++--- packages/widget-v2/src/utils/clientType.ts | 111 ++++++++---------- packages/widget-v2/src/utils/operations.ts | 17 --- 18 files changed, 322 insertions(+), 287 deletions(-) create mode 100644 packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts delete mode 100644 packages/widget-v2/src/utils/operations.ts diff --git a/packages/widget-v2/src/components/RenderWalletList.tsx b/packages/widget-v2/src/components/RenderWalletList.tsx index d00f2aaa..13eac0e2 100644 --- a/packages/widget-v2/src/components/RenderWalletList.tsx +++ b/packages/widget-v2/src/components/RenderWalletList.tsx @@ -3,7 +3,7 @@ import { styled, useTheme } from "styled-components"; import { Row, Column } from "@/components/Layout"; import { ModalRowItem } from "./ModalRowItem"; import { VirtualList } from "./VirtualList"; -import { Text } from "@/components/Typography"; +import { SmallText, Text } from "@/components/Typography"; import { MinimalWallet } from "@/state/wallets"; import { StyledAnimatedBorder } from "@/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow"; import { useMutation } from "@tanstack/react-query"; @@ -91,6 +91,7 @@ export const RenderWalletList = ({ const name = isMinimalWallet(wallet) ? wallet.walletPrettyName ?? wallet.walletName : wallet.walletName; const imageUrl = isMinimalWallet(wallet) ? wallet.walletInfo.logo : undefined; const rightContent = isManualWalletEntry(wallet) ? wallet.rightContent : undefined; + const isAvailable = isMinimalWallet(wallet) ? wallet.isAvailable : undefined; if (wallet.walletName === "prax") { return ( @@ -136,18 +137,20 @@ export const RenderWalletList = ({ }} style={{ marginTop: ITEM_GAP }} leftContent={ - - {imageUrl && ( - {`${name} - )} - - {name} + + + {imageUrl && ( + {`${name} + )} + {name} + + {isAvailable !== undefined && {isAvailable ? "Installed" : "Not Installed"}} } rightContent={rightContent?.()} diff --git a/packages/widget-v2/src/constants/graz.ts b/packages/widget-v2/src/constants/graz.ts index e391ddf5..0e98a0a9 100644 --- a/packages/widget-v2/src/constants/graz.ts +++ b/packages/widget-v2/src/constants/graz.ts @@ -62,7 +62,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "HoneyWood", "LumenX", "Neutaro-1", - "Oraichain", "ShareRing-VoyagerNet", "agoric-3", "aioz_168-1", @@ -75,18 +74,15 @@ export const keplrMainnetChainIdsInitialConnect = [ "arkeo", "athens_7001-1", "atlantic-2", - "aura_6322-2", "axelar-dojo-1", "axelar-testnet-lisbon-3", "band-laozi-testnet6", "banksy-testnet-5", "bbn-dev-5", "bbn-test-3", - "beezee-1", "berberis-1", "birdee-1", "bitcanna-1", - "bitsong-2b", "blockx_100-1", "bluechip-2", "bluzelle-testnet-10", @@ -99,7 +95,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "carbon-1", "celestia", "centauri-1", - "cheqd-mainnet-1", "chihuahua-1", "cifer-2", "cnho_stables-1", @@ -109,11 +104,9 @@ export const keplrMainnetChainIdsInitialConnect = [ "comdex-1", "constantine-3", "core-1", - "coreum-mainnet-1", "coreum-testnet-1", "cosmoshub-4", "coss-1", - "crescent-1", "crossfi-evm-testnet-1", "crypto-org-chain-mainnet-1", "cudos-1", @@ -121,8 +114,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "cvn_2032-1", "desmos-mainnet", "developer", - "dhealth-testnet-2", - "dhealth", "dimension_37-1", "dydx-mainnet-1", "dydx-testnet-4", @@ -137,7 +128,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "entrypoint-pubtest-2", "evmos_9001-2", "fairyring-testnet-1", - "fetchhub-4", "fiamma-testnet-1", "finschia-2", "fivenet", @@ -200,11 +190,9 @@ export const keplrMainnetChainIdsInitialConnect = [ "pacific-1", "panacea-3", "passage-2", - "perun-1", "phoenix-1", "pio-testnet-1", "pion-1", - "pirin-1", "planq_7077-1", "poktroll", "provider", @@ -222,8 +210,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "secret-4", "seda-1-testnet", "seda-1", - "self-dev-1", - "self-1", "sentinelhub-2", "sge-network-4", "sgenet-1", @@ -258,14 +244,13 @@ export const keplrMainnetChainIdsInitialConnect = [ "unicorn-420", "union-testnet-8", "uptick_117-1", - "vota-ash", - "vota-testnet", "wormchain", "xion-testnet-1", "yulei-2.1", "zetachain_7000-1" ]; +// other wallets not the keplr wallet export const walletMainnetChainIdsInitialConnect = [ "cosmoshub-4", "injective-1", diff --git a/packages/widget-v2/src/hooks/useAutoSetAddress.ts b/packages/widget-v2/src/hooks/useAutoSetAddress.ts index e325e687..ff644341 100644 --- a/packages/widget-v2/src/hooks/useAutoSetAddress.ts +++ b/packages/widget-v2/src/hooks/useAutoSetAddress.ts @@ -8,7 +8,6 @@ import { useCreateEvmWallets } from "./useCreateEvmWallets"; import { useCreateSolanaWallets } from "./useCreateSolanaWallets"; import { useEffect, useMemo } from "react"; import { getClientOperations } from "@/utils/clientType"; -import { getSignRequiredChainIds } from "@/utils/operations"; import { SetAddressModal } from "@/modals/SetAddressModal/SetAddressModal"; import { useModal } from "@/components/Modal"; @@ -30,7 +29,7 @@ export const useAutoSetAddress = () => { const signRequiredChains = useMemo(() => { if (!route?.operations) return; const operations = getClientOperations(route.operations); - const signRequiredChains = getSignRequiredChainIds(operations); + const signRequiredChains = operations.filter(o => o.signRequired).map(o => o.fromChainID); return signRequiredChains; }, [route?.operations]); diff --git a/packages/widget-v2/src/hooks/useCreateCosmosWallets.tsx b/packages/widget-v2/src/hooks/useCreateCosmosWallets.tsx index d825cea9..48972216 100644 --- a/packages/widget-v2/src/hooks/useCreateCosmosWallets.tsx +++ b/packages/widget-v2/src/hooks/useCreateCosmosWallets.tsx @@ -1,7 +1,6 @@ import { getChainInfo } from "@/state/chains"; import { cosmosWalletAtom, MinimalWallet } from "@/state/wallets"; import { - getAvailableWallets, getWallet, useAccount, useActiveWalletType, @@ -29,10 +28,6 @@ export const useCreateCosmosWallets = () => { const { data: assets } = useAtomValue(skipAssetsAtom); const setCosmosWallet = useSetAtom(cosmosWalletAtom); const setSourceAsset = useSetAtom(sourceAssetAtom); - const _availableWallets = getAvailableWallets(); - const cosmosWallets = Object.entries(_availableWallets) - .filter(([_, value]) => value) - .map(([key]) => key) as WalletType[]; const { walletType: currentWallet } = useActiveWalletType(); const { data: accounts, isConnected } = useAccount({ @@ -43,6 +38,8 @@ export const useCreateCosmosWallets = () => { const createCosmosWallets = useCallback( (chainID?: string) => { + const cosmosWallets = [WalletType.KEPLR, WalletType.LEAP, WalletType.COSMOSTATION, WalletType.XDEFI, WalletType.STATION, WalletType.VECTIS]; + const isPenumbra = chainID?.includes("penumbra"); if (isPenumbra) { const praxWallet: MinimalWallet = { @@ -189,22 +186,20 @@ export const useCreateCosmosWallets = () => { await disconnectAsync(); }, isWalletConnected: currentWallet === wallet, + isAvailable: (() => { + try { + const w = getWallet(wallet); + return Boolean(w); + } catch (_error) { + return false; + } + })() }; wallets.push(minimalWallet); } return wallets; }, - [ - accounts, - assets, - chains, - cosmosWallets, - currentWallet, - disconnectAsync, - isConnected, - setCosmosWallet, - setSourceAsset, - ] + [accounts, assets, chains, currentWallet, disconnectAsync, isConnected, setCosmosWallet, setSourceAsset] ); return { createCosmosWallets }; diff --git a/packages/widget-v2/src/hooks/useGetAccount.ts b/packages/widget-v2/src/hooks/useGetAccount.ts index 1c236e51..1f60462f 100644 --- a/packages/widget-v2/src/hooks/useGetAccount.ts +++ b/packages/widget-v2/src/hooks/useGetAccount.ts @@ -65,13 +65,6 @@ export const useGetAccount = () => { const getCosmosAccount = () => { if (!cosmosAccounts || !chainId) return; - if (checkChainType) { - const cosmosHub = cosmosAccounts["cosmoshub-4"]; - if (cosmosHub) { - return cosmosHub; - } - return Object.values(cosmosAccounts).find(key => key?.bech32Address); - } return cosmosAccounts[chainId]; }; const cosmosAccount = getCosmosAccount(); diff --git a/packages/widget-v2/src/pages/ErrorPage/ErrorPageTransactionFailed.tsx b/packages/widget-v2/src/pages/ErrorPage/ErrorPageTransactionFailed.tsx index e109619e..575f5559 100644 --- a/packages/widget-v2/src/pages/ErrorPage/ErrorPageTransactionFailed.tsx +++ b/packages/widget-v2/src/pages/ErrorPage/ErrorPageTransactionFailed.tsx @@ -6,6 +6,7 @@ import { ICONS } from "@/icons"; import { ChainIcon } from "@/icons/ChainIcon"; import { useTheme } from "styled-components"; import { SwapPageHeader } from "../SwapPage/SwapPageHeader"; +import { getTruncatedAddress } from "@/utils/crypto"; export type ErrorPageTransactionFailedProps = { transactionHash: string; @@ -45,7 +46,7 @@ export const ErrorPageTransactionFailed = ({ onClick={() => window.open(explorerLink, "_blank")} color={theme.primary.text.lowContrast} > - Transaction: {transactionHash} + Transaction: {getTruncatedAddress(transactionHash)} diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx index a2c2ecfe..0db4ccf7 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx @@ -23,12 +23,12 @@ import { } from "@/state/swapExecutionPage"; import { useAutoSetAddress } from "@/hooks/useAutoSetAddress"; import { convertSecondsToMinutesOrHours } from "@/utils/number"; -import { useFetchTransactionStatus } from "./useFetchTransactionStatus"; -import { getSignRequiredChainIds } from "@/utils/operations"; import { SignatureIcon } from "@/icons/SignatureIcon"; import pluralize from "pluralize"; +import { useBroadcastedTxsStatus } from "./useBroadcastedTxs"; +import { useFetchTransactionStatus } from "./useFetchTransactionStatus"; -enum SwapExecutionState { +export enum SwapExecutionState { recoveryAddressUnset, destinationAddressUnset, ready, @@ -50,17 +50,22 @@ export const SwapExecutionPage = () => { const setManualAddressModal = useModal(SetAddressModal); const { mutate } = useAtomValue(skipSubmitSwapExecutionAtom); - const operationToTransferEventsMap = useFetchTransactionStatus(); + + const { data: statusData } = useBroadcastedTxsStatus({ + txsRequired: route?.txsRequired, + txs: transactionDetailsArray, + }); + + useFetchTransactionStatus({ + transferEvents: statusData?.transferEvents, + }); const clientOperations = useMemo(() => { if (!route?.operations) return [] as ClientOperation[]; return getClientOperations(route.operations); }, [route?.operations]); - const signRequiredChains = useMemo(() => { - const signRequiredChains = getSignRequiredChainIds(clientOperations); - return signRequiredChains; - }, [clientOperations]); + const lastOperation = clientOperations[clientOperations.length - 1]; const swapExecutionState = useMemo(() => { if (!chainAddresses) return; @@ -130,7 +135,7 @@ export const SwapExecutionPage = () => { const destinationChainID = route?.destAssetChainID; if (!destinationChainID) return; setManualAddressModal.show({ - signRequired: signRequiredChains.includes(destinationChainID), + signRequired: lastOperation.signRequired, chainId: destinationChainID, }); }} @@ -172,17 +177,7 @@ export const SwapExecutionPage = () => { /> ); } - }, [ - connectRequiredChains, - mutate, - route?.destAssetChainID, - route?.estimatedRouteDurationSeconds, - setCurrentPage, - setManualAddressModal, - signRequiredChains, - swapExecutionState, - theme.success.text, - ]); + }, [connectRequiredChains, lastOperation.signRequired, mutate, route?.destAssetChainID, route?.estimatedRouteDurationSeconds, setCurrentPage, setManualAddressModal, swapExecutionState, theme.success.text]); return ( @@ -206,12 +201,14 @@ export const SwapExecutionPage = () => { }) } operations={clientOperations} - operationToTransferEventsMap={operationToTransferEventsMap} + statusData={statusData} + swapExecutionState={swapExecutionState} /> ) : ( )} {renderMainButton} diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx index 8848d30f..1640d9cd 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx @@ -8,14 +8,16 @@ import { SwapExecutionSendIcon } from "@/icons/SwapExecutionSendIcon"; import { SwapExecutionSwapIcon } from "@/icons/SwapExecutionSwapIcon"; import { useState } from "react"; import { SmallText } from "@/components/Typography"; -import { ClientOperation, ClientTransferEvent, OperationType } from "@/utils/clientType"; +import { ClientOperation, OperationType } from "@/utils/clientType"; import { skipBridgesAtom, skipSwapVenuesAtom } from "@/state/skipClient"; import { useAtom } from "jotai"; -import { getIsOperationSignRequired } from "@/utils/operations"; +import { TxsStatus } from "./useBroadcastedTxs"; +import { SwapExecutionState } from "./SwapExecutionPage"; export type SwapExecutionPageRouteDetailedProps = { operations: ClientOperation[]; - operationToTransferEventsMap: Record; + statusData?: TxsStatus + swapExecutionState?: SwapExecutionState; }; type operationTypeToIcon = Record; @@ -49,7 +51,8 @@ type tooltipMap = Record; export const SwapExecutionPageRouteDetailed = ({ operations, - operationToTransferEventsMap, + statusData, + swapExecutionState }: SwapExecutionPageRouteDetailedProps) => { const [{ data: swapVenues }] = useAtom(skipSwapVenuesAtom); const [{ data: bridges }] = useAtom(skipBridgesAtom); @@ -71,15 +74,16 @@ export const SwapExecutionPageRouteDetailed = ({ }; const firstOperation = operations[0]; - + const status = statusData?.transferEvents; + const firstOpStatus = swapExecutionState === SwapExecutionState.confirmed ? "completed" : status?.[0]?.status; return ( @@ -135,9 +140,9 @@ export const SwapExecutionPageRouteDetailed = ({ {...asset} index={index} context={index === operations.length - 1 ? "destination" : "intermediary"} - isSignRequired={isSignRequired} - status={operationToTransferEventsMap[index]?.status} - explorerLink={operationToTransferEventsMap[index]?.explorerLink} + isSignRequired={nextOperation?.signRequired} + status={opStatus} + explorerLink={explorerLink} key={`row-${operation.fromChain}-${operation.toChainID}-${index}`} /> diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx index 4aaffdf9..8572d875 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx @@ -115,27 +115,32 @@ export const SwapExecutionPageRouteDetailedRow = ({ justify="space-between" > - - - {assetDetails?.amount} - - {assetDetails?.symbol} - - {" "} - on {assetDetails?.chainName} - - {explorerLink && ( - - - - - + + + + {assetDetails?.amount} + + {assetDetails?.symbol} + + {" "} + on {assetDetails?.chainName} + + {explorerLink && ( + + + + + + )} + {" "} + {isSignRequired && ( + + Signature required + )} - + {source.address && ( - copyToClipboard(source.address)} - > + copyToClipboard(source.address)}> {source.image && ( )} - + {getTruncatedAddress(source.address)} - + )} - {isSignRequired && ( - Signature required - )} ); }; +const AddressText = styled(SmallText)` + font-family: monospace; + text-transform: lowercase; +`; + const StyledButton = styled(Button)` padding: 5px 8px; height: 28px; @@ -226,7 +233,6 @@ const StyledLoadingContainer = styled(Row) <{ return `border: ${borderSize}px solid ${theme.success.text}`; case "failed": return `border: ${borderSize}px solid ${theme.error.text}`; - case "broadcasted": default: return ""; } diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimple.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimple.tsx index 08c41dbf..2c515e1a 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimple.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimple.tsx @@ -4,29 +4,33 @@ import { useAtomValue } from "jotai"; import { SwapExecutionPageRouteSimpleRow } from "./SwapExecutionPageRouteSimpleRow"; import { BridgeArrowIcon } from "@/icons/BridgeArrowIcon"; import { ICONS } from "@/icons"; -import { ClientOperation, ClientTransferEvent } from "@/utils/clientType"; +import { ClientOperation } from "@/utils/clientType"; import { swapExecutionStateAtom } from "@/state/swapExecutionPage"; import { useMemo } from "react"; -import { getIsOperationSignRequired } from "@/utils/operations"; +import { TxsStatus } from "./useBroadcastedTxs"; +import { SwapExecutionState } from "./SwapExecutionPage"; export type SwapExecutionPageRouteSimpleProps = { operations: ClientOperation[]; - operationToTransferEventsMap: Record; onClickEditDestinationWallet?: () => void; + statusData?: TxsStatus + swapExecutionState?: SwapExecutionState; }; export const SwapExecutionPageRouteSimple = ({ operations, - operationToTransferEventsMap, + statusData, onClickEditDestinationWallet: _onClickEditDestinationWallet, + swapExecutionState }: SwapExecutionPageRouteSimpleProps) => { const theme = useTheme(); - const { route, overallStatus } = useAtomValue(swapExecutionStateAtom); + const { route } = useAtomValue(swapExecutionStateAtom); const firstOperation = operations[0]; const lastOperation = operations[operations.length - 1]; - const sourceStatus = operationToTransferEventsMap?.[0]?.status; - const destinationStatus = operationToTransferEventsMap?.[operations.length - 1]?.status; + const status = statusData?.transferEvents; + const sourceStatus = swapExecutionState === SwapExecutionState.confirmed ? "completed" : status?.[firstOperation.transferIndex]?.status; + const destinationStatus = swapExecutionState === SwapExecutionState.confirmed ? "completed" : status?.[lastOperation.transferIndex]?.status; const sourceDenom = firstOperation.denomIn; const destinationDenom = lastOperation.denomOut; @@ -44,17 +48,17 @@ export const SwapExecutionPageRouteSimple = ({ usdValue: route?.usdAmountOut, }; - const operationIndexBeforeLastOperation = operations.length - 2; - - const isSignRequired = getIsOperationSignRequired(operationIndexBeforeLastOperation, operations, firstOperation, lastOperation); - - const explorerLink = operationToTransferEventsMap?.[operations.length - 1]?.toExplorerLink; + const isSignRequired = lastOperation.signRequired; + const sourceExplorerLink = status?.[firstOperation.transferIndex]?.fromExplorerLink; + const destinationExplorerLink = status?.[lastOperation.transferIndex]?.toExplorerLink; const onClickEditDestinationWallet = useMemo(() => { if (isSignRequired) return; - if (overallStatus) return; + if (swapExecutionState !== SwapExecutionState.ready) return; return _onClickEditDestinationWallet; - }, [isSignRequired, overallStatus, _onClickEditDestinationWallet]); + }, [isSignRequired, swapExecutionState, _onClickEditDestinationWallet]); + + return ( @@ -62,15 +66,15 @@ export const SwapExecutionPageRouteSimple = ({ {...source} status={sourceStatus} context="source" + explorerLink={sourceExplorerLink} /> diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx index 26501942..b4369776 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx @@ -21,7 +21,6 @@ export type SwapExecutionPageRouteSimpleRowProps = { tokenAmount: ClientOperation["amountIn"] | ClientOperation["amountOut"]; usdValue?: string; chainId: ClientOperation["fromChainID"] | ClientOperation["chainID"]; - destination?: boolean; onClickEditDestinationWallet?: () => void; explorerLink?: ChainTransaction["explorerLink"]; status?: SimpleStatus; @@ -35,7 +34,6 @@ export const SwapExecutionPageRouteSimpleRow = ({ usdValue, chainId, status, - destination, onClickEditDestinationWallet, explorerLink, context, @@ -49,13 +47,6 @@ export const SwapExecutionPageRouteSimpleRow = ({ tokenAmount, }); - const txStateOfAnimatedBorder = useMemo(() => { - if (destination && status === "broadcasted") { - return; - } - return status; - }, [status, destination]); - const Icon = iconMap[icon]; const chainAddresses = useAtomValue(chainAddressesAtom); @@ -90,11 +81,12 @@ export const SwapExecutionPageRouteSimpleRow = ({ width={50} height={50} backgroundColor={theme.success.text} - status={txStateOfAnimatedBorder} + status={status} > diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts b/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts new file mode 100644 index 00000000..99d8279c --- /dev/null +++ b/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts @@ -0,0 +1,75 @@ +import { skipClient as skipClientAtom } from "@/state/skipClient"; +import { ClientTransferEvent, getTransferEventsFromTxStatusResponse } from "@/utils/clientType"; +import { useQuery, UseQueryResult } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { useState, useMemo } from "react"; + + +export type TxsStatus = { + isSuccess: boolean; + isSettled: boolean; + transferEvents: ClientTransferEvent[]; +}; + +export const useBroadcastedTxsStatus = ({ + txs, + txsRequired, + enabled, +}: { + txsRequired?: number; + txs: { chainID: string; txHash: string }[] | undefined; + enabled?: boolean; +}): UseQueryResult => { + const skipClient = useAtomValue(skipClientAtom); + const [isSettled, setIsSettled] = useState(false); + const [prevData, setPrevData] = useState(undefined); + const queryKey = useMemo( + () => ["txs-status", txsRequired, txs] as const, + [txs, txsRequired] + ); + return useQuery({ + queryKey, + queryFn: async ({ queryKey: [, txsRequired, txs] }) => { + if (!txs) return; + + const results = await Promise.all( + txs.map(async (tx) => { + const _res = await skipClient.transactionStatus({ + chainID: tx.chainID, + txHash: tx.txHash, + }); + return _res; + })); + const transferEvents = getTransferEventsFromTxStatusResponse(results); + const _isSettled = results.every((tx) => { + return ( + tx.state === "STATE_COMPLETED_SUCCESS" || + tx.state === "STATE_COMPLETED_ERROR" || + tx.state === "STATE_ABANDONED" + ); + }); + + const _isSuccess = transferEvents.every((tx) => { + return tx.status === "completed"; + }); + + if (transferEvents.length > 0 && txsRequired === results.length && _isSettled) { + setIsSettled(true); + } + + const resData: TxsStatus = { + isSuccess: _isSuccess, + isSettled: _isSettled, + transferEvents, + }; + setPrevData(resData); + return resData; + }, + enabled: + !isSettled && + (!!txs && txs.length > 0 && enabled !== undefined ? enabled : true), + refetchInterval: 500, + // to make the data persist when query key changed + initialData: prevData, + }); +}; diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx index ddfde499..efa5a507 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx @@ -1,10 +1,24 @@ import { setTransactionHistoryAtom } from "@/state/history"; -import { setOverallStatusAtom, swapExecutionStateAtom, skipTransactionStatusAtom, skipSubmitSwapExecutionAtom } from "@/state/swapExecutionPage"; -import { ClientTransferEvent, getTransferEventsFromTxStatusResponse, getOperationToTransferEventsMap, getClientOperations, ClientOperation } from "@/utils/clientType"; +import { + setOverallStatusAtom, + swapExecutionStateAtom, + skipTransactionStatusAtom, + skipSubmitSwapExecutionAtom, +} from "@/state/swapExecutionPage"; +import { + ClientTransferEvent, + getClientOperations, + ClientOperation, + getSimpleOverallStatus, +} from "@/utils/clientType"; import { useSetAtom, useAtomValue, useAtom } from "jotai"; -import { useState, useMemo, useEffect } from "react"; +import { useMemo, useEffect } from "react"; -export const useFetchTransactionStatus = () => { +export const useFetchTransactionStatus = ({ + transferEvents +}: { + transferEvents?: ClientTransferEvent[]; +}) => { const setOverallStatus = useSetAtom(setOverallStatusAtom); const { route, @@ -16,8 +30,6 @@ export const useFetchTransactionStatus = () => { const setTransactionHistory = useSetAtom(setTransactionHistoryAtom); const { isPending } = useAtomValue(skipSubmitSwapExecutionAtom); - const [operationToTransferEventsMap, setOperationToTransferEventsMap] = - useState>({}); const clientOperations = useMemo(() => { if (!route?.operations) return [] as ClientOperation[]; @@ -25,69 +37,48 @@ export const useFetchTransactionStatus = () => { }, [route?.operations]); const computedSwapStatus = useMemo(() => { - if (!route?.operations) return; - const operationTransferEventsArray = Object.values( - operationToTransferEventsMap - ); - - const lastOperationIndex = route?.operations?.length - 1; + if (!route?.operations || !route?.txsRequired) return; - if (operationTransferEventsArray.length === 0) { + if (transferEvents?.length === 0) { if (isPending) { setOverallStatus("signing"); } return; } - if ( - operationToTransferEventsMap[lastOperationIndex]?.status === "completed" - ) { + if (!transactionStatus) return; + const lastTransactionIndex = route?.txsRequired - 1; + + const lastTransactionStatus = getSimpleOverallStatus( + transactionStatus?.[lastTransactionIndex]?.state + ); + + if (lastTransactionStatus === "completed") { return "completed"; } if ( - operationTransferEventsArray.find(({ status }) => status === "failed") + transferEvents?.find(({ status }) => status === "failed") ) { return "failed"; } if ( - operationTransferEventsArray.find(({ status }) => status === "pending") + transferEvents?.find(({ status }) => status === "pending") ) { return "pending"; } if ( - operationTransferEventsArray.every( - (state) => state.status === "broadcasted" + transferEvents?.every( + ({ status }) => status === "unconfirmed" ) ) { - return "broadcasted"; + return "unconfirmed"; } - }, [isPending, operationToTransferEventsMap, route?.operations, setOverallStatus]); + }, [isPending, route?.operations, route?.txsRequired, setOverallStatus, transactionStatus, transferEvents]); useEffect(() => { if (overallStatus === "completed" || overallStatus === "failed") return; - const transferEvents = - getTransferEventsFromTxStatusResponse(transactionStatus); - const operationToTransferEventsMap = getOperationToTransferEventsMap( - transactionStatus ?? [], - clientOperations - ); - const operationTransferEventsArray = Object.values( - operationToTransferEventsMap - ); - - if (transactionDetailsArray.length === 1 && transferEvents.length === 0) { - setOperationToTransferEventsMap({ - 0: { - status: "pending", - } as ClientTransferEvent, - }); - } - if (operationTransferEventsArray.length > 0) { - setOperationToTransferEventsMap(operationToTransferEventsMap); - } - if (computedSwapStatus) { setTransactionHistory(transactionHistoryIndex, { status: computedSwapStatus, @@ -105,5 +96,4 @@ export const useFetchTransactionStatus = () => { transactionHistoryIndex, ]); - return operationToTransferEventsMap; -}; \ No newline at end of file +}; diff --git a/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx b/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx index cfeb9341..a0d66e58 100644 --- a/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx +++ b/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx @@ -87,7 +87,12 @@ export const SwapPage = () => { tokenAndChainSelectorModal.hide(); }, }); - }, [setDestinationAssetAmount, setSourceAsset, setSourceAssetAmount, tokenAndChainSelectorModal]); + }, [ + setDestinationAssetAmount, + setSourceAsset, + setSourceAssetAmount, + tokenAndChainSelectorModal, + ]); const handleChangeSourceChain = useCallback(() => { tokenAndChainSelectorModal.show({ @@ -101,7 +106,13 @@ export const SwapPage = () => { selectedAsset: getClientAsset(sourceAsset?.denom, sourceAsset?.chainID), networkSelection: true, }); - }, [getClientAsset, setSourceAsset, sourceAsset?.chainID, sourceAsset?.denom, tokenAndChainSelectorModal]); + }, [ + getClientAsset, + setSourceAsset, + sourceAsset?.chainID, + sourceAsset?.denom, + tokenAndChainSelectorModal, + ]); const handleChangeDestinationAsset = useCallback(() => { tokenAndChainSelectorModal.show({ @@ -191,8 +202,6 @@ export const SwapPage = () => { return ; } - - if (isRouteError) { return ( @@ -234,7 +243,25 @@ export const SwapPage = () => { }} /> ); - }, [sourceAsset?.chainID, sourceAsset?.amount, destinationAsset?.chainID, destinationAsset?.amount, isWaitingForNewRoute, sourceAccount?.address, isRouteError, isLoadingBalances, insufficientBalance, route, connectedWalletModal, selectWalletmodal, routeError?.message, setChainAddresses, setCurrentPage, setSwapExecutionState, setError]); + }, [ + sourceAsset?.chainID, + sourceAsset?.amount, + destinationAsset?.chainID, + destinationAsset?.amount, + isWaitingForNewRoute, + sourceAccount?.address, + isRouteError, + isLoadingBalances, + insufficientBalance, + route, + connectedWalletModal, + selectWalletmodal, + routeError?.message, + setChainAddresses, + setCurrentPage, + setSwapExecutionState, + setError, + ]); const priceChangePercentage = useMemo(() => { if (!route?.usdAmountIn || !route?.usdAmountOut || isWaitingForNewRoute) { diff --git a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx index 3fdf354b..c3ed2e21 100644 --- a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx +++ b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx @@ -66,7 +66,7 @@ export const TransactionHistoryPageHistoryItem = ({ const renderStatus = useMemo(() => { switch (status) { - case "broadcasted": + case "unconfirmed": case "pending": return ( { +export const setOverallStatusAtom = atom(null, (_get, set, status: SimpleStatus) => { set(swapExecutionStateAtom, (state) => ({ ...state, overallStatus: status })); }); @@ -75,6 +75,7 @@ export const setSwapExecutionStateAtom = atom(null, (get, set) => { transactionDetailsArray: [], route, transactionHistoryIndex, + overallStatus: "unconfirmed", isValidatingGasBalance: undefined, }); @@ -82,6 +83,9 @@ export const setSwapExecutionStateAtom = atom(null, (get, set) => { onTransactionUpdated: (transactionDetails) => { set(setTransactionDetailsArrayAtom, transactionDetails, transactionHistoryIndex); }, + onTransactionSigned: async (transactionDetails) => { + set(setTransactionDetailsArrayAtom, { ...transactionDetails, explorerLink: undefined, status: undefined }, transactionHistoryIndex); + }, onError: (error: unknown, transactionDetailsArray) => { const lastTransaction = transactionDetailsArray?.[transactionDetailsArray?.length - 1]; @@ -90,7 +94,7 @@ export const setSwapExecutionStateAtom = atom(null, (get, set) => { errorType: ErrorType.AuthFailed, onClickBack: () => { set(errorAtom, undefined); - set(setOverallStatusAtom, undefined); + set(setOverallStatusAtom, "unconfirmed"); } }); } else if (lastTransaction?.explorerLink) { @@ -98,7 +102,7 @@ export const setSwapExecutionStateAtom = atom(null, (get, set) => { errorType: ErrorType.TransactionFailed, onClickBack: () => { set(errorAtom, undefined); - set(setOverallStatusAtom, undefined); + set(setOverallStatusAtom, "unconfirmed"); }, explorerLink: lastTransaction?.explorerLink ?? "", transactionHash: lastTransaction?.txHash ?? "", @@ -112,14 +116,11 @@ export const setSwapExecutionStateAtom = atom(null, (get, set) => { error: error as Error, onClickBack: () => { set(errorAtom, undefined); - set(setOverallStatusAtom, undefined); + set(setOverallStatusAtom, "unconfirmed"); }, }); } }, - onTransactionSigned: async (transactionDetails) => { - set(setTransactionDetailsArrayAtom, { ...transactionDetails, explorerLink: undefined, status: undefined }, transactionHistoryIndex); - }, onValidateGasBalance: async (props) => { set(setValidatingGasBalanceAtom, props); }, @@ -139,7 +140,7 @@ export const setTransactionDetailsArrayAtom = atom( const newTransactionDetailsArray = transactionDetailsArray; const transactionIndexFound = newTransactionDetailsArray.findIndex( - (transaction) => transaction.txHash === transactionDetails.txHash + (transaction) => transaction.txHash.toLowerCase() === transactionDetails.txHash.toLowerCase() ); if (transactionIndexFound !== -1) { newTransactionDetailsArray[transactionIndexFound] = { @@ -159,7 +160,7 @@ export const setTransactionDetailsArrayAtom = atom( route, transactionDetails: newTransactionDetailsArray, timestamp: Date.now(), - status: "broadcasted", + status: "unconfirmed", }); } ); @@ -192,12 +193,6 @@ export type TransactionDetails = { status?: TxStatusResponse; }; -export type ClientTransactionStatus = - | "pending" - | "broadcasted" - | "completed" - | "failed"; - type SubmitSwapExecutionCallbacks = { onTransactionUpdated?: (transactionDetails: TransactionDetails) => void; onError: (error: unknown, transactionDetailsArray?: TransactionDetails[]) => void; diff --git a/packages/widget-v2/src/utils/clientType.ts b/packages/widget-v2/src/utils/clientType.ts index 86170f23..6962eb12 100644 --- a/packages/widget-v2/src/utils/clientType.ts +++ b/packages/widget-v2/src/utils/clientType.ts @@ -68,6 +68,9 @@ type OperationDetails = CombineObjectTypes< }; export type ClientOperation = { + transferIndex: number; + signRequired: boolean; + isSwap: boolean; type: OperationType; txIndex: number; amountIn: string; @@ -153,7 +156,35 @@ export function getClientOperation(operation: SkipClientOperation) { } export function getClientOperations(operations: SkipClientOperation[]) { - return operations.map((operation) => getClientOperation(operation)); + let transferIndex = 0; + return operations.map((operation, index, arr) => { + const prevOperation = arr[index - 1]; + + const signRequired = (() => { + if (index === 0) { + return false; + } else { + if (operation.txIndex > prevOperation.txIndex) { + return true; + } + return false; + } + })(); + const clientOperation = getClientOperation(operation); + const isSwap = + clientOperation.type === OperationType.swap || + clientOperation.type === OperationType.evmSwap; + const result = { + ...clientOperation, + transferIndex, + signRequired, + isSwap, + }; + if (!isSwap) { + transferIndex++; + } + return result; + }); } function getClientTransferEvent(transferEvent: TransferEvent) { @@ -203,8 +234,7 @@ function getClientTransferEvent(transferEvent: TransferEvent) { ?.txs.receiveTx?.explorerLink; } }; - - return { + const _result = { ...ibcTransfer, ...axelarTransfer, ...cctpTransfer, @@ -213,69 +243,23 @@ function getClientTransferEvent(transferEvent: TransferEvent) { fromExplorerLink: getExplorerLink("send"), toExplorerLink: getExplorerLink("receive"), } as ClientTransferEvent; + const status = getSimpleStatus(_result.state); + const result = { + ..._result, + status, + }; + return result; } export function getTransferEventsFromTxStatusResponse( txStatusResponse?: TxStatusResponse[] ) { if (!txStatusResponse) return []; - return txStatusResponse?.flatMap((txStatus) => - txStatus.transferSequence.map((transferEvent) => - getClientTransferEvent(transferEvent) - ) - ); -} - -export function getOperationToTransferEventsMap( - txStatusResponse: TxStatusResponse[], - clientOperations: ClientOperation[] -) { - if (!txStatusResponse) return {}; - const operationToTransferEventsMap = {} as Record< - number, - ClientTransferEvent - >; - const transferEvents = - getTransferEventsFromTxStatusResponse(txStatusResponse); - - clientOperations.forEach((operation, index) => { - const foundTransferEventMatchingOperation = transferEvents?.find( - (transferEvent) => - transferEvent.fromChainID === operation.fromChainID && transferEvent.toChainID === operation.toChainID - ); - - const foundSwapTransferEvent = transferEvents?.find( - (transferEvent) => { - const isSwapType = ["evmSwap", "swap"].includes(operation.type); - if (!isSwapType) return false; - - const operationStaysOnTheSameChain = operation.fromChainID === operation.toChainID; - const fromChainMatches = transferEvent.fromChainID === operation.fromChainID; - const toChainMatches = transferEvent.toChainID === operation.toChainID; - - return operationStaysOnTheSameChain - ? fromChainMatches || toChainMatches - : fromChainMatches && toChainMatches; - } - ); - - if (foundTransferEventMatchingOperation) { - foundTransferEventMatchingOperation.status = getSimpleStatus( - foundTransferEventMatchingOperation?.state - ); - operationToTransferEventsMap[index] = foundTransferEventMatchingOperation; - operationToTransferEventsMap[index].explorerLink = foundTransferEventMatchingOperation.toExplorerLink; - - } else if (foundSwapTransferEvent) { - foundSwapTransferEvent.status = getSimpleStatus( - foundSwapTransferEvent?.state - ); - operationToTransferEventsMap[index] = { ...foundSwapTransferEvent }; - operationToTransferEventsMap[index].explorerLink = foundSwapTransferEvent.fromExplorerLink; - } + return txStatusResponse?.flatMap((txStatus) => { + return txStatus.transferSequence.map((transferEvent) => { + return getClientTransferEvent(transferEvent); + }); }); - - return operationToTransferEventsMap; } export function getSimpleOverallStatus(state: StatusState) { @@ -299,9 +283,10 @@ export function getSimpleStatus( | CCTPTransferState | HyperlaneTransferState | OPInitTransferState -) { +): SimpleStatus { switch (state) { case "TRANSFER_PENDING": + case "TRANSFER_RECEIVED": case "AXELAR_TRANSFER_PENDING_CONFIRMATION": case "AXELAR_TRANSFER_PENDING_RECEIPT": case "CCTP_TRANSFER_SENT": @@ -338,11 +323,11 @@ export enum TransferType { } export type SimpleStatus = + | "unconfirmed" + | "signing" | "pending" - | "broadcasted" | "completed" - | "failed" - | "signing"; + | "failed"; export type ClientTransferEvent = { fromChainID: string; diff --git a/packages/widget-v2/src/utils/operations.ts b/packages/widget-v2/src/utils/operations.ts deleted file mode 100644 index e550fa1a..00000000 --- a/packages/widget-v2/src/utils/operations.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ClientOperation } from "./clientType"; - -export const getSignRequiredChainIds = (operations: ClientOperation[]) => { - const signRequiredChains: string[] = []; - operations.forEach((op, i, arr) => { - const nextOperation = arr[i + 1]; - const isSignRequired = getIsOperationSignRequired(i, arr, nextOperation, op); - if (isSignRequired && op.chainID) { - signRequiredChains.push(op.chainID); - } - }); - return signRequiredChains; -}; - -export const getIsOperationSignRequired = (operationIndex: number, operations: ClientOperation[], nextOperation: ClientOperation, currentOperation: ClientOperation) => { - return ((operationIndex + 1) !== operations.length) && (nextOperation?.txIndex !== currentOperation.txIndex); -}; From 2424bac435a8e90fdd4e719c4e997979e33959d5 Mon Sep 17 00:00:00 2001 From: plubber <51789398+ericHgorski@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:01:04 -0400 Subject: [PATCH 03/10] Refactor client types and options (#334) --- packages/client/src/client-types.ts | 146 ++++++++---------- packages/client/src/client.ts | 10 +- packages/client/src/types/converters.ts | 3 +- packages/client/src/types/lifecycle.ts | 54 +++++-- packages/client/src/types/routing.ts | 4 +- packages/client/src/types/shared.ts | 1 - packages/client/src/types/unified.ts | 2 + .../widget-v2/src/hooks/useWalletList.tsx | 9 +- .../SetAddressModal/SetAddressModal.tsx | 2 +- .../SetAddressModal/isValidWalletAddress.ts | 5 +- packages/widget-v2/src/state/skipClient.ts | 2 +- .../widget-v2/src/state/swapExecutionPage.ts | 8 +- packages/widget/src/hooks/use-chains.ts | 6 +- .../widget/src/hooks/use-make-wallets.tsx | 3 +- .../src/ui/WalletModal/WalletListItem.tsx | 3 +- 15 files changed, 129 insertions(+), 129 deletions(-) diff --git a/packages/client/src/client-types.ts b/packages/client/src/client-types.ts index 739ebe34..8f3f3df3 100644 --- a/packages/client/src/client-types.ts +++ b/packages/client/src/client-types.ts @@ -15,7 +15,9 @@ import { WalletClient } from 'viem'; import * as types from './types'; import { Adapter } from '@solana/wallet-adapter-base'; +import { TransactionCallbacks } from './types'; +/** Common Types */ export interface UserAddress { chainID: string; address: string; @@ -26,12 +28,40 @@ export type EndpointOptions = { rest?: string; }; -export interface SkipClientOptions { - apiURL?: string; - apiKey?: string; +/** Signer Getters */ +export interface SignerGetters { getEVMSigner?: (chainID: string) => Promise; getCosmosSigner?: (chainID: string) => Promise; getSVMSigner?: () => Promise; +} + +/** Gas Options */ +export type GetFallbackGasAmount = ( + chainID: string, + chainType: types.ChainType +) => Promise; + +export type GetGasPrice = ( + chainID: string, + chainType: types.ChainType +) => Promise; + +interface GasOptions { + /** + * If `getGasPrice` is undefined, or returns undefined, the router will attempt to set the recommended gas price + **/ + getGasPrice?: GetGasPrice; + /** + * If `getFallbackGasAmount` is set, when router fails to simulate the gas amount, it will use the fallback gas amount + */ + getFallbackGasAmount?: GetFallbackGasAmount; + gasAmountMultiplier?: number; +} + +/** Skip Client Options */ +export interface SkipClientOptions extends SignerGetters { + apiURL?: string; + apiKey?: string; endpointOptions?: { endpoints?: Record; getRpcEndpointForChain?: (chainID: string) => Promise; @@ -42,55 +72,24 @@ export interface SkipClientOptions { chainIDsToAffiliates?: Record; } -export type ExecuteRouteOptions = { - route: types.RouteResponse; - /** - * addresses should be in the same order with the `chainIDs` in the `route` +/** Execute Route Options */ +export type ExecuteRouteOptions = SignerGetters & + GasOptions & + types.TransactionCallbacks & { + route: types.RouteResponse; + /** + * Addresses should be in the same order with the `chainIDs` in the `route` + */ + userAddresses: UserAddress[]; + validateGasBalance?: boolean; + slippageTolerancePercent?: string; + /** + * Arbitrary Tx to be executed before or after route msgs */ - userAddresses: UserAddress[]; - getEVMSigner?: (chainID: string) => Promise; - getCosmosSigner?: (chainID: string) => Promise; - getSVMSigner?: () => Promise; - onTransactionSigned?: (txInfo: { - txHash: string; - chainID: string; - }) => Promise; - onTransactionBroadcast?: (txInfo: { - txHash: string; - chainID: string; - }) => Promise; - onTransactionTracked?: (txInfo: { - txHash: string; - chainID: string; - explorerLink: string; - }) => Promise; - onTransactionCompleted?: ( - chainID: string, - txHash: string, - status: types.TxStatusResponse - ) => Promise; - onValidateGasBalance?: (value: { - chainID?: string; - txIndex?: number; - status: "success" | "error" | "pending" | "completed" - }) => Promise; - validateGasBalance?: boolean; - slippageTolerancePercent?: string; - /** - * If `getGasPrice` is undefined, or returns undefined, the router will attempt to set the recommended gas price - **/ - getGasPrice?: (chainID: string) => Promise; - /** - * If `getFallbackGasAmount` is set, when router fails to simulate the gas amount, it will use the fallback gas amount - */ - getFallbackGasAmount?: GetFallbackGasAmount; - gasAmountMultiplier?: number; - /** - * Arbitrary Tx to be executed before or after route msgs - */ - beforeMsg?: types.CosmosMsg; - afterMsg?: types.CosmosMsg; -}; + beforeMsg?: types.CosmosMsg; + afterMsg?: types.CosmosMsg; + }; + export type ExecuteCosmosMessageOptions = { signerAddress: string; @@ -99,48 +98,29 @@ export type ExecuteCosmosMessageOptions = { fee: StdFee; }; -export type ExecuteCosmosMessage = { +export type ExecuteCosmosMessage = GasOptions & { signerAddress: string; - getCosmosSigner?: (chainID: string) => Promise; - /** - * If `getGasPrice` is undefined, or returns undefined, the router will attempt to set the recommended gas price - **/ - getGasPrice?: GetGasPrice; - /** - * If `getFallbackGasAmount` is set, when router fails to simulate the gas amount, it will use the fallback gas amount - */ - getFallbackGasAmount?: GetFallbackGasAmount; + getCosmosSigner?: SignerGetters['getCosmosSigner']; chainID: string; messages: types.CosmosMsg[]; - gasAmountMultiplier?: number; gasTokenUsed?: Coin; - onTransactionSigned?: ExecuteRouteOptions['onTransactionSigned']; + onTransactionSigned?: TransactionCallbacks['onTransactionSigned']; }; -export type SignCosmosMessageDirectOptions = { +interface SignCosmosMessageOptionsBase { signerAddress: string; - signer: OfflineDirectSigner; chainID: string; cosmosMsgs: types.CosmosMsg[]; fee: StdFee; signerData: SignerData; -}; - -export type SignCosmosMessageAminoOptions = { - signerAddress: string; - signer: OfflineAminoSigner; - chainID: string; - cosmosMsgs: types.CosmosMsg[]; - fee: StdFee; - signerData: SignerData; -}; +} -export type GetFallbackGasAmount = ( - chainID: string, - chainType: 'cosmos' | 'evm' | 'svm' -) => Promise; +export type SignCosmosMessageDirectOptions = + SignCosmosMessageOptionsBase & { + signer: OfflineDirectSigner; + }; -export type GetGasPrice = ( - chainID: string, - chainType: 'cosmos' | 'evm' | 'svm' -) => Promise; +export type SignCosmosMessageAminoOptions = + SignCosmosMessageOptionsBase & { + signer: OfflineAminoSigner; + }; \ No newline at end of file diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index cb4c54b2..346b64d3 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -84,10 +84,10 @@ export class SkipClient { getRestEndpointForChain?: (chainID: string) => Promise; }; - protected getCosmosSigner?: (chainID: string) => Promise; - protected getEVMSigner?: (chainID: string) => Promise; - protected getSVMSigner?: () => Promise; - protected chainIDsToAffiliates?: Record; + protected getCosmosSigner?: clientTypes.SignerGetters['getCosmosSigner']; + protected getEVMSigner?: clientTypes.SignerGetters['getEVMSigner']; + protected getSVMSigner?: clientTypes.SignerGetters['getSVMSigner']; + protected chainIDsToAffiliates?: clientTypes.SkipClientOptions['chainIDsToAffiliates']; protected cumulativeAffiliateFeeBPS?: string = '0'; constructor(options: clientTypes.SkipClientOptions = {}) { @@ -774,7 +774,7 @@ export class SkipClient { }: { signer: Adapter; message: types.SvmTx; - onTransactionSigned?: clientTypes.ExecuteRouteOptions['onTransactionSigned']; + onTransactionSigned?: types.TransactionCallbacks['onTransactionSigned']; }) { const _tx = Buffer.from(message.tx, 'base64'); const transaction = Transaction.from(_tx); diff --git a/packages/client/src/types/converters.ts b/packages/client/src/types/converters.ts index 33ae8ed2..8ce18672 100644 --- a/packages/client/src/types/converters.ts +++ b/packages/client/src/types/converters.ts @@ -151,6 +151,7 @@ import { BridgeJSON, BridgesResponse, BridgesResponseJSON, + ChainType, EstimatedFee, EstimatedFeeJSON, Msg, @@ -314,7 +315,7 @@ export function chainFromJSON(chainJSON: ChainJSON): Chain { logoURI: chainJSON.logo_uri, bech32Prefix: chainJSON.bech32_prefix, feeAssets: chainJSON.fee_assets.map(feeAssetFromJSON), - chainType: chainJSON.chain_type, + chainType: chainJSON.chain_type as ChainType, ibcCapabilities: ibcCapabilitiesFromJSON(chainJSON.ibc_capabilities), isTestnet: chainJSON.is_testnet, prettyName: chainJSON.pretty_name, diff --git a/packages/client/src/types/lifecycle.ts b/packages/client/src/types/lifecycle.ts index ef444ef8..fb9eb93f 100644 --- a/packages/client/src/types/lifecycle.ts +++ b/packages/client/src/types/lifecycle.ts @@ -267,19 +267,19 @@ export type AxelarTransferInfo = { export type AxelarTransferTransactionsJSON = | { - contract_call_with_token_txs: ContractCallWithTokenTransactionsJSON; - } + contract_call_with_token_txs: ContractCallWithTokenTransactionsJSON; + } | { - send_token_txs: SendTokenTransactionsJSON; - }; + send_token_txs: SendTokenTransactionsJSON; + }; export type AxelarTransferTransactions = | { - contractCallWithTokenTxs: ContractCallWithTokenTransactions; - } + contractCallWithTokenTxs: ContractCallWithTokenTransactions; + } | { - sendTokenTxs: SendTokenTransactions; - }; + sendTokenTxs: SendTokenTransactions; + }; export type ContractCallWithTokenTransactionsJSON = { send_tx: ChainTransactionJSON | null; @@ -428,20 +428,46 @@ export type OPInitTransferInfo = { export type TransferEventJSON = | { - ibc_transfer: TransferInfoJSON; - } + ibc_transfer: TransferInfoJSON; + } | { - axelar_transfer: AxelarTransferInfoJSON; - } + axelar_transfer: AxelarTransferInfoJSON; + } | { cctp_transfer: CCTPTransferInfoJSON } | { hyperlane_transfer: HyperlaneTransferInfoJSON } | { op_init_transfer: OPInitTransferInfoJSON }; export type TransferEvent = | { - ibcTransfer: TransferInfo; - } + ibcTransfer: TransferInfo; + } | { axelarTransfer: AxelarTransferInfo } | { cctpTransfer: CCTPTransferInfo } | { hyperlaneTransfer: HyperlaneTransferInfo } | { opInitTransfer: OPInitTransferInfo }; + +export interface TransactionCallbacks { + onTransactionSigned?: (txInfo: { + txHash: string; + chainID: string; + }) => Promise; + onTransactionBroadcast?: (txInfo: { + txHash: string; + chainID: string; + }) => Promise; + onTransactionTracked?: (txInfo: { + txHash: string; + chainID: string; + explorerLink: string; + }) => Promise; + onTransactionCompleted?: ( + chainID: string, + txHash: string, + status: TxStatusResponse + ) => Promise; + onValidateGasBalance?: (value: { + chainID?: string; + txIndex?: number; + status: 'success' | 'error' | 'pending' | 'completed'; + }) => Promise; +} \ No newline at end of file diff --git a/packages/client/src/types/routing.ts b/packages/client/src/types/routing.ts index fbf7097d..2600985d 100644 --- a/packages/client/src/types/routing.ts +++ b/packages/client/src/types/routing.ts @@ -1,3 +1,5 @@ +import { ChainType } from "./unified"; + export type ModuleSupport = { authz: boolean; feegrant: boolean; @@ -44,7 +46,7 @@ export type Chain = { logoURI?: string; bech32Prefix: string; feeAssets: FeeAsset[]; - chainType: string; + chainType: ChainType; ibcCapabilities: IbcCapabilities; isTestnet: boolean; prettyName: string; diff --git a/packages/client/src/types/shared.ts b/packages/client/src/types/shared.ts index 42730568..36ab27be 100644 --- a/packages/client/src/types/shared.ts +++ b/packages/client/src/types/shared.ts @@ -9,7 +9,6 @@ export type IBCAddress = { address: string; chainID: string; }; - export type AssetJSON = { denom: string; chain_id: string; diff --git a/packages/client/src/types/unified.ts b/packages/client/src/types/unified.ts index 5a368569..a5483d37 100644 --- a/packages/client/src/types/unified.ts +++ b/packages/client/src/types/unified.ts @@ -550,6 +550,8 @@ export type MsgsResponse = { export type BridgeType = 'IBC' | 'AXELAR' | 'CCTP' | 'HYPERLANE' | 'OPINIT'; +export type ChainType = 'cosmos' | 'evm' | 'svm'; + export type AssetBetweenChainsJSON = { asset_on_source: AssetJSON; asset_on_dest: AssetJSON; diff --git a/packages/widget-v2/src/hooks/useWalletList.tsx b/packages/widget-v2/src/hooks/useWalletList.tsx index 16152301..7d43b7ee 100644 --- a/packages/widget-v2/src/hooks/useWalletList.tsx +++ b/packages/widget-v2/src/hooks/useWalletList.tsx @@ -13,14 +13,7 @@ export const useWalletList = ({ chainID, destinationWalletList, chainType: _chai const { data: chains } = useAtomValue(skipChainsAtom); const chainType = chainID ? chains?.find(c => c.chainID === chainID)?.chainType : _chainType; - - let walletType = chainType; - const isSei = chainID === "pacific-1"; - if (destinationWalletList) { - if (isSei) { - walletType = "sei"; - } - } + const walletType = destinationWalletList && chainID === "pacific-1" ? "sei" : chainType; const walletList = useMemo(() => { switch (walletType) { diff --git a/packages/widget-v2/src/modals/SetAddressModal/SetAddressModal.tsx b/packages/widget-v2/src/modals/SetAddressModal/SetAddressModal.tsx index a3237d5c..a5168ecf 100644 --- a/packages/widget-v2/src/modals/SetAddressModal/SetAddressModal.tsx +++ b/packages/widget-v2/src/modals/SetAddressModal/SetAddressModal.tsx @@ -62,7 +62,7 @@ export const SetAddressModal = createModal((modalProps: SetAddressModalProps) => return isValidWalletAddress({ address: manualWalletAddress, bech32Prefix, - chainType, + chainType }); }, [chain, manualWalletAddress]); diff --git a/packages/widget-v2/src/modals/SetAddressModal/isValidWalletAddress.ts b/packages/widget-v2/src/modals/SetAddressModal/isValidWalletAddress.ts index 97bb0ea8..229417bd 100644 --- a/packages/widget-v2/src/modals/SetAddressModal/isValidWalletAddress.ts +++ b/packages/widget-v2/src/modals/SetAddressModal/isValidWalletAddress.ts @@ -1,12 +1,11 @@ import { fromBech32 } from "@cosmjs/encoding"; import { isAddress } from "viem"; import { PublicKey } from "@solana/web3.js"; - -type chainType = "cosmos" | "evm" | "svm" | string; +import { ChainType } from "@skip-go/client"; type isValidWalletAddressProps = { address: string; - chainType: chainType; + chainType: ChainType | string; bech32Prefix: string; } diff --git a/packages/widget-v2/src/state/skipClient.ts b/packages/widget-v2/src/state/skipClient.ts index 8c30f9c9..7d854a3a 100644 --- a/packages/widget-v2/src/state/skipClient.ts +++ b/packages/widget-v2/src/state/skipClient.ts @@ -108,7 +108,7 @@ export const skipChainsAtom = atomWithQuery((get) => { const skip = get(skipClient); return { queryKey: ["skipChains"], - queryFn: async () => { + queryFn: async (): Promise => { return skip.chains({ includeEVM: true, includeSVM: true, diff --git a/packages/widget-v2/src/state/swapExecutionPage.ts b/packages/widget-v2/src/state/swapExecutionPage.ts index 030e400a..61628d3d 100644 --- a/packages/widget-v2/src/state/swapExecutionPage.ts +++ b/packages/widget-v2/src/state/swapExecutionPage.ts @@ -2,7 +2,7 @@ import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query"; import { skipClient } from "@/state/skipClient"; import { skipRouteAtom } from "@/state/route"; import { atom } from "jotai"; -import { ExecuteRouteOptions, RouteResponse, TxStatusResponse, UserAddress } from "@skip-go/client"; +import { TransactionCallbacks, RouteResponse, TxStatusResponse, UserAddress, ChainType } from "@skip-go/client"; import { MinimalWallet } from "./wallets"; import { atomEffect } from "jotai-effect"; import { setTransactionHistoryAtom, transactionHistoryAtom } from "./history"; @@ -28,7 +28,7 @@ type SwapExecutionState = { export type ChainAddress = { chainID: string; - chainType?: "evm" | "cosmos" | "svm"; + chainType?: ChainType; address?: string; } & ( | { source?: "input" | "parent" } @@ -196,8 +196,8 @@ export type TransactionDetails = { type SubmitSwapExecutionCallbacks = { onTransactionUpdated?: (transactionDetails: TransactionDetails) => void; onError: (error: unknown, transactionDetailsArray?: TransactionDetails[]) => void; - onValidateGasBalance?: ExecuteRouteOptions["onValidateGasBalance"]; - onTransactionSigned?: ExecuteRouteOptions["onTransactionSigned"]; + onValidateGasBalance?: TransactionCallbacks["onValidateGasBalance"]; + onTransactionSigned?: TransactionCallbacks["onTransactionSigned"]; }; export const submitSwapExecutionCallbacksAtom = atom< diff --git a/packages/widget/src/hooks/use-chains.ts b/packages/widget/src/hooks/use-chains.ts index b1ab66ed..68b113a6 100644 --- a/packages/widget/src/hooks/use-chains.ts +++ b/packages/widget/src/hooks/use-chains.ts @@ -1,12 +1,8 @@ -import { Chain as SkipChain } from '@skip-go/client'; +import { Chain } from '@skip-go/client'; import { useQuery, UseQueryResult } from '@tanstack/react-query'; import { useSkipClient } from './use-skip-client'; import { useSwapWidgetUIStore } from '../store/swap-widget'; -export type Chain = SkipChain & { - prettyName: string; -}; - export type UseChainsQueryArgs = { enabled?: boolean; select?: (arr?: Chain[]) => T; diff --git a/packages/widget/src/hooks/use-make-wallets.tsx b/packages/widget/src/hooks/use-make-wallets.tsx index 762f761e..814f7956 100644 --- a/packages/widget/src/hooks/use-make-wallets.tsx +++ b/packages/widget/src/hooks/use-make-wallets.tsx @@ -15,11 +15,12 @@ import { createPublicClient, http } from 'viem'; import { sei } from 'viem/chains'; import { seiPrecompileAddrABI } from '../constants/abis'; import { StyledBorderDiv } from '../ui/StyledComponents/Theme'; +import { ChainType } from '@skip-go/client'; export interface MinimalWallet { walletName: string; walletPrettyName: string; - walletChainType: 'evm' | 'cosmos' | 'svm'; + walletChainType: ChainType; walletInfo: { logo?: string | { major: string; minor: string }; }; diff --git a/packages/widget/src/ui/WalletModal/WalletListItem.tsx b/packages/widget/src/ui/WalletModal/WalletListItem.tsx index 3332bf50..72c035b7 100644 --- a/packages/widget/src/ui/WalletModal/WalletListItem.tsx +++ b/packages/widget/src/ui/WalletModal/WalletListItem.tsx @@ -6,6 +6,7 @@ import { ComponentProps, useEffect, useMemo } from 'react'; import { create } from 'zustand'; import { MergedWalletClient } from '../../lib/cosmos-kit'; import { isMobile } from '../../utils/os'; +import { ChainType } from '@skip-go/client'; const useStore = create>(() => ({})); @@ -16,7 +17,7 @@ export function useTotalWallets() { type Props = ComponentProps<'div'> & { chainType: string; walletName: string; - walletChainType: 'evm' | 'cosmos' | 'svm'; + walletChainType: ChainType; }; export const WalletListItem = ({ From 14ca45f6657aec1a4861791165ea2f8949c1c662 Mon Sep 17 00:00:00 2001 From: Todd Kao Date: Fri, 11 Oct 2024 12:08:13 -0400 Subject: [PATCH 04/10] anchor image position to bottom (#355) --- examples/nextjs/pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nextjs/pages/index.tsx b/examples/nextjs/pages/index.tsx index d0029bd0..ba913bca 100644 --- a/examples/nextjs/pages/index.tsx +++ b/examples/nextjs/pages/index.tsx @@ -28,7 +28,7 @@ const Home = () => { backgroundImage: `url('${theme === 'dark' ? darkbg.src : lightbg.src }')`, backgroundSize: 'cover', - backgroundPosition: 'center', + backgroundPosition: 'bottom', }} > diff --git a/packages/widget-v2/src/index.tsx b/packages/widget-v2/src/index.tsx index 161bce52..db494f3c 100644 --- a/packages/widget-v2/src/index.tsx +++ b/packages/widget-v2/src/index.tsx @@ -1,3 +1,3 @@ -export { SwapWidget, ShowSwapWidget } from "./widget/Widget"; -export type { SwapWidgetProps } from "./widget/Widget"; +export { Widget, ShowWidget } from "./widget/Widget"; +export type { WidgetProps } from "./widget/Widget"; export { defaultTheme, lightTheme } from "./widget/theme"; \ No newline at end of file diff --git a/packages/widget-v2/src/widget/Widget.tsx b/packages/widget-v2/src/widget/Widget.tsx index a64f936f..4861fc16 100644 --- a/packages/widget-v2/src/widget/Widget.tsx +++ b/packages/widget-v2/src/widget/Widget.tsx @@ -11,11 +11,11 @@ import { useAtom, useSetAtom } from "jotai"; import { skipClientConfigAtom, themeAtom } from "@/state/skipClient"; import { SkipClientOptions } from "@skip-go/client"; -export type SwapWidgetProps = { +export type WidgetProps = { theme?: PartialTheme; } & SkipClientOptions; -export const SwapWidget = ({ theme, ...skipClientConfig }: SwapWidgetProps) => { +export const Widget = ({ theme, ...skipClientConfig }: WidgetProps) => { const [defaultSkipClientConfig, setSkipClientConfig] = useAtom(skipClientConfigAtom); const setTheme = useSetAtom(themeAtom); useEffect(() => { @@ -37,7 +37,7 @@ export const SwapWidget = ({ theme, ...skipClientConfig }: SwapWidgetProps) => { ); }; -const SwapWidgetWithoutNiceModalProvider = ({ theme, ...skipClientConfig }: SwapWidgetProps) => { +const WidgetWithoutNiceModalProvider = ({ theme, ...skipClientConfig }: SwapWidgetProps) => { const [defaultSkipClientConfig, setSkipClientConfig] = useAtom(skipClientConfigAtom); const setTheme = useSetAtom(themeAtom); useEffect(() => { @@ -59,14 +59,14 @@ const SwapWidgetWithoutNiceModalProvider = ({ theme, ...skipClientConfig }: Swap export type ShowSwapWidget = { button?: ReactElement; -} & SwapWidgetProps; +} & WidgetProps; -export const ShowSwapWidget = ({ +export const ShowWidget = ({ button = , ...props }: ShowSwapWidget) => { const modal = useModal( - createModal(() => ) + createModal(() => ) ); const resetNumberOfModalsOpen = useResetAtom(numberOfModalsOpenAtom); From da8a55a5c88ee8a484337443e9a5f15a10ce98d5 Mon Sep 17 00:00:00 2001 From: Todd Kao Date: Fri, 11 Oct 2024 16:01:28 -0400 Subject: [PATCH 08/10] Remove back button in detailedRoute, fix bankSend rendering of detailedRouteRow (#360) --- .../pages/SwapExecutionPage/SwapExecutionPage.tsx | 4 ++-- .../src/pages/SwapPage/SwapPageHeader.tsx | 14 ++++++++------ packages/widget-v2/src/utils/clientType.ts | 6 ++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx index 0db4ccf7..fa89e1f7 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx @@ -182,11 +182,11 @@ export const SwapExecutionPage = () => { return ( setCurrentPage(Routes.SwapPage), - }} + } : undefined} rightButton={{ label: simpleRoute ? "Details" : "Hide details", icon: simpleRoute ? ICONS.hamburger : ICONS.horizontalLine, diff --git a/packages/widget-v2/src/pages/SwapPage/SwapPageHeader.tsx b/packages/widget-v2/src/pages/SwapPage/SwapPageHeader.tsx index d76f21de..b45628a3 100644 --- a/packages/widget-v2/src/pages/SwapPage/SwapPageHeader.tsx +++ b/packages/widget-v2/src/pages/SwapPage/SwapPageHeader.tsx @@ -24,12 +24,14 @@ export const SwapPageHeader = ({ const RightIcon = iconMap[rightButton?.icon || ICONS.none]; return ( - {leftButton && ( - - - {leftButton.label} - - )} + + {leftButton && ( + + + {leftButton.label} + + )} + {rightContent} diff --git a/packages/widget-v2/src/utils/clientType.ts b/packages/widget-v2/src/utils/clientType.ts index 6962eb12..96365b46 100644 --- a/packages/widget-v2/src/utils/clientType.ts +++ b/packages/widget-v2/src/utils/clientType.ts @@ -126,6 +126,12 @@ function getOperationDetailsAndType(operation: SkipClientOperation) { (operationDetails as Transfer).denomOut = ( operationDetails as BankSend ).denom; + (operationDetails as Transfer).fromChainID = ( + operationDetails as BankSend + ).chainID; + (operationDetails as Transfer).toChainID = ( + operationDetails as BankSend + ).chainID; break; default: } From 95f128abeb1a679fa715a772074e0517af364243 Mon Sep 17 00:00:00 2001 From: Todd Kao Date: Fri, 11 Oct 2024 16:04:36 -0400 Subject: [PATCH 09/10] Speed up animation of modal/drawer (#362) --- packages/widget-v2/src/components/Modal.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/widget-v2/src/components/Modal.tsx b/packages/widget-v2/src/components/Modal.tsx index 0168b71e..0e3c1a56 100644 --- a/packages/widget-v2/src/components/Modal.tsx +++ b/packages/widget-v2/src/components/Modal.tsx @@ -47,7 +47,7 @@ export const Modal = ({ open={modal.visible} onOpenChange={() => { setOpen(false); - delay(150).then(() => modal.remove()); + delay(75).then(() => modal.remove()); }} > @@ -199,7 +199,7 @@ const StyledOverlay = styled(Dialog.Overlay) <{ place-items: center; overflow-y: auto; z-index: 10; - animation: ${({ open }) => (open ? fadeIn : fadeOut)} 350ms ease-in-out; + animation: ${({ open }) => (open ? fadeIn : fadeOut)} 150ms ease-in-out; /* For Chrome */ &::-webkit-scrollbar { display: none; @@ -215,7 +215,7 @@ const StyledOverlay = styled(Dialog.Overlay) <{ align-items: flex-end; position: absolute; background: rgba(255, 255, 255, 0); - animation: ${props.open ? fadeIn : fadeOut} 350ms ease-in-out; + animation: ${props.open ? fadeIn : fadeOut} 150ms ease-in-out; /* For Chrome */ &::-webkit-scrollbar { display: none; @@ -242,5 +242,5 @@ const StyledContent = styled(Dialog.Content) <{ : drawer ? fadeOutAndSlideDown : fadeOutAndZoomIn} - 350ms ease-in-out; + 150ms ease-in-out; `; From 116dc6b4af140ee356e19829d03957c4a8b409c9 Mon Sep 17 00:00:00 2001 From: Nur Fikri Date: Sat, 12 Oct 2024 04:22:29 +0700 Subject: [PATCH 10/10] [FRE-1088, FRE-1008, FRE-1113, FRE-1110] fix history (#359) --- packages/widget-v2/src/constants/graz.ts | 16 ------ .../SwapExecutionPage/SwapExecutionPage.tsx | 6 +-- .../SwapExecutionPageRouteDetailed.tsx | 1 + .../SwapExecutionPage/useBroadcastedTxs.ts | 12 +++-- ...nsactionStatus.tsx => useSyncTxStatus.tsx} | 49 +++++++------------ .../useSyncPendingTransactionHistoryItems.tsx | 48 ------------------ .../TransactionHistoryPage.tsx | 9 +++- .../TransactionHistoryPageHistoryItem.tsx | 24 +++++++-- ...ansactionHistoryPageHistoryItemDetails.tsx | 13 ++--- .../widget-v2/src/state/swapExecutionPage.ts | 24 +-------- 10 files changed, 60 insertions(+), 142 deletions(-) rename packages/widget-v2/src/pages/SwapExecutionPage/{useFetchTransactionStatus.tsx => useSyncTxStatus.tsx} (59%) delete mode 100644 packages/widget-v2/src/pages/SwapPage/useSyncPendingTransactionHistoryItems.tsx diff --git a/packages/widget-v2/src/constants/graz.ts b/packages/widget-v2/src/constants/graz.ts index 0e98a0a9..5e5d5817 100644 --- a/packages/widget-v2/src/constants/graz.ts +++ b/packages/widget-v2/src/constants/graz.ts @@ -67,8 +67,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "aioz_168-1", "akashnet-2", "alfama", - "andromeda-1", - "archway-1", "arctic-1", "ares-1", "arkeo", @@ -91,17 +89,13 @@ export const keplrMainnetChainIdsInitialConnect = [ "bouachain", "buenavista-1", "bzetestnet-2", - "canto_7700-1", - "carbon-1", "celestia", - "centauri-1", "chihuahua-1", "cifer-2", "cnho_stables-1", "colosseum-1", "columbus-5", "comdex-test-4", - "comdex-1", "constantine-3", "core-1", "coreum-testnet-1", @@ -135,10 +129,8 @@ export const keplrMainnetChainIdsInitialConnect = [ "furya-1", "fxcore", "galactica_9302-1", - "gitopia", "govgen-1", "grand-1", - "gravity-bridge-3", "helichain", "iconlake-testnet-1", "iconlake-1", @@ -152,7 +144,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "ixo-5", "joltify_1729-1", "juno-1", - "kaiyo-1", "kaon-1", "kava_2222-10", "korellia-2", @@ -166,12 +157,10 @@ export const keplrMainnetChainIdsInitialConnect = [ "loop-1", "mainnet-tura", "mande_18071918-1", - "mantle-1", "mantra-hongbai-1", "mars-1", "medasdigital-1", "meme-1", - "migaloo-1", "mineplex-mainnet-1", "mocha-4", "morocco-1", @@ -187,7 +176,6 @@ export const keplrMainnetChainIdsInitialConnect = [ "osmo-test-5", "osmosis-1", "ovg", - "pacific-1", "panacea-3", "passage-2", "phoenix-1", @@ -213,9 +201,7 @@ export const keplrMainnetChainIdsInitialConnect = [ "sentinelhub-2", "sge-network-4", "sgenet-1", - "shentu-2.2", "shido_9007-1", - "sifchain-1", "sixnet", "soarchaintestnet", "sommelier-3", @@ -254,8 +240,6 @@ export const keplrMainnetChainIdsInitialConnect = [ export const walletMainnetChainIdsInitialConnect = [ "cosmoshub-4", "injective-1", - "migaloo-1", - "archway-1", "pacific-1", "noble-1", "osmosis-1", diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx index fa89e1f7..7737847d 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPage.tsx @@ -26,7 +26,7 @@ import { convertSecondsToMinutesOrHours } from "@/utils/number"; import { SignatureIcon } from "@/icons/SignatureIcon"; import pluralize from "pluralize"; import { useBroadcastedTxsStatus } from "./useBroadcastedTxs"; -import { useFetchTransactionStatus } from "./useFetchTransactionStatus"; +import { useSyncTxStatus } from "./useSyncTxStatus"; export enum SwapExecutionState { recoveryAddressUnset, @@ -56,8 +56,8 @@ export const SwapExecutionPage = () => { txs: transactionDetailsArray, }); - useFetchTransactionStatus({ - transferEvents: statusData?.transferEvents, + useSyncTxStatus({ + statusData }); const clientOperations = useMemo(() => { diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx index 1640d9cd..a51d9562 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailed.tsx @@ -118,6 +118,7 @@ export const SwapExecutionPageRouteDetailed = ({ const explorerLink = operation.isSwap ? status?.[operation.transferIndex]?.fromExplorerLink : status?.[operation.transferIndex]?.toExplorerLink; const opStatus = swapExecutionState === SwapExecutionState.confirmed ? "completed" : status?.[operation.transferIndex]?.status; + return ( <> diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts b/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts index 99d8279c..86c8e904 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts +++ b/packages/widget-v2/src/pages/SwapExecutionPage/useBroadcastedTxs.ts @@ -41,7 +41,7 @@ export const useBroadcastedTxsStatus = ({ return _res; })); const transferEvents = getTransferEventsFromTxStatusResponse(results); - const _isSettled = results.every((tx) => { + const _isAllTxSettled = results.every((tx) => { return ( tx.state === "STATE_COMPLETED_SUCCESS" || tx.state === "STATE_COMPLETED_ERROR" || @@ -49,17 +49,19 @@ export const useBroadcastedTxsStatus = ({ ); }); - const _isSuccess = transferEvents.every((tx) => { + const _isAllTxSuccess = transferEvents.every((tx) => { return tx.status === "completed"; }); - if (transferEvents.length > 0 && txsRequired === results.length && _isSettled) { + const isRouteSettled = transferEvents.length > 0 && txsRequired === results.length && _isAllTxSettled; + if (isRouteSettled) { setIsSettled(true); } + const resData: TxsStatus = { - isSuccess: _isSuccess, - isSettled: _isSettled, + isSuccess: _isAllTxSuccess && isRouteSettled, + isSettled: isRouteSettled, transferEvents, }; setPrevData(resData); diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/useSyncTxStatus.tsx similarity index 59% rename from packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx rename to packages/widget-v2/src/pages/SwapExecutionPage/useSyncTxStatus.tsx index efa5a507..e9f26cbd 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/useFetchTransactionStatus.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/useSyncTxStatus.tsx @@ -2,23 +2,24 @@ import { setTransactionHistoryAtom } from "@/state/history"; import { setOverallStatusAtom, swapExecutionStateAtom, - skipTransactionStatusAtom, skipSubmitSwapExecutionAtom, } from "@/state/swapExecutionPage"; import { - ClientTransferEvent, getClientOperations, ClientOperation, - getSimpleOverallStatus, } from "@/utils/clientType"; -import { useSetAtom, useAtomValue, useAtom } from "jotai"; +import { useSetAtom, useAtomValue } from "jotai"; import { useMemo, useEffect } from "react"; +import { TxsStatus } from "./useBroadcastedTxs"; -export const useFetchTransactionStatus = ({ - transferEvents +export const useSyncTxStatus = ({ + statusData, + historyIndex }: { - transferEvents?: ClientTransferEvent[]; + statusData?: TxsStatus, + historyIndex?: number; }) => { + const transferEvents = statusData?.transferEvents; const setOverallStatus = useSetAtom(setOverallStatusAtom); const { route, @@ -26,7 +27,6 @@ export const useFetchTransactionStatus = ({ overallStatus, transactionHistoryIndex, } = useAtomValue(swapExecutionStateAtom); - const [{ data: transactionStatus }] = useAtom(skipTransactionStatusAtom); const setTransactionHistory = useSetAtom(setTransactionHistoryAtom); const { isPending } = useAtomValue(skipSubmitSwapExecutionAtom); @@ -46,19 +46,14 @@ export const useFetchTransactionStatus = ({ return; } - if (!transactionStatus) return; - const lastTransactionIndex = route?.txsRequired - 1; + if (!transferEvents) return; - const lastTransactionStatus = getSimpleOverallStatus( - transactionStatus?.[lastTransactionIndex]?.state - ); - - if (lastTransactionStatus === "completed") { + if (statusData.isSuccess) { return "completed"; } if ( - transferEvents?.find(({ status }) => status === "failed") + !statusData.isSuccess && statusData.isSettled ) { return "failed"; } @@ -74,26 +69,16 @@ export const useFetchTransactionStatus = ({ ) { return "unconfirmed"; } - }, [isPending, route?.operations, route?.txsRequired, setOverallStatus, transactionStatus, transferEvents]); - + }, [isPending, route?.operations, route?.txsRequired, setOverallStatus, statusData?.isSettled, statusData?.isSuccess, transferEvents]); useEffect(() => { - if (overallStatus === "completed" || overallStatus === "failed") return; - if (computedSwapStatus) { - setTransactionHistory(transactionHistoryIndex, { + setTransactionHistory(historyIndex ?? transactionHistoryIndex, { status: computedSwapStatus, }); - setOverallStatus(computedSwapStatus); + if (!historyIndex) { + setOverallStatus(computedSwapStatus); + } } - }, [ - clientOperations, - overallStatus, - computedSwapStatus, - setOverallStatus, - transactionDetailsArray.length, - transactionStatus, - setTransactionHistory, - transactionHistoryIndex, - ]); + }, [clientOperations, overallStatus, computedSwapStatus, setOverallStatus, transactionDetailsArray.length, setTransactionHistory, transactionHistoryIndex, historyIndex]); }; diff --git a/packages/widget-v2/src/pages/SwapPage/useSyncPendingTransactionHistoryItems.tsx b/packages/widget-v2/src/pages/SwapPage/useSyncPendingTransactionHistoryItems.tsx deleted file mode 100644 index 40024b43..00000000 --- a/packages/widget-v2/src/pages/SwapPage/useSyncPendingTransactionHistoryItems.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { setTransactionHistoryAtom, skipFetchPendingTransactionHistoryStatus, transactionHistoryAtom } from "@/state/history"; -import { getSimpleOverallStatus } from "@/utils/clientType"; -import { TxStatusResponse } from "@skip-go/client"; -import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { useEffect } from "react"; - -export const useSyncPendingTransactionHistoryItems = () => { - const [{ data: transactionStatusArray }] = useAtom(skipFetchPendingTransactionHistoryStatus); - const transactionHistory = useAtomValue(transactionHistoryAtom); - const setTransactionHistory = useSetAtom(setTransactionHistoryAtom); - - useEffect(() => { - if (!transactionStatusArray || transactionStatusArray?.length === 0) return; - transactionStatusArray.forEach(async (transactionStatusPromise, index) => { - const transactionStatusByTransactionOrNull = await transactionStatusPromise; - const route = transactionHistory?.[index]?.route; - const transactionStatusFoundForEachOperation = transactionStatusByTransactionOrNull?.flatMap(operation => operation).length === route?.txsRequired; - if (transactionStatusByTransactionOrNull.find(transactionStatus => transactionStatus === null)) return; - const transactionStatusByTransaction = transactionStatusByTransactionOrNull as TxStatusResponse[]; - - const getOverallStatus = () => { - if (transactionStatusByTransaction.every(({ state }) => getSimpleOverallStatus(state) === "completed")) { - if (transactionStatusFoundForEachOperation) { - return "completed"; - } - return "pending"; - } - - if ( - transactionStatusByTransaction.find(({ state }) => getSimpleOverallStatus(state) === "failed") - ) { - return "failed"; - } - if ( - transactionStatusByTransaction.find(({ state }) => getSimpleOverallStatus(state) === "pending") - ) { - return "pending"; - } - - }; - - setTransactionHistory(index, { - status: getOverallStatus(), - timestamp: Date.now(), - }); - }); - }, [setTransactionHistory, transactionHistory, transactionStatusArray]); -}; \ No newline at end of file diff --git a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPage.tsx b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPage.tsx index d874d9e1..a8411fba 100644 --- a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPage.tsx +++ b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPage.tsx @@ -4,7 +4,7 @@ import { SwapPageHeader } from "@/pages/SwapPage/SwapPageHeader"; import { SwapPageFooter } from "@/pages/SwapPage/SwapPageFooter"; import { ICONS } from "@/icons"; import { VirtualList } from "@/components/VirtualList"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { HistoryIcon } from "@/icons/HistoryIcon"; import { SmallText } from "@/components/Typography"; import { useAtomValue, useSetAtom } from "jotai"; @@ -19,6 +19,11 @@ export const TransactionHistoryPage = () => { number | undefined >(); const txHistory = useAtomValue(transactionHistoryAtom); + const historyList = useMemo(() => { + return txHistory.sort((a, b) => { + return b.timestamp - a.timestamp; + }); + }, [txHistory]); return ( { ) : ( ( diff --git a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx index c3ed2e21..ff867186 100644 --- a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx +++ b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItem.tsx @@ -10,6 +10,9 @@ import { HistoryArrowIcon } from "@/icons/HistoryArrowIcon"; import { useGetAssetDetails } from "@/hooks/useGetAssetDetails"; import { removeTransactionHistoryItemAtom, TransactionHistoryItem } from "@/state/history"; import { useSetAtom } from "jotai"; +import { formatDistanceStrict } from "date-fns"; +import { useBroadcastedTxsStatus } from "../SwapExecutionPage/useBroadcastedTxs"; +import { useSyncTxStatus } from "../SwapExecutionPage/useSyncTxStatus"; type TransactionHistoryPageHistoryItemProps = { index: number; @@ -25,6 +28,20 @@ export const TransactionHistoryPageHistoryItem = ({ onClickRow, }: TransactionHistoryPageHistoryItemProps) => { const theme = useTheme(); + + const { data: statusData } = useBroadcastedTxsStatus({ + txsRequired: txHistoryItem?.route.txsRequired, + txs: txHistoryItem.transactionDetails.map(tx => ({ + chainID: tx.chainID, + txHash: tx.txHash, + })), + }); + + useSyncTxStatus({ + statusData, + historyIndex: index, + }); + const removeTransactionHistoryItem = useSetAtom(removeTransactionHistoryItemAtom); const { route: { @@ -92,8 +109,8 @@ export const TransactionHistoryPageHistoryItem = ({ if (status === "pending") { return "In Progress"; } - return "5 mins ago"; - }, [status]); + return formatDistanceStrict(new Date(timestamp), new Date(), { addSuffix: true }); + }, [status, timestamp]); return ( @@ -121,7 +138,6 @@ export const TransactionHistoryPageHistoryItem = ({ sourceChainName={sourceAssetDetails.chainName ?? "--"} destinationChainName={destinationAssetDetails.chainName ?? "--"} absoluteTimeString={absoluteTimeString} - relativeTimeString={relativeTime} transactionDetails={transactionDetails} onClickDelete={() => removeTransactionHistoryItem(index)} /> @@ -187,4 +203,4 @@ const StyledChainName = styled(SmallText)` text-overflow: ellipsis; overflow: hidden; white-space: nowrap; -`; \ No newline at end of file +`; diff --git a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItemDetails.tsx b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItemDetails.tsx index a9c454fb..039263a8 100644 --- a/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItemDetails.tsx +++ b/packages/widget-v2/src/pages/TransactionHistoryPage/TransactionHistoryPageHistoryItemDetails.tsx @@ -15,12 +15,12 @@ type TransactionHistoryPageHistoryItemDetailsProps = { sourceChainName: string; destinationChainName: string; absoluteTimeString: string; - relativeTimeString: string; transactionDetails: TransactionDetails[]; onClickDelete?: () => void; }; const statusMap = { + unconfirmed: "Unconfirmed", signing: "In Progress", broadcasted: "In Progress", pending: "In Progress", @@ -33,7 +33,6 @@ export const TransactionHistoryPageHistoryItemDetails = ({ sourceChainName, destinationChainName, absoluteTimeString, - relativeTimeString, transactionDetails, onClickDelete, }: TransactionHistoryPageHistoryItemDetailsProps) => { @@ -67,10 +66,6 @@ export const TransactionHistoryPageHistoryItemDetails = ({ {destinationChainName} - - Time - {relativeTimeString} - {transactionDetails.length === 1 ? ( @@ -96,9 +91,9 @@ export const TransactionHistoryPageHistoryItemDetails = ({ return "Transaction"; }; return ( - - {getTransactionIdLabel()} - + + {getTransactionIdLabel()} + {getTruncatedAddress(transactionDetail.txHash)} diff --git a/packages/widget-v2/src/state/swapExecutionPage.ts b/packages/widget-v2/src/state/swapExecutionPage.ts index f6e2481a..3e9ac03e 100644 --- a/packages/widget-v2/src/state/swapExecutionPage.ts +++ b/packages/widget-v2/src/state/swapExecutionPage.ts @@ -1,4 +1,4 @@ -import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query"; +import { atomWithMutation } from "jotai-tanstack-query"; import { skipClient } from "@/state/skipClient"; import { skipRouteAtom } from "@/state/route"; import { atom } from "jotai"; @@ -266,25 +266,3 @@ export const skipSubmitSwapExecutionAtom = atomWithMutation((get) => { }, }; }); - -export const skipTransactionStatusAtom = atomWithQuery((get) => { - const skip = get(skipClient); - const { transactionDetailsArray, overallStatus } = get(swapExecutionStateAtom); - - return { - queryKey: ["skipTxStatus", transactionDetailsArray], - queryFn: async () => { - return Promise.all( - transactionDetailsArray.map(async (transaction) => { - return skip.transactionStatus({ - chainID: transaction.chainID, - txHash: transaction.txHash, - }); - }) - ); - }, - enabled: overallStatus !== "completed" && overallStatus !== "failed", - refetchInterval: 1000 * 2, - keepPreviousData: true, - }; -});