diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index 6c65d5c7..c7afbb06 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -11,6 +11,7 @@ import { shallow } from "zustand/shallow"; import { createWithEqualityFn as create } from "zustand/traditional"; import { AssetWithMetadata, useAssets } from "@/context/assets"; +import { useAnyDisclosureOpen } from "@/context/disclosures"; import { useAccount } from "@/hooks/useAccount"; import { useBalancesByChain } from "@/hooks/useBalancesByChain"; import { Chain, useChains } from "@/hooks/useChains"; @@ -47,6 +48,8 @@ export function useSwapWidget() { sourceChain, } = useFormValuesStore(); + const isAnyDisclosureOpen = useAnyDisclosureOpen(); + const { data: routeResponse, fetchStatus: routeFetchStatus, @@ -59,7 +62,7 @@ export function useSwapWidget() { sourceAssetChainID: sourceAsset?.chainID, destinationAsset: destinationAsset?.denom, destinationAssetChainID: destinationAsset?.chainID, - enabled: true, + enabled: !isAnyDisclosureOpen, }); const errorMessage = useMemo(() => { diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index f633c096..821130db 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -1,12 +1,12 @@ import { useManager } from "@cosmos-kit/react"; import { ArrowLeftIcon, CheckCircleIcon } from "@heroicons/react/20/solid"; +import * as Sentry from "@sentry/react"; import { RouteResponse } from "@skip-router/core"; import { clsx } from "clsx"; import { useState } from "react"; import { toast } from "react-hot-toast"; import { useAccount as useWagmiAccount } from "wagmi"; -import { getTrackAccount, trackAccount } from "@/context/account"; import { useSettingsStore } from "@/context/settings"; import { addTxHistory, @@ -14,9 +14,11 @@ import { failTxHistory, successTxHistory, } from "@/context/tx-history"; +import { useAccount } from "@/hooks/useAccount"; import { useChains } from "@/hooks/useChains"; import { useFinalityTimeEstimate } from "@/hooks/useFinalityTimeEstimate"; import { useSkipClient } from "@/solve"; +import { isUserRejectedRequestError } from "@/utils/error"; import { getChainExplorerUrl } from "@/utils/explorer"; import RouteDisplay from "../RouteDisplay"; @@ -63,6 +65,9 @@ function TransactionDialogContent({ const { getWalletRepo } = useManager(); + const srcAccount = useAccount(route.sourceAssetChainID); + const dstAccount = useAccount(route.destAssetChainID); + async function onSubmit() { setTransacting(true); @@ -71,8 +76,12 @@ function TransactionDialogContent({ try { const userAddresses: Record = {}; - const [sourceChainID] = route.chainIDs; - const sourceWalletName = getTrackAccount(sourceChainID)!; + const srcChain = chains.find((c) => { + return c.chainID === route.sourceAssetChainID; + }); + const dstChain = chains.find((c) => { + return c.chainID === route.destAssetChainID; + }); for (const chainID of route.chainIDs) { const chain = chains.find((c) => c.chainID === chainID); @@ -83,18 +92,39 @@ function TransactionDialogContent({ if (chain.chainType === "cosmos") { const { wallets } = getWalletRepo(chain.chainName); - const walletName = getTrackAccount(chainID) || sourceWalletName; + const walletName = (() => { + // if `chainID` is the source or destination chain + if (srcChain?.chainID === chainID) { + return srcAccount?.wallet?.walletName; + } + if (dstChain?.chainID === chainID) { + return dstAccount?.wallet?.walletName; + } + + // if `chainID` isn't the source or destination chain + if (srcChain?.chainType === "cosmos") { + return srcAccount?.wallet?.walletName; + } + if (dstChain?.chainType === "cosmos") { + return dstAccount?.wallet?.walletName; + } + })(); + + if (!walletName) { + throw new Error( + `executeRoute error: cannot find wallet for '${chain.chainName}'`, + ); + } + const wallet = wallets.find((w) => w.walletName === walletName); if (!wallet) { throw new Error( - `executeRoute error: cannot find active wallet for '${chain.chainName}'`, + `executeRoute error: cannot find wallet for '${chain.chainName}'`, ); } - if (wallet.isWalletDisconnected) { + if (wallet.isWalletDisconnected || !wallet.isWalletConnected) { await wallet.connect(); - trackAccount.track(chainID, walletName); } - if (!wallet.address) { throw new Error( `executeRoute error: cannot resolve wallet address for '${chain.chainName}'`, @@ -107,7 +137,6 @@ function TransactionDialogContent({ if (!evmAddress) { throw new Error(`executeRoute error: evm wallet not connected`); } - userAddresses[chainID] = evmAddress; } } @@ -177,6 +206,33 @@ function TransactionDialogContent({ console.error(err); } if (err instanceof Error) { + if (!isUserRejectedRequestError(err)) { + Sentry.withScope((scope) => { + scope.setUser({ + id: srcAccount?.address, + }); + scope.setTransactionName("Swap.onSubmit"); + scope.setTags({ + sourceChain: route.sourceAssetChainID, + destinationChain: route.destAssetChainID, + sourceAssetDenom: route.sourceAssetDenom, + destinationAssetDenom: route.destAssetDenom, + doesSwap: route.doesSwap, + }); + scope.setExtras({ + sourceAddress: srcAccount?.address, + destinationAddress: dstAccount?.address, + sourceChain: route.sourceAssetChainID, + destinationChain: route.destAssetChainID, + sourceAssetDenom: route.sourceAssetDenom, + destinationAssetDenom: route.destAssetDenom, + amountIn: route.amountIn, + amountOut: route.amountOut, + }); + Sentry.captureException(err); + }); + } + toast.error(

Swap Failed! @@ -335,7 +391,7 @@ function TransactionDialogContent({ /> ) : ( - {route.doesSwap ? "Swap" : "Transfer"} Again + Create New {route.doesSwap ? "Swap" : "Transfer"} )} ) : ( diff --git a/src/components/TransactionDialog/index.tsx b/src/components/TransactionDialog/index.tsx index 06a4cc01..f7a547ad 100644 --- a/src/components/TransactionDialog/index.tsx +++ b/src/components/TransactionDialog/index.tsx @@ -29,7 +29,7 @@ function TransactionDialog({ routeWarningTitle, }: Props) { const [hasDisplayedWarning, setHasDisplayedWarning] = useState(false); - const [isOpen, setIsOpen] = useState(false); + const [isOpen, { set: setIsOpen }] = useDisclosureKey("confirmSwapDialog"); const [, control] = useDisclosureKey("priceImpactDialog"); diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index bb5ba619..53812472 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -183,6 +183,25 @@ function WalletModalWithContext() { logo: w.walletInfo.logo, }, connect: async () => { + await w.client.addChain?.({ + chain: { + bech32_prefix: w.chain.bech32_prefix, + chain_id: w.chain.chain_id, + chain_name: w.chain.chain_name, + network_type: w.chain.network_type, + pretty_name: w.chain.pretty_name, + slip44: w.chain.slip44, + status: w.chain.status, + apis: w.chain.apis, + bech32_config: w.chain.bech32_config, + explorers: w.chain.explorers, + extra_codecs: w.chain.extra_codecs, + fees: w.chain.fees, + peers: w.chain.peers, + }, + name: w.chainName, + assetList: w.assetList, + }); await w.connect(); trackAccount.track(chainID, w.walletName); }, diff --git a/src/context/disclosures.ts b/src/context/disclosures.ts index 440bcfc0..b5f2cd54 100644 --- a/src/context/disclosures.ts +++ b/src/context/disclosures.ts @@ -2,6 +2,7 @@ import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; const defaultValues = { + confirmSwapDialog: false, contactDialog: false, historyDialog: false, priceImpactDialog: false, @@ -11,7 +12,6 @@ const defaultValues = { // TODO: port dialogs to new system // assetSelect: false, // chainSelect: false, - // txDialog: false, }; export type DisclosureStore = typeof defaultValues & { diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 00000000..3fa0b8f5 --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,14 @@ +export const isUserRejectedRequestError = (error: Error) => { + if ( + // keplr | metamask + error.message.toLowerCase().includes("rejected") || + // leap + error.message.toLowerCase().includes("declined") || + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error common user rejected request error code + error.code === 4001 + ) { + return true; + } + return false; +};