From c1c77b462f8a3c6af96720c98a3d643d822763e8 Mon Sep 17 00:00:00 2001 From: plubber Date: Mon, 18 Mar 2024 11:38:53 -0400 Subject: [PATCH 01/47] remove unused deps --- package-lock.json | 60 ----------------------------------------------- package.json | 4 ---- 2 files changed, 64 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29b073509..3928016d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,12 +22,10 @@ "@terra-money/terra-utils": "^1.2.0-beta.8", "@terra-money/terra.js": "^3.1.9", "@terra-money/terra.proto": "^2.0.0", - "@terra-money/wallet-kit": "^1.0.15", "@tippyjs/react": "^4.2.6", "axios": "^0.27.2", "bech32": "^2.0.0", "bignumber.js": "^9.0.2", - "buffer": "^6.0.3", "classnames": "^2.3.2", "crypto-js": "^4.1.1", "date-fns": "^2.28.0", @@ -36,10 +34,8 @@ "immutability-helper": "^3.1.1", "js-base64": "^3.7.2", "keccak256": "^1.0.6", - "lottie-react": "^2.3.1", "moment": "^2.29.4", "numeral": "^2.0.6", - "prettier": "^2.6.2", "qrcode.react": "^3.1.0", "qs": "^6.11.0", "ramda": "^0.28.0", @@ -8577,36 +8573,6 @@ } } }, - "node_modules/@terra-money/station-wallet": { - "version": "1.0.16", - "license": "Apache-2.0", - "dependencies": { - "@terra-money/station-connector": "^1.0.16", - "@terra-money/wallet-interface": "^1.0.16" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@terra-money/feather.js": "^1.0.8" - } - }, - "node_modules/@terra-money/station-wallet/node_modules/@terra-money/station-connector": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@terra-money/station-connector/-/station-connector-1.0.16.tgz", - "integrity": "sha512-m16a4KkUWF+Mhmf4LiBO1TvCdIabJFudbCC+Cr2JwknZnPQ2SmMHSfyBhS5SaJGWIhmHd4yZalRdNBr+QUc6hA==", - "dependencies": { - "bech32": "^2.0.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@cosmjs/amino": "^0.31.0", - "@terra-money/feather.js": "^1.0.8", - "axios": "^0.27.2" - } - }, "node_modules/@terra-money/terra-utils": { "version": "1.2.0-beta.8", "resolved": "https://registry.npmjs.org/@terra-money/terra-utils/-/terra-utils-1.2.0-beta.8.tgz", @@ -8712,32 +8678,6 @@ "protobufjs": "~6.11.2" } }, - "node_modules/@terra-money/wallet-interface": { - "version": "1.0.16", - "license": "Apache-2.0", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@terra-money/feather.js": "^1.0.8" - } - }, - "node_modules/@terra-money/wallet-kit": { - "version": "1.0.16", - "license": "Apache-2.0", - "dependencies": { - "@terra-money/station-wallet": "^1.0.16", - "@terra-money/wallet-interface": "^1.0.16" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@terra-money/feather.js": "^1.0.8", - "axios": "^0.27.2", - "react": "^18.2.0" - } - }, "node_modules/@tippyjs/react": { "version": "4.2.6", "license": "MIT", diff --git a/package.json b/package.json index 9bbd98ccf..9a08bdc13 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,10 @@ "@terra-money/terra-utils": "^1.2.0-beta.8", "@terra-money/terra.js": "^3.1.9", "@terra-money/terra.proto": "^2.0.0", - "@terra-money/wallet-kit": "^1.0.15", "@tippyjs/react": "^4.2.6", "axios": "^0.27.2", "bech32": "^2.0.0", "bignumber.js": "^9.0.2", - "buffer": "^6.0.3", "classnames": "^2.3.2", "crypto-js": "^4.1.1", "date-fns": "^2.28.0", @@ -45,10 +43,8 @@ "immutability-helper": "^3.1.1", "js-base64": "^3.7.2", "keccak256": "^1.0.6", - "lottie-react": "^2.3.1", "moment": "^2.29.4", "numeral": "^2.0.6", - "prettier": "^2.6.2", "qrcode.react": "^3.1.0", "qs": "^6.11.0", "ramda": "^0.28.0", From 3f00091a4ce6b3c24845a8f0867cc46e03782052 Mon Sep 17 00:00:00 2001 From: plubber Date: Mon, 18 Mar 2024 15:56:55 -0400 Subject: [PATCH 02/47] Add network validation retries*** This commit adds the ability to retry failed network validation queries. It introduces a new state to track failed networks and an attempt count. The useEffect hook is used to schedule retries every 10 seconds for up to 10 attempts. The commit also includes error handling for failed queries and updates the useValidNetworks function to return the validation queries with null data for queries that are still failing. --- src/data/queries/tendermint.ts | 53 +++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/data/queries/tendermint.ts b/src/data/queries/tendermint.ts index eef7bf3df..98b39cfe8 100644 --- a/src/data/queries/tendermint.ts +++ b/src/data/queries/tendermint.ts @@ -4,6 +4,7 @@ import { useQueries, useQuery } from "react-query" import { useNetworks } from "app/InitNetworks" import { randomAddress } from "utils/bech32" import axios from "axios" +import { useEffect, useState } from "react" export const useLocalNodeInfo = (chainID: string) => { const { networks } = useNetworks() @@ -77,27 +78,53 @@ interface Network { } export const useValidNetworks = (networks: Network[]) => { - return useQueries( + const [failedNetworks, setFailedNetworks] = useState([]) + const [attemptCount, setAttemptCount] = useState(0) + console.log("attemptCount", attemptCount) + console.log("failedNetworks", failedNetworks) + + useEffect(() => { + if (failedNetworks.length > 0 && attemptCount < 10) { + const timer = setTimeout(() => { + setAttemptCount(attemptCount + 1) + }, 10000) + + return () => clearTimeout(timer) + } + }, [failedNetworks, attemptCount]) + + const validationQueries = useQueries( networks.map(({ chainID, prefix, lcd }) => { return { queryKey: [queryKey.tendermint.nodeInfo, lcd], queryFn: async () => { - if (prefix === "terra") return chainID - - const { data } = (await axios.get( - `/cosmos/bank/v1beta1/balances/${randomAddress(prefix)}`, - { - baseURL: lcd, // TODO: pass custom lcd to the function - timeout: VALIDATION_TIMEOUT, - } - )) || { - data: {}, + try { + if (prefix === "terra") return chainID + if (prefix === "eth") throw Error("poop") + const { data } = (await axios.get( + `/cosmos/bank/v1beta1/balances/${randomAddress(prefix)}`, + { + baseURL: lcd, + timeout: VALIDATION_TIMEOUT, + } + )) ?? { data: {} } + if (Array.isArray(data.balances)) return chainID + } catch (error) { + setFailedNetworks((prev) => [...prev, { chainID, prefix, lcd }]) } - - if (Array.isArray(data.balances)) return chainID }, ...RefetchOptions.INFINITY, } }) ) + + return validationQueries.map((query, index) => { + if ( + query.isError && + !failedNetworks.some((network) => network.lcd === networks[index].lcd) + ) { + return { ...query, data: null } + } + return query + }) } From fc26630177f60a2321f2bafbd8f284628ee43cd6 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 18 Mar 2024 23:32:54 -0500 Subject: [PATCH 03/47] completed updating input to have text dropdown inside. Added this to Token.tsx --- src/pages/wallet/SendPage/Token.tsx | 124 +++++++++++++++++++--------- 1 file changed, 84 insertions(+), 40 deletions(-) diff --git a/src/pages/wallet/SendPage/Token.tsx b/src/pages/wallet/SendPage/Token.tsx index 8953b48f8..a531fa4e5 100644 --- a/src/pages/wallet/SendPage/Token.tsx +++ b/src/pages/wallet/SendPage/Token.tsx @@ -1,34 +1,35 @@ +import { useEffect, useMemo, useState } from "react" +import { useTranslation } from "react-i18next" +import { truncate } from "@terra-money/terra-utils" import { SectionHeader, InputInLine, TokenSingleChainListItem, FlexColumn, + InputWrapper, + Input, + Dropdown, } from "@terra-money/station-ui" import { useInterchainAddresses } from "auth/hooks/useAddress" -import WithSearchInput from "pages/custom/WithSearchInput" -import { truncate } from "@terra-money/terra-utils" import { useWhitelist } from "data/queries/chains" -import { useTranslation } from "react-i18next" import { useNetworkName } from "data/wallet" -import { useNativeDenoms } from "data/token" import { Empty } from "components/feedback" -import { useSend } from "./SendContext" import { Read } from "components/token" -import { AssetType } from "./types" -import { useMemo } from "react" +import { ChainID } from "types/network" import { has } from "utils/num" +import { useSend } from "./SendContext" +import { AssetType } from "./types" const Token = () => { const { form, goToStep, getWalletName, assetList, getIBCChannel, networks } = useSend() - const { setValue, watch } = form + const { setValue, watch, register } = form const networkName = useNetworkName() const addresses = useInterchainAddresses() const { ibcDenoms } = useWhitelist() const { t } = useTranslation() - const readNativeDenoms = useNativeDenoms() const { destination, recipient, asset } = watch() - const defaultSearch = readNativeDenoms(asset ?? "") + const [selectedChain, setSelectedChain] = useState("all") const tokens = useMemo(() => { return assetList.reduce((acc, a) => { @@ -101,6 +102,46 @@ const Token = () => { getIBCChannel, ]) + const [filtered, setFiltered] = useState(tokens) + + useEffect(() => { + console.log("🚀 ~ useEffect ~ asset:", asset) + if (asset) { + const filtered = tokens + .filter((t: AssetType) => { + if (selectedChain === "all") { + return ( + t.symbol.toLowerCase().includes(asset.toLowerCase()) || + t.chain.label.toLowerCase().includes(asset.toLowerCase()) + ) + } + + return ( + (t.symbol.toLowerCase().includes(asset.toLowerCase()) || + t.chain.label.toLowerCase().includes(asset.toLowerCase())) && + t.tokenChain.toLowerCase() === selectedChain.toLowerCase() + ) + }) + .sort( + (a: AssetType, b: AssetType) => + parseInt(b.balVal) - parseInt(a.balVal) + ) + setFiltered(filtered) + } else if (selectedChain !== "all" && (asset === "" || !asset)) { + const filtered = tokens + .filter((t: AssetType) => { + return t.tokenChain.toLowerCase() === selectedChain.toLowerCase() + }) + .sort( + (a: AssetType, b: AssetType) => + parseInt(b.balVal) - parseInt(a.balVal) + ) + setFiltered(filtered) + } else { + setFiltered(tokens) + } + }, [asset, selectedChain]) + const onClick = (asset: AssetType) => { setValue("asset", asset.denom) setValue("chain", asset.tokenChain) @@ -108,6 +149,14 @@ const Token = () => { goToStep(4) } + const options = [ + { label: "All Chains", value: "all" }, + ...Object.values(networks).map((n) => ({ + label: n.name, + value: n.chainID, + })), + ] + if (!recipient) { goToStep(1) return null @@ -124,36 +173,31 @@ const Token = () => { value={recipientName} /> - - {(search) => { - const filtered = tokens - .filter( - (t: AssetType) => - t.symbol.toLowerCase().includes(search.toLowerCase()) || - t.chain.label.toLowerCase().includes(search.toLowerCase()) - ) - .sort( - (a: AssetType, b: AssetType) => - parseInt(b.balVal) - parseInt(a.balVal) - ) - return ( - - {filtered.length === 0 && } - {filtered.map((asset: AssetType, i: number) => ( - onClick(asset)} - /> - ))} - - ) - }} - + + setSelectedChain(value)} + variant="textDisplay" + optionsAlign="right" + /> + } + /> + + + {filtered.length === 0 && } + {filtered.map((asset: AssetType, i: number) => ( + onClick(asset)} + /> + ))} + ) } From 1902e64d6c309a6d947027ebcc9d77bffdca782e Mon Sep 17 00:00:00 2001 From: plubber Date: Tue, 19 Mar 2024 11:46:40 -0400 Subject: [PATCH 04/47] Refactor useValidNetworks function to handle network validation and retries --- src/data/queries/tendermint.ts | 69 ++++++++++++++++------------------ 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/src/data/queries/tendermint.ts b/src/data/queries/tendermint.ts index 98b39cfe8..be8d78612 100644 --- a/src/data/queries/tendermint.ts +++ b/src/data/queries/tendermint.ts @@ -4,7 +4,8 @@ import { useQueries, useQuery } from "react-query" import { useNetworks } from "app/InitNetworks" import { randomAddress } from "utils/bech32" import axios from "axios" -import { useEffect, useState } from "react" +import { useState } from "react" +import { ChainID } from "types/network" export const useLocalNodeInfo = (chainID: string) => { const { networks } = useNetworks() @@ -78,53 +79,47 @@ interface Network { } export const useValidNetworks = (networks: Network[]) => { + const MAX_ATTEMPTS = 10 + const REFETCH_WAIT_INTERVAL = 5000 // 5 seconds + const [failedNetworks, setFailedNetworks] = useState([]) + const [successNetworks, setSuccessNetworks] = useState([]) const [attemptCount, setAttemptCount] = useState(0) - console.log("attemptCount", attemptCount) - console.log("failedNetworks", failedNetworks) - - useEffect(() => { - if (failedNetworks.length > 0 && attemptCount < 10) { - const timer = setTimeout(() => { - setAttemptCount(attemptCount + 1) - }, 10000) - return () => clearTimeout(timer) - } - }, [failedNetworks, attemptCount]) - - const validationQueries = useQueries( + return useQueries( networks.map(({ chainID, prefix, lcd }) => { return { queryKey: [queryKey.tendermint.nodeInfo, lcd], queryFn: async () => { - try { - if (prefix === "terra") return chainID - if (prefix === "eth") throw Error("poop") - const { data } = (await axios.get( - `/cosmos/bank/v1beta1/balances/${randomAddress(prefix)}`, - { - baseURL: lcd, - timeout: VALIDATION_TIMEOUT, - } - )) ?? { data: {} } - if (Array.isArray(data.balances)) return chainID - } catch (error) { - setFailedNetworks((prev) => [...prev, { chainID, prefix, lcd }]) + if (successNetworks.includes(chainID) || prefix === "terra") + return chainID + const res = await axios.get( + `/cosmos/bank/v1beta1/balances/${randomAddress(prefix)}`, + { + baseURL: lcd, + timeout: VALIDATION_TIMEOUT, + } + ) + if (Array.isArray(res?.data?.balances)) { + setSuccessNetworks((prev) => [...prev, chainID]) + return chainID } }, ...RefetchOptions.INFINITY, + enabled: attemptCount < MAX_ATTEMPTS, + refetchOnMount: false, + refetchInterval: failedNetworks.some((network) => network.lcd === lcd) + ? REFETCH_WAIT_INTERVAL + : undefined, + onSettled: (data: any, error: any) => { + if (error && attemptCount < MAX_ATTEMPTS - 1) { + setFailedNetworks((prev) => [...prev, { chainID, prefix, lcd }]) + setAttemptCount(attemptCount + 1) + } else if (data) { + setSuccessNetworks((prev) => [...prev, chainID]) + } + }, } }) ) - - return validationQueries.map((query, index) => { - if ( - query.isError && - !failedNetworks.some((network) => network.lcd === networks[index].lcd) - ) { - return { ...query, data: null } - } - return query - }) } From ce38063883ec47740be93822207ba36ce2cb93a0 Mon Sep 17 00:00:00 2001 From: plubber Date: Tue, 19 Mar 2024 16:27:09 -0400 Subject: [PATCH 05/47] Add WalletNameInput component and update ManageWalletsPage --- src/auth/scripts/validate.ts | 2 + src/extension/auth/ManageWalletsPage.tsx | 7 ++- src/extension/auth/WalletNameInput.tsx | 59 ++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/extension/auth/WalletNameInput.tsx diff --git a/src/auth/scripts/validate.ts b/src/auth/scripts/validate.ts index 00c2ab814..9a83d654d 100644 --- a/src/auth/scripts/validate.ts +++ b/src/auth/scripts/validate.ts @@ -21,6 +21,8 @@ const validate = { return true } }, + noSpaces: (name: string) => + !name.includes(" ") || "A name cannot contain spaces.", }, address: { diff --git a/src/extension/auth/ManageWalletsPage.tsx b/src/extension/auth/ManageWalletsPage.tsx index e5cdb7816..84ee3b29a 100644 --- a/src/extension/auth/ManageWalletsPage.tsx +++ b/src/extension/auth/ManageWalletsPage.tsx @@ -3,6 +3,8 @@ import { useNavigate, useParams } from "react-router-dom" import { useManageWallet } from "auth/modules/manage/ManageWallets" import ExtensionList from "extension/components/ExtensionList" import ExtensionPageV2 from "extension/components/ExtensionPageV2" +import { Grid } from "@terra-money/station-ui" +import WalletNameInput from "./WalletNameInput" export default function ManageWalletsPage() { const { t } = useTranslation() @@ -22,7 +24,10 @@ export default function ManageWalletsPage() { backButtonPath="/manage-wallet/select" subtitle={wallet} > - + + + + ) } diff --git a/src/extension/auth/WalletNameInput.tsx b/src/extension/auth/WalletNameInput.tsx new file mode 100644 index 000000000..36ae0ad77 --- /dev/null +++ b/src/extension/auth/WalletNameInput.tsx @@ -0,0 +1,59 @@ +import { useTranslation } from "react-i18next" +import { useNavigate, useParams } from "react-router-dom" +import { InputWrapper, Input, Form } from "@terra-money/station-ui" +import validate from "auth/scripts/validate" +import { useForm } from "react-hook-form" +import { getStoredWallets, storeWallets } from "auth/scripts/keystore" +import { useAuth } from "auth" + +type Values = { + name: string +} + +const WalletNameInput = () => { + const { t } = useTranslation() + const { wallet } = useParams() + const { connect } = useAuth() + const { formState, register, handleSubmit, setValue } = useForm({ + mode: "onChange", + defaultValues: { name: wallet }, + }) + const navigate = useNavigate() + + const submit = (values: Values) => { + const { name } = values + const validationResult = validate.name.exists(name) + + if (typeof validationResult === "string") { + // Revert the name to the previously stored wallet name + setValue("name", wallet ?? "") + return + } + + const wallets = getStoredWallets() + const updatedWallets = wallets.map((w: ResultStoredWallet) => + w.name === wallet ? { ...w, name } : w + ) + storeWallets(updatedWallets) + navigate(`/manage-wallet/manage/${name}`) + connect(name) + } + + return ( +
+ + + +
+ ) +} + +export default WalletNameInput From 63ead6c092f7d0a2d3256772e57a6a369a1ae862 Mon Sep 17 00:00:00 2001 From: plubber Date: Tue, 19 Mar 2024 16:44:56 -0400 Subject: [PATCH 06/47] Refactor ManageWalletsPage component --- src/extension/auth/ManageWalletsPage.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/extension/auth/ManageWalletsPage.tsx b/src/extension/auth/ManageWalletsPage.tsx index 84ee3b29a..bdaf7bd6c 100644 --- a/src/extension/auth/ManageWalletsPage.tsx +++ b/src/extension/auth/ManageWalletsPage.tsx @@ -1,16 +1,17 @@ -import { useTranslation } from "react-i18next" import { useNavigate, useParams } from "react-router-dom" import { useManageWallet } from "auth/modules/manage/ManageWallets" import ExtensionList from "extension/components/ExtensionList" import ExtensionPageV2 from "extension/components/ExtensionPageV2" import { Grid } from "@terra-money/station-ui" import WalletNameInput from "./WalletNameInput" +import useAddress from "auth/hooks/useAddress" +import { truncate } from "@terra-money/terra-utils" export default function ManageWalletsPage() { - const { t } = useTranslation() const { wallet } = useParams() const manageWallet = useManageWallet(wallet ?? "") const navigate = useNavigate() + const address = useAddress() if (!manageWallet || !wallet) { navigate("/manage-wallet/select") @@ -19,10 +20,10 @@ export default function ManageWalletsPage() { return ( From e0e97f24752f9cb92dd26bc5ceeeaaff74951737 Mon Sep 17 00:00:00 2001 From: plubber Date: Tue, 19 Mar 2024 17:31:44 -0400 Subject: [PATCH 07/47] Fix SendContext import and add ConfirmLeaveModal to SwapTx --- src/components/form/ConfirmLeaveModal.tsx | 43 ++++++++++++++++++++ src/components/form/useConfirmLeave.tsx | 29 +++++++++++++ src/extension/App.tsx | 10 ++++- src/extension/components/ExtensionPageV2.tsx | 11 ++++- src/pages/wallet/SendPage/SendContext.tsx | 2 +- src/pages/wallet/SendPage/SendTx.tsx | 20 +++++++-- src/pages/wallet/WalletRouter.tsx | 7 +++- src/txs/swap/SwapTx.tsx | 18 ++++++-- 8 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 src/components/form/ConfirmLeaveModal.tsx create mode 100644 src/components/form/useConfirmLeave.tsx diff --git a/src/components/form/ConfirmLeaveModal.tsx b/src/components/form/ConfirmLeaveModal.tsx new file mode 100644 index 000000000..722302cc7 --- /dev/null +++ b/src/components/form/ConfirmLeaveModal.tsx @@ -0,0 +1,43 @@ +import { useTranslation } from "react-i18next" +import { + Button, + ButtonInlineWrapper, + Modal, + ModalProps, +} from "@terra-money/station-ui" + +interface ConfirmLeaveModalProps extends ModalProps { + onConfirm: () => void +} + +const ConfirmLeaveModal = (props: ConfirmLeaveModalProps) => { + const { t } = useTranslation() + const { isOpen, onRequestClose, onConfirm } = props + + const Footer = () => { + return ( + + + + + ) + } + + return ( + + ) +} + +export default ConfirmLeaveModal diff --git a/src/components/form/useConfirmLeave.tsx b/src/components/form/useConfirmLeave.tsx new file mode 100644 index 000000000..85c0fcce9 --- /dev/null +++ b/src/components/form/useConfirmLeave.tsx @@ -0,0 +1,29 @@ +import { useState } from "react" +import { useNavigate } from "react-router-dom" + +const useConfirmLeave = (isDirty: boolean) => { + const [confirmModal, setConfirmModal] = useState(false) + const navigate = useNavigate() + + const onClose = () => { + if (isDirty) { + setConfirmModal(true) + } else { + navigate("/") + } + } + + const handleConfirmLeave = () => { + setConfirmModal(false) + navigate("/") + } + + return { + confirmModal, + setConfirmModal, + onClose, + handleConfirmLeave, + } +} + +export default useConfirmLeave diff --git a/src/extension/App.tsx b/src/extension/App.tsx index 13828d069..bdf0881a7 100644 --- a/src/extension/App.tsx +++ b/src/extension/App.tsx @@ -39,6 +39,7 @@ import Auth from "./auth/Auth" import UpgradeWalletButton from "app/sections/UpgradeWalletButton" import { Tooltip } from "@terra-money/station-ui" import { useTranslation } from "react-i18next" +import SwapContext, { SwapProvider } from "txs/swap/SwapContext" const App = () => { const { networks } = useNetworks() @@ -82,7 +83,14 @@ const App = () => { { path: "/preferences/*", element: }, /* default txs */ - { path: "/swap/*", element: }, + { + path: "/swap/*", + element: ( + + + + ), + }, { path: "/multisig/sign", element: }, { path: "/multisig/post", element: }, diff --git a/src/extension/components/ExtensionPageV2.tsx b/src/extension/components/ExtensionPageV2.tsx index 64e980973..3c9906201 100644 --- a/src/extension/components/ExtensionPageV2.tsx +++ b/src/extension/components/ExtensionPageV2.tsx @@ -14,6 +14,7 @@ interface Props extends QueryState { overNavbar?: boolean backButtonPath?: string hideCloseButton?: boolean + onClose?: () => void } const ExtensionPageV2 = (props: PropsWithChildren) => { @@ -25,10 +26,16 @@ const ExtensionPageV2 = (props: PropsWithChildren) => { backButtonPath, hideCloseButton, children, + onClose, } = props const cx = classNames.bind(styles) const navigate = useNavigate() + const handleClose = () => { + if (onClose) onClose() + else navigate("/") + } + return ( ) => { diff --git a/src/pages/wallet/SendPage/SendContext.tsx b/src/pages/wallet/SendPage/SendContext.tsx index 120bf6a0f..9a5c45b8e 100644 --- a/src/pages/wallet/SendPage/SendContext.tsx +++ b/src/pages/wallet/SendPage/SendContext.tsx @@ -39,7 +39,7 @@ interface Send { networks: IInterchainNetworks } -export const [useSend, SendProvider] = createContext("useSwap") +export const [useSend, SendProvider] = createContext("useSend") const SendContext = ({ children }: PropsWithChildren<{}>) => { const navigate = useNavigate() const form = useForm({ mode: "onChange" }) diff --git a/src/pages/wallet/SendPage/SendTx.tsx b/src/pages/wallet/SendPage/SendTx.tsx index dab33fd16..76c61eb47 100644 --- a/src/pages/wallet/SendPage/SendTx.tsx +++ b/src/pages/wallet/SendPage/SendTx.tsx @@ -1,16 +1,21 @@ -import { Routes, Route, useLocation } from "react-router-dom" +import { Routes, Route, useLocation, useNavigate } from "react-router-dom" import { useTranslation } from "react-i18next" import Address from "./Address" import Chain from "./Chain" import Token from "./Token" import Submit from "./Submit" import Confirm from "./Confirm" -import SendContext from "./SendContext" import ExtensionPageV2 from "extension/components/ExtensionPageV2" +import { useSend } from "./SendContext" +import ConfirmLeaveModal from "components/form/ConfirmLeaveModal" +import useConfirmLeave from "components/form/useConfirmLeave" const SendTx = () => { const { pathname } = useLocation() const { t } = useTranslation() + const { form } = useSend() + const { confirmModal, setConfirmModal, onClose, handleConfirmLeave } = + useConfirmLeave(form.formState.isDirty) const getBackPath = (pathname: string) => { const step = Number(pathname.split("/").pop()) @@ -28,7 +33,13 @@ const SendTx = () => { ] return ( - + <> + setConfirmModal(false)} + onConfirm={handleConfirmLeave} + /> + {routes.map((r) => ( { backButtonPath={getBackPath(pathname)} title={t(r.title)} fullHeight + onClose={onClose} > {r.element} @@ -46,7 +58,7 @@ const SendTx = () => { /> ))} - + ) } diff --git a/src/pages/wallet/WalletRouter.tsx b/src/pages/wallet/WalletRouter.tsx index 5bc618726..504c8a4f2 100644 --- a/src/pages/wallet/WalletRouter.tsx +++ b/src/pages/wallet/WalletRouter.tsx @@ -9,6 +9,7 @@ import SendTx from "./SendPage/SendTx" import WalletMain from "./WalletMain" import AssetPage from "./AssetPage" import ExtensionPageV2 from "extension/components/ExtensionPageV2" +import SendContext from "./SendPage/SendContext" interface IRoute { path: string @@ -43,7 +44,11 @@ export const useWalletRoutes = (): IRoute[] => { }, { path: "/send/*", - element: , + element: ( + + + + ), }, { path: "/asset/:chain/:denom", diff --git a/src/txs/swap/SwapTx.tsx b/src/txs/swap/SwapTx.tsx index 15ae30ed1..d7469bd4b 100644 --- a/src/txs/swap/SwapTx.tsx +++ b/src/txs/swap/SwapTx.tsx @@ -2,13 +2,19 @@ import { useTranslation } from "react-i18next" import { Routes, Route, useLocation } from "react-router-dom" import Setup from "./SwapSetup" import Confirm from "./SwapConfirm" -import SwapContext from "./SwapContext" +import { useSwap } from "./SwapContext" import SwapSettings from "./SwapSettingsPage" import ExtensionPageV2 from "extension/components/ExtensionPageV2" +import useConfirmLeave from "components/form/useConfirmLeave" +import ConfirmLeaveModal from "components/form/ConfirmLeaveModal" const SwapTx = () => { const location = useLocation() const { t } = useTranslation() + const { form } = useSwap() + const { confirmModal, setConfirmModal, onClose, handleConfirmLeave } = + useConfirmLeave(form.formState.isDirty) + const backPath = location.pathname.split("/").slice(0, -1).join("/") const routes = [ { path: "/", element: , title: "Swap" }, @@ -21,7 +27,12 @@ const SwapTx = () => { ] return ( - + <> + setConfirmModal(false)} + onConfirm={handleConfirmLeave} + /> {routes.map((r) => ( { title={t(r.title)} overNavbar={r.path !== "/"} fullHeight + onClose={onClose} > {r.element} @@ -40,7 +52,7 @@ const SwapTx = () => { /> ))} - + ) } From 4af5a1fd658abc062f9608812e6a799387ba342a Mon Sep 17 00:00:00 2001 From: Manuel Alessandro Collazo Date: Wed, 20 Mar 2024 18:59:38 +0900 Subject: [PATCH 08/47] feat: porting activity details changes --- src/pages/activity/ActivityDetailsPage.tsx | 63 ++++++++++++++-------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/pages/activity/ActivityDetailsPage.tsx b/src/pages/activity/ActivityDetailsPage.tsx index ee83baf8d..829d75759 100644 --- a/src/pages/activity/ActivityDetailsPage.tsx +++ b/src/pages/activity/ActivityDetailsPage.tsx @@ -5,6 +5,8 @@ import { SummaryTable, Timeline, ExternalLinkIcon, + Button, + Grid, } from "@terra-money/station-ui" import styles from "./ActivityDetailsPage.module.scss" import { ExternalLink } from "components/general" @@ -13,7 +15,7 @@ import { useTranslation } from "react-i18next" import { useNetwork } from "data/wallet" import { toNow } from "utils/date" import moment from "moment" -import { ReactElement } from "react" +import { ReactElement, useState } from "react" import { useAllInterchainAddresses } from "auth/hooks/useAddress" import { getCanonicalMsg } from "@terra-money/terra-utils" import { last } from "ramda" @@ -24,7 +26,6 @@ import { getRecvIbcTxDetails, useIbcNextHop, useIbcPrevHop, - useIbcTimeout, } from "txs/useIbcTxs" interface Props { @@ -132,9 +133,7 @@ const PrevHopActivity = (ibcDetails: IbcTxDetails) => { } const NextHopActivity = (ibcDetails: IbcTxDetails) => { - const { data: nextTx } = useIbcNextHop(ibcDetails) - const { data: timeoutTx } = useIbcTimeout(ibcDetails, !!nextTx) - const tx = nextTx ?? timeoutTx + const { data: tx } = useIbcNextHop(ibcDetails) const network = useNetwork() const { t } = useTranslation() const parseMsgs = useParseMessages() @@ -158,12 +157,12 @@ const NextHopActivity = (ibcDetails: IbcTxDetails) => { const { activityMessages, activityType } = parseMsgs(tx) - const nextIbcDetails = nextTx && getIbcTxDetails(nextTx) + const nextIbcDetails = getIbcTxDetails(tx) const timelineDisplayMessages = activityMessages.map( (message: ReactElement) => { return { - variant: (tx.code !== 0 || !!timeoutTx ? "warning" : "success") as + variant: (tx.code === 0 ? "success" : "warning") as | "success" | "warning", msg: message, @@ -176,7 +175,7 @@ const NextHopActivity = (ibcDetails: IbcTxDetails) => { { + navigator.clipboard.writeText(txHash) + + setIsCopied(true) + setTimeout(() => setIsCopied(false), 2000) // Reset after 2 seconds + } const timelineDisplayMessages = timelineMessages.map( (message: ReactElement) => { @@ -246,7 +253,7 @@ const ActivityDetailsPage = ({ return (
- {!!prevIbcDetails && } + {prevIbcDetails && } } middleItems={timelineDisplayMessages} - hasNextElement={!!ibcDetails} + hasNextElement={ibcDetails && variant !== "failed"} /> - {!!ibcDetails && } + {ibcDetails && variant !== "failed" && ( + + )}
- - - - } - /> - + + + + + } + /> +
) } From a7c6b713af5ad06e2a01e4c107d3587688cb6284 Mon Sep 17 00:00:00 2001 From: plubber Date: Wed, 20 Mar 2024 11:55:48 -0400 Subject: [PATCH 09/47] Update value property in AssetType interface --- src/pages/wallet/SendPage/Token.tsx | 131 ++++++++++++++++------------ src/pages/wallet/SendPage/types.tsx | 2 +- 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/pages/wallet/SendPage/Token.tsx b/src/pages/wallet/SendPage/Token.tsx index a531fa4e5..9fbed8916 100644 --- a/src/pages/wallet/SendPage/Token.tsx +++ b/src/pages/wallet/SendPage/Token.tsx @@ -19,8 +19,21 @@ import { ChainID } from "types/network" import { has } from "utils/num" import { useSend } from "./SendContext" import { AssetType } from "./types" +import { toInput } from "txs/utils" -const Token = () => { +type TokenChainData = { + denom: string + id: string + balance: number + decimals: number + tokenPrice: number + chainID: string + chainName: string + tokenIcon: string + supported: boolean +} + +const TokenSelection = () => { const { form, goToStep, getWalletName, assetList, getIBCChannel, networks } = useSend() const { setValue, watch, register } = form @@ -31,9 +44,9 @@ const Token = () => { const { destination, recipient, asset } = watch() const [selectedChain, setSelectedChain] = useState("all") - const tokens = useMemo(() => { + const tokens: AssetType[] = useMemo(() => { return assetList.reduce((acc, a) => { - a.tokenChainInfo.forEach((tokenChainData: any) => { + a.tokenChainInfo.forEach((tokenChainData: TokenChainData) => { const { denom, id, @@ -46,12 +59,13 @@ const Token = () => { supported, } = tokenChainData - if (acc.some((asset: AssetType) => asset.id === id)) { - return acc - } - if (!has(tokenChainData.balance)) { + if ( + acc.some((asset: AssetType) => asset.id === id) || + !has(tokenChainData.balance) + ) { return acc } + const isNative = chain === destination const channel = getIBCChannel({ from: chain, @@ -61,23 +75,22 @@ const Token = () => { }) if ((isNative || channel) && supported) { - const balVal = balance * price + const value = balance * price + console.log({ value, denom }) const senderAddress = addresses?.[chain] - const item = { + const item: AssetType = { ...a, id, denom, tokenImg: icon, - balVal, + value: value / Math.pow(10, decimals), senderAddress, balance, channel, tokenChain: chain, amountNode: , - priceNode: balVal ? ( - <> - - + priceNode: value ? ( + ) : ( — ), @@ -85,7 +98,7 @@ const Token = () => { label: name ?? chain, icon: networks[chain]?.icon, }, - } as AssetType + } acc.push(item) } @@ -102,54 +115,53 @@ const Token = () => { getIBCChannel, ]) - const [filtered, setFiltered] = useState(tokens) + const [filteredTokens, setFilteredTokens] = useState(tokens) - useEffect(() => { - console.log("🚀 ~ useEffect ~ asset:", asset) - if (asset) { - const filtered = tokens - .filter((t: AssetType) => { - if (selectedChain === "all") { + const filterTokens = useMemo(() => { + return (searchTerm: string, chain: ChainID | "all") => { + if (searchTerm) { + const filtered = tokens.filter((t: AssetType) => { + if (chain === "all") { return ( - t.symbol.toLowerCase().includes(asset.toLowerCase()) || - t.chain.label.toLowerCase().includes(asset.toLowerCase()) + t.symbol.toLowerCase().includes(searchTerm.toLowerCase()) || + t.chain.label.toLowerCase().includes(searchTerm.toLowerCase()) ) } return ( - (t.symbol.toLowerCase().includes(asset.toLowerCase()) || - t.chain.label.toLowerCase().includes(asset.toLowerCase())) && - t.tokenChain.toLowerCase() === selectedChain.toLowerCase() + (t.symbol.toLowerCase().includes(searchTerm.toLowerCase()) || + t.chain.label.toLowerCase().includes(searchTerm.toLowerCase())) && + t.tokenChain.toLowerCase() === chain.toLowerCase() ) - }) - .sort( - (a: AssetType, b: AssetType) => - parseInt(b.balVal) - parseInt(a.balVal) - ) - setFiltered(filtered) - } else if (selectedChain !== "all" && (asset === "" || !asset)) { - const filtered = tokens - .filter((t: AssetType) => { - return t.tokenChain.toLowerCase() === selectedChain.toLowerCase() - }) - .sort( - (a: AssetType, b: AssetType) => - parseInt(b.balVal) - parseInt(a.balVal) - ) - setFiltered(filtered) - } else { - setFiltered(tokens) + }) as AssetType[] + + return filtered + } else if (chain !== "all") { + const filtered = tokens.filter( + (t: AssetType) => t.tokenChain.toLowerCase() === chain.toLowerCase() + ) as AssetType[] + + return filtered + } else { + return tokens + } } + }, [tokens]) + + useEffect(() => { + const tokens = filterTokens(asset ?? "", selectedChain) + setFilteredTokens(tokens) + // eslint-disable-next-line }, [asset, selectedChain]) - const onClick = (asset: AssetType) => { + const handleTokenClick = (asset: AssetType) => { setValue("asset", asset.denom) setValue("chain", asset.tokenChain) setValue("assetInfo", asset) goToStep(4) } - const options = [ + const chainOptions = [ { label: "All Chains", value: "all" }, ...Object.values(networks).map((n) => ({ label: n.name, @@ -161,7 +173,8 @@ const Token = () => { goToStep(1) return null } - const recipientName = getWalletName(recipient) // wallet name or address if none found + + const recipientName = getWalletName(recipient) return ( @@ -179,7 +192,7 @@ const Token = () => { {...register("asset")} extra={ setSelectedChain(value)} variant="textDisplay" @@ -189,17 +202,19 @@ const Token = () => { /> - {filtered.length === 0 && } - {filtered.map((asset: AssetType, i: number) => ( - onClick(asset)} - /> - ))} + {filteredTokens.length === 0 && } + {filteredTokens + .sort((a, b) => b.value - a.value) + .map((asset: AssetType) => ( + handleTokenClick(asset)} + /> + ))} ) } -export default Token +export default TokenSelection diff --git a/src/pages/wallet/SendPage/types.tsx b/src/pages/wallet/SendPage/types.tsx index c6b823d3f..7e18ee875 100644 --- a/src/pages/wallet/SendPage/types.tsx +++ b/src/pages/wallet/SendPage/types.tsx @@ -2,7 +2,7 @@ import { TokenSingleChainListItemProps } from "@terra-money/station-ui" import { AccAddress } from "@terra-money/feather.js" export interface AssetType extends TokenSingleChainListItemProps { - balVal: string + value: number balance: string decimals: number amount: string From 45b54bfd378ca156d67295ab2e3dbaf59c1c105e Mon Sep 17 00:00:00 2001 From: plubber <51789398+ericHgorski@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:58:56 -0400 Subject: [PATCH 10/47] Update Token.tsx --- src/pages/wallet/SendPage/Token.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/wallet/SendPage/Token.tsx b/src/pages/wallet/SendPage/Token.tsx index 9fbed8916..d6f073389 100644 --- a/src/pages/wallet/SendPage/Token.tsx +++ b/src/pages/wallet/SendPage/Token.tsx @@ -76,7 +76,6 @@ const TokenSelection = () => { if ((isNative || channel) && supported) { const value = balance * price - console.log({ value, denom }) const senderAddress = addresses?.[chain] const item: AssetType = { ...a, From 6de8074e316d3c05bf3127f777712806a2e96962 Mon Sep 17 00:00:00 2001 From: plubber Date: Wed, 20 Mar 2024 12:39:24 -0400 Subject: [PATCH 11/47] fix: exclusion of unsupported assets and mismatch between asset chain and asset --- src/data/token.tsx | 49 +++++++++++++--------------------- src/pages/wallet/Asset.tsx | 2 -- src/pages/wallet/AssetPage.tsx | 6 ++++- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/data/token.tsx b/src/data/token.tsx index 4d1eec076..c03b64336 100644 --- a/src/data/token.tsx +++ b/src/data/token.tsx @@ -261,19 +261,8 @@ export const readIBCDenom = (item: IBCTokenItem): TokenItem => { } export const usePortfolioValue = () => { - const readNativeDenom = useNativeDenoms() - const coins = useBankBalance() - const { data: prices } = useExchangeRates() - - return coins?.reduce((acc, { amount, denom }) => { - const { token, decimals, symbol } = readNativeDenom(denom) - return ( - acc + - (parseInt(amount) * - (symbol?.endsWith("...") ? 0 : prices?.[token]?.price ?? 0)) / - 10 ** decimals - ) - }, 0) + const assets = useParsedAssetList() + return assets.reduce((acc, { totalValue }) => acc + totalValue, 0) } interface IBCDenom { @@ -321,17 +310,19 @@ interface ChainTokenItem { chainID: string chainName: string chainIcon: string + tokenIcon?: string + supported?: boolean + tokenPrice: number } export interface AssetItem { - //balance: string denom: string decimals: number totalBalance: string icon?: string symbol: string - price: number change: number + price: number tokenChainInfo: ChainTokenItem[] nativeChain: string id: string @@ -355,9 +346,7 @@ export const useParsedAssetList = () => { ibcDenomData?.baseDenom ?? denom, ibcDenomData?.chainID ?? chain ) - const nativeChain = chainID ?? ibcDenomData?.chainID ?? chain - const tokenID = `${nativeChain}*${token}` let tokenIcon, tokenPrice, tokenChange, tokenWhitelisted @@ -372,7 +361,8 @@ export const useParsedAssetList = () => { tokenChange = prices?.[token]?.change ?? 0 tokenWhitelisted = !( isNonWhitelisted || - ibcDenomData?.chainIDs.find((c: any) => !networks[c]) + (ibcDenomData?.chainIDs && + ibcDenomData.chainIDs.some((c: string) => !networks[c])) ) } @@ -384,7 +374,8 @@ export const useParsedAssetList = () => { : ibcDenomData?.baseDenom === token const { name: chainName, icon: chainIcon } = networks[chain] || {} - const chainTokenItem = { + + const chainTokenItem: ChainTokenItem = { denom, id: `${token}*${nativeChain}*${chain}`, decimals, @@ -398,20 +389,15 @@ export const useParsedAssetList = () => { } if (acc[tokenID]) { - if (chainTokenItem.supported) { - acc[tokenID].totalBalance = `${ - parseInt(acc[tokenID].totalBalance) + parseInt(amount) - }` - acc[tokenID].totalValue = acc[tokenID].totalValue += - toInput(amount, decimals) * tokenPrice - } + acc[tokenID].totalBalance = `${ + parseInt(acc[tokenID].totalBalance) + parseInt(amount) + }` + acc[tokenID].totalValue += toInput(amount, decimals) * tokenPrice acc[tokenID].tokenChainInfo.push(chainTokenItem) return acc } else { - const totalBalance = supported ? amount : "0" - const totalValue = supported - ? tokenPrice * toInput(amount, decimals) - : 0 + const totalBalance = amount + const totalValue = tokenPrice * toInput(amount, decimals) const result: Record = { ...acc, @@ -426,7 +412,7 @@ export const useParsedAssetList = () => { price: tokenPrice, change: tokenChange, tokenChainInfo: [chainTokenItem], - nativeChain: nativeChain, + nativeChain, whitelisted: tokenWhitelisted, }, } @@ -436,5 +422,6 @@ export const useParsedAssetList = () => { }, {} as Record) ?? {} ) }, [coins, readNativeDenom, unknownIBCDenoms, prices, networks]) + return Object.values(list) } diff --git a/src/pages/wallet/Asset.tsx b/src/pages/wallet/Asset.tsx index e69756393..7a47db16e 100644 --- a/src/pages/wallet/Asset.tsx +++ b/src/pages/wallet/Asset.tsx @@ -105,9 +105,7 @@ const Asset = (props: Props) => { {...props} amount={price * parseInt(totalBalance ?? "0")} decimals={decimals} - fixed={2} denom="" - token="" currency /> ) : ( diff --git a/src/pages/wallet/AssetPage.tsx b/src/pages/wallet/AssetPage.tsx index 89498eed2..7c8b24d01 100644 --- a/src/pages/wallet/AssetPage.tsx +++ b/src/pages/wallet/AssetPage.tsx @@ -99,7 +99,11 @@ const AssetPage = () => { const AssetPageHeader = () => { const totalBalance = useMemo( - () => supportedAssets.reduce((acc, b) => acc + parseInt(b.amount), 0), + () => + [...unsupportedAssets, ...supportedAssets].reduce( + (acc, b) => acc + parseInt(b.amount), + 0 + ), [] ) From 02d320d080a54b64c3e67c83a477d99777bb974a Mon Sep 17 00:00:00 2001 From: plubber Date: Wed, 20 Mar 2024 15:16:43 -0400 Subject: [PATCH 12/47] abstract fee estimation and double max click logic --- src/data/queries/swap/skip.ts | 2 +- src/data/queries/tx.ts | 113 ++++++++++++++++++- src/txs/Tx.tsx | 92 +++------------ src/txs/swap/SwapConfirm.tsx | 33 +----- src/txs/swap/SwapContext.tsx | 45 +++++++- src/txs/swap/{SwapSetup.tsx => SwapForm.tsx} | 59 ++++++++-- src/txs/swap/SwapTx.tsx | 2 +- 7 files changed, 220 insertions(+), 126 deletions(-) rename src/txs/swap/{SwapSetup.tsx => SwapForm.tsx} (79%) diff --git a/src/data/queries/swap/skip.ts b/src/data/queries/swap/skip.ts index ca73cf45d..06ded2ca5 100644 --- a/src/data/queries/swap/skip.ts +++ b/src/data/queries/swap/skip.ts @@ -28,7 +28,7 @@ export const skipApi = { }, }) - const tokens = Object.entries(result.data.chain_to_assets_map).reduce( + const tokens = Object.entries(result.data?.chain_to_assets_map).reduce( // @ts-ignore (acc, [chainId, { assets }]) => { const transformedAssets = assets.map((asset: SkipTokenResponse) => ({ diff --git a/src/data/queries/tx.ts b/src/data/queries/tx.ts index 3750c5727..9efe0ed38 100644 --- a/src/data/queries/tx.ts +++ b/src/data/queries/tx.ts @@ -1,11 +1,19 @@ import { QueryKey, useQuery, useQueryClient } from "react-query" import { useInterchainLCDClient } from "./lcdClient" -import { atom, useSetRecoilState } from "recoil" +import { atom, useRecoilValue, useSetRecoilState } from "recoil" import { CARBON_API, OSMOSIS_GAS_ENDPOINT } from "config/constants" import { useNetworks } from "app/InitNetworks" -import { RefetchOptions } from "../query" -import { queryKey } from "../query" import axios from "axios" +import { useCallback, useMemo } from "react" +import BigNumber from "bignumber.js" +import { isNil } from "ramda" +import { useInterchainAddresses } from "auth/hooks/useAddress" +import { useNetwork } from "data/wallet" +import { useIsWalletEmpty } from "data/queries/bank" +import { queryKey, RefetchOptions } from "data/query" +import { useAuth } from "auth" +import { getLocalSetting, SettingKey } from "utils/localStorage" +import { CreateTxOptions, Msg } from "@terra-money/feather.js" interface LatestTx { txhash: string @@ -104,3 +112,102 @@ export const useCarbonFees = () => { { ...RefetchOptions.INFINITY } ) } + +interface UseGasEstimationProps { + chain: string + estimationTxValues: any + createTx: (values: any) => CreateTxOptions | undefined + txGasAdjustment?: number + gasDenom?: string +} + +export const useGasEstimation = ({ + chain, + estimationTxValues, + createTx, + txGasAdjustment, + gasDenom, +}: UseGasEstimationProps) => { + const lcd = useInterchainLCDClient() + const networks = useNetwork() + const { wallet } = useAuth() + const addresses = useInterchainAddresses() + const isWalletEmpty = useIsWalletEmpty() + const { data: carbonFees } = useCarbonFees() + const { data: osmosisGas } = useOsmosisGas(!chain?.startsWith("osmosis-")) + const isBroadcasting = useRecoilValue(isBroadcastingState) + + const simulationTx = estimationTxValues && createTx(estimationTxValues) + const gasAdjustmentSetting = SettingKey.GasAdjustment + const gasAdjustment = + networks[chain]?.gasAdjustment * + getLocalSetting(gasAdjustmentSetting) + + const key = { + address: addresses?.[chain], + gasAdjustment: gasAdjustment * (txGasAdjustment ?? 1), + estimationTxValues, + msgs: simulationTx?.msgs.map( + (msg: Msg) => msg.toData(networks[chain].isClassic)["@type"] + ), + } + + const carbonFee = useMemo(() => { + const fee = + carbonFees?.costs[key.msgs?.[0] ?? ""] ?? carbonFees?.costs["default_fee"] + return Number(fee) + }, [carbonFees, key.msgs]) + + const { data: estimatedGas, ...estimatedGasState } = useQuery( + [queryKey.tx.create, key, isWalletEmpty, carbonFee], + async () => { + if (!key.address || isWalletEmpty) return 0 + if (!wallet) return 0 + if (!simulationTx || !simulationTx.msgs.length) return 0 + try { + if (chain.startsWith("carbon-")) return carbonFee + const unsignedTx = await lcd.tx.create([{ address: key.address }], { + ...simulationTx, + feeDenoms: [gasDenom], + }) + console.log( + "unsignedTx.auth_info.fee.gas_limit", + unsignedTx.auth_info.fee.gas_limit + ) + return Math.ceil(unsignedTx.auth_info.fee.gas_limit) + } catch (error) { + console.error(error) + return 200_000 + } + }, + { + ...RefetchOptions.INFINITY, + retry: 3, + retryDelay: 1000, + refetchOnWindowFocus: false, + enabled: !isBroadcasting, + } + ) + + const getGasAmount = useCallback( + (denom: CoinDenom) => { + const gasPrice = chain?.startsWith("carbon-") + ? carbonFees?.prices[denom] + : chain?.startsWith("osmosis-") + ? (osmosisGas || 0.0025) * 10 + : networks[chain]?.gasPrices[denom] + if (isNil(estimatedGas) || !gasPrice) return "0" + return new BigNumber(estimatedGas) + .times(gasPrice) + .integerValue(BigNumber.ROUND_CEIL) + .toString() + }, + [chain, carbonFees?.prices, osmosisGas, networks, estimatedGas] + ) + + return { + estimatedGas, + estimatedGasState, + getGasAmount, + } +} diff --git a/src/txs/Tx.tsx b/src/txs/Tx.tsx index 03a8520a4..a326632b5 100644 --- a/src/txs/Tx.tsx +++ b/src/txs/Tx.tsx @@ -1,11 +1,10 @@ -import { ReactNode } from "react" -import { useCallback, useEffect, useMemo, useState } from "react" +import { ReactNode, useMemo } from "react" +import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" -import { QueryKey, useQuery, useQueryClient } from "react-query" +import { QueryKey, useQueryClient } from "react-query" import { useRecoilValue, useSetRecoilState } from "recoil" import classNames from "classnames" import BigNumber from "bignumber.js" -import { isNil } from "ramda" import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet" import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline" @@ -15,8 +14,7 @@ import { Fee } from "@terra-money/feather.js" import { has } from "utils/num" import { getErrorMessage } from "utils/error" -import { getLocalSetting, SettingKey } from "utils/localStorage" -import { combineState, RefetchOptions } from "data/query" +import { combineState } from "data/query" import { queryKey } from "data/query" import { useNetwork } from "data/wallet" import { isBroadcastingState, latestTxState } from "data/queries/tx" @@ -30,10 +28,8 @@ import useToPostMultisigTx from "pages/multisig/utils/useToPostMultisigTx" import { isWallet, useAuth } from "auth" import { toInput, CoinInput, calcTaxes } from "./utils" import styles from "./Tx.module.scss" -import { useInterchainLCDClient } from "data/queries/lcdClient" import { useInterchainAddresses } from "auth/hooks/useAddress" import { getShouldTax, useTaxCap, useTaxRate } from "data/queries/treasury" -import { useCarbonFees, useOsmosisGas } from "data/queries/tx" import { Banner, Button, @@ -50,6 +46,7 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle" import { usePendingIbcTx } from "./useIbcTxs" import { useNavigate } from "react-router-dom" import { useAddCachedTx } from "data/queries/activity" +import { useGasEstimation } from "data/queries/tx" const cx = classNames.bind(styles) @@ -121,16 +118,13 @@ function Tx(props: Props) { /* context */ const { t } = useTranslation() - const lcd = useInterchainLCDClient() const networks = useNetwork() const { wallet, validatePassword, ...auth } = useAuth() const addresses = useInterchainAddresses() const isWalletEmpty = useIsWalletEmpty() const setLatestTx = useSetRecoilState(latestTxState) const isBroadcasting = useRecoilValue(isBroadcastingState) - const { data: carbonFees } = useCarbonFees() const { addTx: trackIbcTx } = usePendingIbcTx() - const { data: osmosisGas } = useOsmosisGas(!chain?.startsWith("osmosis-")) /* taxes */ const isClassic = networks[chain]?.isClassic @@ -146,71 +140,14 @@ function Tx(props: Props) { ) : undefined - /* simulation: estimate gas */ - const simulationTx = estimationTxValues && createTx(estimationTxValues) - const gasAdjustmentSetting = SettingKey.GasAdjustment - const gasAdjustment = - networks[chain]?.gasAdjustment * - getLocalSetting(gasAdjustmentSetting) - - const key = { - address: addresses?.[chain], - //network: networks, - gasAdjustment: gasAdjustment * (txGasAdjustment ?? 1), + /* gas estimation */ + const { estimatedGas, estimatedGasState, getGasAmount } = useGasEstimation({ + chain, estimationTxValues, - msgs: simulationTx?.msgs.map((msg) => msg.toData(isClassic)["@type"]), - } - - const carbonFee = useMemo(() => { - const fee = - carbonFees?.costs[key.msgs?.[0] ?? ""] ?? carbonFees?.costs["default_fee"] - return Number(fee) - }, [carbonFees, key.msgs]) - - const { data: estimatedGas, ...estimatedGasState } = useQuery( - [queryKey.tx.create, key, isWalletEmpty, carbonFee], - async () => { - if (!key.address || isWalletEmpty) return 0 - if (!wallet) return 0 - if (!simulationTx || !simulationTx.msgs.length) return 0 - try { - if (chain.startsWith("carbon-")) return carbonFee - const unsignedTx = await lcd.tx.create([{ address: key.address }], { - ...simulationTx, - feeDenoms: [gasDenom], - }) - return Math.ceil(unsignedTx.auth_info.fee.gas_limit) - } catch (error) { - console.error(error) - return 200_000 - } - }, - { - ...RefetchOptions.INFINITY, - // To handle sequence mismatch - retry: 3, - retryDelay: 1000, - // Because the focus occurs once when posting back from the extension - refetchOnWindowFocus: false, - enabled: !isBroadcasting, - } - ) - - const getGasAmount = useCallback( - (denom: CoinDenom) => { - const gasPrice = chain?.startsWith("carbon-") - ? carbonFees?.prices[denom] - : chain?.startsWith("osmosis-") - ? (osmosisGas || 0.0025) * 10 - : networks[chain]?.gasPrices[denom] - if (isNil(estimatedGas) || !gasPrice) return "0" - return new BigNumber(estimatedGas) - .times(gasPrice) - .integerValue(BigNumber.ROUND_CEIL) - .toString() - }, - [chain, carbonFees?.prices, osmosisGas, networks, estimatedGas] - ) + createTx, + txGasAdjustment, + gasDenom, + }) const gasAmount = getGasAmount(gasDenom) const gasFee = { amount: gasAmount, denom: gasDenom, decimals } @@ -248,13 +185,10 @@ function Tx(props: Props) { useEffect(() => { if (process.env.NODE_ENV === "development" && failed) { console.groupCollapsed("Fee estimation failed") - console.info( - simulationTx?.msgs.map((msg) => msg.toData(networks[chain].isClassic)) - ) console.info(failed) console.groupEnd() } - }, [failed, simulationTx, networks, chain]) + }, [failed]) /* submit */ const passwordRequired = isWallet.single(wallet) diff --git a/src/txs/swap/SwapConfirm.tsx b/src/txs/swap/SwapConfirm.tsx index caf5d1ea0..315bf9d52 100644 --- a/src/txs/swap/SwapConfirm.tsx +++ b/src/txs/swap/SwapConfirm.tsx @@ -1,9 +1,6 @@ -import { useMemo } from "react" import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" -import { MsgTransfer, Coin, MsgExecuteContract } from "@terra-money/feather.js" import { toAmount } from "@terra-money/terra-utils" -import { Coins } from "@terra-money/feather.js" import { SectionHeader, Grid, Form } from "@terra-money/station-ui" import SwapTimeline from "./components/SwapTimeline" import Errors from "./components/ConfirmErrors" @@ -26,12 +23,11 @@ export const validateAssets = ( const Confirm = () => { const { t } = useTranslation() - const { form } = useSwap() + const { form, createTx, estimationTxValues } = useSwap() const navigate = useNavigate() const { watch, handleSubmit, getValues } = form const { offerAsset, offerInput, msgs: swapMsgs } = watch() const amount = toAmount(offerInput, { decimals: offerAsset.decimals }) - const estimationTxValues = useMemo(() => getValues(), [getValues]) const isLedger = useIsLedger() if (!swapMsgs) { @@ -39,33 +35,6 @@ const Confirm = () => { return null } - const createTx = ({ offerAsset }: SwapState) => { - const msg = JSON.parse(swapMsgs?.[0]?.msg) - let msgs - - if (msg.source_channel) { - msgs = new MsgTransfer( - "transfer", - msg.source_channel, - new Coin(msg.token?.denom ?? "", msg.token?.amount), - msg.sender, - msg.receiver, - undefined, - (Date.now() + 120 * 1000) * 1e6, - msg.memo - ) - } else { - // for native swaps (osmo to osmo) - msgs = new MsgExecuteContract( - msg.sender, - msg.contract, - msg.msg, - Coins.fromAmino(msg.funds) - ) - } - return { msgs: [msgs] ?? [], chainID: offerAsset.chainId } - } - const tx = { token: offerAsset.denom, decimals: offerAsset.decimals, diff --git a/src/txs/swap/SwapContext.tsx b/src/txs/swap/SwapContext.tsx index efb36c748..63e2401bc 100644 --- a/src/txs/swap/SwapContext.tsx +++ b/src/txs/swap/SwapContext.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, useEffect } from "react" +import { PropsWithChildren, useCallback, useEffect, useMemo } from "react" import createContext from "utils/createContext" import { combineState } from "data/query" import { Fetching } from "components/feedback" @@ -19,6 +19,12 @@ import { import { UseFormReturn, useForm } from "react-hook-form" import { useSwapSlippage } from "utils/localStorage" import SwapLoadingPage from "./components/SwapLoadingPage" +import { + Coin, + Coins, + MsgExecuteContract, + MsgTransfer, +} from "@terra-money/feather.js" interface Swap { tokens: SwapAssetExtra[] @@ -28,6 +34,8 @@ interface Swap { form: UseFormReturn slippage: string changeSlippage: (slippage: string) => void + createTx: (swap: SwapState) => { msgs: any[]; chainID: string } + estimationTxValues: SwapState } export const [useSwap, SwapProvider] = createContext("useSwap") @@ -50,7 +58,38 @@ const SwapContext = ({ children }: PropsWithChildren<{}>) => { const state = combineState(...swap) const form = useForm({ mode: "onChange" }) - const { askAsset } = form.watch() + + const { askAsset, offerAsset, msgs: swapMsgs } = form.watch() + + const estimationTxValues = useMemo(() => form.getValues(), [form.getValues]) + + const createTx = useCallback(() => { + if (!swapMsgs?.length) return { msgs: [], chainID: "" } + const msg = JSON.parse(swapMsgs?.[0]?.msg) + let msgs + + if (msg.source_channel) { + msgs = new MsgTransfer( + "transfer", + msg.source_channel, + new Coin(msg.token?.denom ?? "", msg.token?.amount), + msg.sender, + msg.receiver, + undefined, + (Date.now() + 120 * 1000) * 1e6, + msg.memo + ) + } else { + // for native swaps (osmo to osmo) + msgs = new MsgExecuteContract( + msg.sender, + msg.contract, + msg.msg, + Coins.fromAmino(msg.funds) + ) + } + return { msgs: [msgs] ?? [], chainID: offerAsset.chainId } + }, [swapMsgs, offerAsset]) useEffect(() => { if (!askAsset) { @@ -78,7 +117,9 @@ const SwapContext = ({ children }: PropsWithChildren<{}>) => { form, getMsgs, slippage, + createTx, changeSlippage, + estimationTxValues, } return {children} } diff --git a/src/txs/swap/SwapSetup.tsx b/src/txs/swap/SwapForm.tsx similarity index 79% rename from src/txs/swap/SwapSetup.tsx rename to src/txs/swap/SwapForm.tsx index 019a1423f..c9958b317 100644 --- a/src/txs/swap/SwapSetup.tsx +++ b/src/txs/swap/SwapForm.tsx @@ -23,6 +23,9 @@ import { Grid, } from "@terra-money/station-ui" import styles from "./Swap.module.scss" +import { useNetworks } from "app/InitNetworks" +import { useNetwork } from "data/wallet" +import { useGasEstimation } from "data/queries/tx" enum SwapAssetType { ASK = "askAsset", @@ -31,8 +34,16 @@ enum SwapAssetType { const SwapForm = () => { // Hooks - const { tokens, getTokensWithBal, getBestRoute, form, getMsgs, slippage } = - useSwap() + const { + tokens, + getTokensWithBal, + getBestRoute, + form, + getMsgs, + slippage, + createTx, + estimationTxValues, + } = useSwap() const { t } = useTranslation() const navigate = useNavigate() const { state: denom } = useLocation() @@ -44,6 +55,19 @@ const SwapForm = () => { const { offerAsset, askAsset, offerInput, route } = watch() const [error, setError] = useState() const [warning, setWarning] = useState() + const [maxClicked, setMaxClicked] = useState(0) + const network = useNetwork() + const { estimatedGas, getGasAmount } = useGasEstimation({ + chain: offerAsset.chainId, + createTx, + estimationTxValues, + gasDenom: network[offerAsset.chainId]?.baseAsset, + }) + + const estimatedGasAmount = useMemo( + () => getGasAmount(offerAsset.denom), + [estimatedGas, getGasAmount] + ) const offerAssetAmount = useMemo( () => toAmount(offerInput, { decimals: offerAsset.decimals }), @@ -72,7 +96,6 @@ const SwapForm = () => { useEffect(() => { setError(undefined) - setWarning(undefined) setValue("route", undefined) // for loading purposees if (insufficientFunds) setError("Insufficient funds") @@ -128,14 +151,34 @@ const SwapForm = () => { }, [route, askAsset]) const sameAssets = !validateAssets({ offerAsset, askAsset }) - const disabled = !(offerInput && !error && route) + const disabled = !(offerInput && !error && route && estimatedGasAmount) const loading = !!(has(offerInput) && !error && !route) + useEffect(() => { + setMaxClicked(0) + }, [offerAsset.denom]) + const handleMaxClick = () => { - setValue( - "offerInput", - toInput(offerAsset.balance, offerAsset.decimals).toString() - ) + setMaxClicked(maxClicked + 1) + const maxClickedIsEven = maxClicked > 0 && maxClicked % 2 === 0 + + let maxAmount = parseInt(offerAsset.balance) + + if (offerAsset.denom === network[offerAsset.chainId]?.baseAsset) { + const balMinusGasFee = + parseInt(offerAsset.balance) - parseInt(estimatedGasAmount) + const balMinusTwoGasFee = + parseInt(offerAsset.balance) - 2 * parseInt(estimatedGasAmount) + maxAmount = maxClickedIsEven ? balMinusGasFee : balMinusTwoGasFee + setWarning( + maxClickedIsEven + ? t( + "Executing this transaction will leave you no gas for future transactions" + ) + : undefined + ) + } + setValue("offerInput", toInput(maxAmount, offerAsset.decimals).toString()) } return ( diff --git a/src/txs/swap/SwapTx.tsx b/src/txs/swap/SwapTx.tsx index 15ae30ed1..6960612e0 100644 --- a/src/txs/swap/SwapTx.tsx +++ b/src/txs/swap/SwapTx.tsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next" import { Routes, Route, useLocation } from "react-router-dom" -import Setup from "./SwapSetup" +import Setup from "./SwapForm" import Confirm from "./SwapConfirm" import SwapContext from "./SwapContext" import SwapSettings from "./SwapSettingsPage" From d903512c7ffd379a4efe340622ad846feb5e5a6e Mon Sep 17 00:00:00 2001 From: plubber Date: Wed, 20 Mar 2024 15:40:17 -0400 Subject: [PATCH 13/47] implement max click logic in sends --- src/pages/wallet/SendPage/Confirm.tsx | 104 +++------------------ src/pages/wallet/SendPage/SendContext.tsx | 108 +++++++++++++++++++++- src/pages/wallet/SendPage/Submit.tsx | 58 ++++++++++-- src/txs/swap/SwapForm.tsx | 1 - 4 files changed, 170 insertions(+), 101 deletions(-) diff --git a/src/pages/wallet/SendPage/Confirm.tsx b/src/pages/wallet/SendPage/Confirm.tsx index 0ea314dc4..a606ea8e4 100644 --- a/src/pages/wallet/SendPage/Confirm.tsx +++ b/src/pages/wallet/SendPage/Confirm.tsx @@ -14,29 +14,14 @@ import { useCurrency } from "data/settings/Currency" import { toInput } from "txs/utils" import { useTranslation } from "react-i18next" import { toAmount } from "@terra-money/terra-utils" -import { TxValues } from "./types" -import { - MsgExecuteContract, - MsgTransfer, - MsgSend, -} from "@terra-money/feather.js" -import { AccAddress } from "@terra-money/feather.js" import { useRecentRecipients } from "utils/localStorage" import Tx from "txs/Tx" -import { useCallback, useMemo, useEffect, useState, ReactNode } from "react" -import { Coin } from "@terra-money/feather.js" +import { useMemo, useEffect, useState, ReactNode } from "react" import { queryKey } from "data/query" -import { useInterchainAddresses } from "auth/hooks/useAddress" import { CoinInput } from "txs/utils" import style from "./Send.module.scss" import { useIsLedger } from "utils/ledger" -enum TxType { - SEND = "Send", - EXECUTE = "Execute Contract", - TRANSFER = "Transfer", -} - interface InfoProps { render: ( descriptions?: { @@ -50,91 +35,28 @@ interface InfoProps { } const Confirm = () => { - const { form, networks, getWalletName, getICSContract, goToStep } = useSend() + const { + form, + networks, + getWalletName, + goToStep, + txType, + createTx, + estimationTxValues, + } = useSend() const { t } = useTranslation() const currency = useCurrency() const { handleSubmit, setValue } = form const { addRecipient } = useRecentRecipients() - const addresses = useInterchainAddresses() const [error, setError] = useState(null) const { input, assetInfo, destination, recipient, chain, memo } = form.watch() const isLedger = useIsLedger() /* fee */ const coins = useMemo(() => [{ input, denom: "" }] as CoinInput[], [input]) - const estimationTxValues = useMemo(() => { - return { - address: addresses?.[chain ?? "phoenix-1"], - input: toInput(1, assetInfo?.decimals), - } - }, [addresses, assetInfo, chain]) - - const amount = useMemo(() => { - return toAmount(input, { decimals: assetInfo?.decimals }) - }, [input, assetInfo]) - - const txType = useMemo(() => { - if (!assetInfo?.denom) return null - return AccAddress.validate(assetInfo.denom) - ? TxType.EXECUTE - : destination === chain - ? TxType.SEND - : TxType.TRANSFER - }, [assetInfo, destination, chain]) - - const createTx = useCallback( - ({ memo }: TxValues) => { - const amount = toAmount(input, { decimals: assetInfo?.decimals }) - const { senderAddress, denom, channel } = assetInfo ?? {} - - if (!(recipient && AccAddress.validate(recipient))) return - if (!(chain && destination && denom && amount && senderAddress)) return - - const execute_msg = { - transfer: { recipient, amount }, - } - - let msgs - - if (destination === chain) { - msgs = - txType === TxType.EXECUTE - ? new MsgExecuteContract(senderAddress, denom, execute_msg) - : new MsgSend(senderAddress, recipient, amount + denom) - } else { - if (!channel) throw new Error("No IBC channel found") - msgs = - txType === TxType.EXECUTE - ? new MsgExecuteContract(senderAddress, denom, { - send: { - contract: getICSContract({ - from: chain, - to: destination, - tokenAddress: denom, - }), - amount: amount, - msg: Buffer.from( - JSON.stringify({ - channel, - remote_address: recipient, - }) - ).toString("base64"), - }, - }) - : new MsgTransfer( - "transfer", - channel, - new Coin(denom ?? "", amount), - senderAddress, - recipient, - undefined, - (Date.now() + 120 * 1000) * 1e6, - undefined - ) - } - return { msgs: [msgs], memo, chainID: chain } - }, - [assetInfo, recipient, chain, getICSContract, destination, input, txType] + const amount = useMemo( + () => toAmount(input, { decimals: assetInfo?.decimals }), + [input, assetInfo] ) if (!(input && destination && chain && recipient)) { diff --git a/src/pages/wallet/SendPage/SendContext.tsx b/src/pages/wallet/SendPage/SendContext.tsx index 120bf6a0f..1eaa659c3 100644 --- a/src/pages/wallet/SendPage/SendContext.tsx +++ b/src/pages/wallet/SendPage/SendContext.tsx @@ -1,20 +1,40 @@ -import { PropsWithChildren } from "react" +import { PropsWithChildren, useCallback, useMemo } from "react" import createContext from "utils/createContext" import { UseFormReturn, useForm } from "react-hook-form" import { useNavigate } from "react-router-dom" -import { useGetWalletName } from "auth/hooks/useAddress" +import { useGetWalletName, useInterchainAddresses } from "auth/hooks/useAddress" import { CoinBalance, useBankBalance } from "data/queries/bank" import { useParsedAssetList } from "data/token" import { useIBCChannels } from "data/queries/chains" import { TxValues } from "./types" import { IInterchainNetworks, useNetwork } from "data/wallet" +import { toAmount } from "@terra-money/terra-utils" +import { + AccAddress, + Coin, + Msg, + MsgExecuteContract, + MsgSend, + MsgTransfer, +} from "@terra-money/feather.js" +import { toInput } from "txs/utils" +enum TxType { + SEND = "Send", + EXECUTE = "Execute Contract", + TRANSFER = "Transfer", +} interface Send { form: UseFormReturn + txType: TxType | null goToStep: (step: number) => void + estimationTxValues: { address: string | undefined; input: number } getWalletName: (address: string) => string balances: CoinBalance[] assetList: any[] + createTx: () => + | { msgs: Msg[]; memo: string | undefined; chainID: string } + | undefined getIBCChannel: ({ from, to, @@ -39,7 +59,8 @@ interface Send { networks: IInterchainNetworks } -export const [useSend, SendProvider] = createContext("useSwap") +export const [useSend, SendProvider] = createContext("useSend") + const SendContext = ({ children }: PropsWithChildren<{}>) => { const navigate = useNavigate() const form = useForm({ mode: "onChange" }) @@ -47,9 +68,87 @@ const SendContext = ({ children }: PropsWithChildren<{}>) => { const getWalletName = useGetWalletName() const balances = useBankBalance() const assetList = useParsedAssetList() + const addresses = useInterchainAddresses() const networks = useNetwork() const { getIBCChannel, getICSContract } = useIBCChannels() + const { input, assetInfo, chain, recipient, destination, memo } = form.watch() + + const txType = useMemo(() => { + if (!assetInfo?.denom) return null + return AccAddress.validate(assetInfo.denom) + ? TxType.EXECUTE + : destination === chain + ? TxType.SEND + : TxType.TRANSFER + }, [assetInfo, destination, chain]) + + const createTx = useCallback(() => { + const amount = toAmount(input, { decimals: assetInfo?.decimals }) + const { senderAddress, denom, channel } = assetInfo ?? {} + + if (!(recipient && AccAddress.validate(recipient))) return + if (!(chain && destination && denom && amount && senderAddress)) return + + const execute_msg = { + transfer: { recipient, amount }, + } + + let msgs + + if (destination === chain) { + msgs = + txType === TxType.EXECUTE + ? new MsgExecuteContract(senderAddress, denom, execute_msg) + : new MsgSend(senderAddress, recipient, amount + denom) + } else { + if (!channel) throw new Error("No IBC channel found") + msgs = + txType === TxType.EXECUTE + ? new MsgExecuteContract(senderAddress, denom, { + send: { + contract: getICSContract({ + from: chain, + to: destination, + tokenAddress: denom, + }), + amount: amount, + msg: Buffer.from( + JSON.stringify({ + channel, + remote_address: recipient, + }) + ).toString("base64"), + }, + }) + : new MsgTransfer( + "transfer", + channel, + new Coin(denom ?? "", amount), + senderAddress, + recipient, + undefined, + (Date.now() + 120 * 1000) * 1e6, + undefined + ) + } + return { msgs: [msgs], memo, chainID: chain } + }, [ + assetInfo, + recipient, + chain, + memo, + getICSContract, + destination, + input, + txType, + ]) + const estimationTxValues = useMemo(() => { + return { + address: addresses?.[chain ?? "phoenix-1"], + input: toInput(1, assetInfo?.decimals), + } + }, [addresses, assetInfo, chain]) const render = () => { const value = { @@ -61,6 +160,9 @@ const SendContext = ({ children }: PropsWithChildren<{}>) => { getIBCChannel, getICSContract, networks, + createTx, + txType, + estimationTxValues, } return {children} } diff --git a/src/pages/wallet/SendPage/Submit.tsx b/src/pages/wallet/SendPage/Submit.tsx index 58f9ea0c5..2af122929 100644 --- a/src/pages/wallet/SendPage/Submit.tsx +++ b/src/pages/wallet/SendPage/Submit.tsx @@ -1,5 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { useEffect, useMemo } from "react" +import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { truncate } from "@terra-money/terra-utils" import validate from "txs/validate" @@ -17,9 +17,18 @@ import { Checkbox, FlexColumn, } from "@terra-money/station-ui" +import { useNetwork } from "data/wallet" +import { useGasEstimation } from "data/queries/tx" const Submit = () => { - const { form, getWalletName, goToStep, networks } = useSend() + const { + form, + getWalletName, + goToStep, + networks, + createTx, + estimationTxValues, + } = useSend() const { register, formState, @@ -45,6 +54,21 @@ const Submit = () => { ) const currency = useCurrency() const { t } = useTranslation() + const network = useNetwork() + const [maxClicked, setMaxClicked] = useState(0) + const [showFeeWarning, setShowFeeWarning] = useState(false) + + const { estimatedGas, getGasAmount } = useGasEstimation({ + chain: assetInfo?.tokenChain ?? "", + createTx, + estimationTxValues, + gasDenom: network[assetInfo?.tokenChain ?? ""]?.baseAsset, + }) + + const estimatedGasAmount = useMemo( + () => getGasAmount(assetInfo?.denom ?? ""), + [estimatedGas, getGasAmount] + ) const originChain = useMemo(() => ibcData?.chainIDs?.[0], [ibcData]) @@ -90,10 +114,22 @@ const Submit = () => { const { balance, decimals, price, tokenImg, symbol } = assetInfo - const handleMax = () => { - setValue("input", toInput(balance, decimals, 5)) // 5 decimal place round-down for SendAmount component + const handleMaxClick = () => { + const maxClickCount = maxClicked + 1 + setMaxClicked(maxClickCount) + const maxClickedIsEven = maxClickCount > 0 && maxClickCount % 2 === 0 + + let maxAmount = parseInt(balance) + + if (assetInfo.denom === network[assetInfo.tokenChain]?.baseAsset) { + const balMinusGasFee = maxAmount - parseInt(estimatedGasAmount) + const balMinusTwoGasFee = maxAmount - 2 * parseInt(estimatedGasAmount) + maxAmount = maxClickedIsEven ? balMinusGasFee : balMinusTwoGasFee + setShowFeeWarning(maxClickedIsEven) + } + setValue("input", toInput(maxAmount, assetInfo.decimals, 5)) if (price) { - setValue("currencyAmount", toInput(Number(balance) * price, decimals)) + setValue("currencyAmount", toInput(Number(balance) * price, decimals, 4)) } trigger("input") } @@ -112,7 +148,7 @@ const Submit = () => { /> { setValue("asset", undefined) @@ -144,6 +180,16 @@ const Submit = () => { })} /> + {showFeeWarning && ( + + )} {showIBCWarning && ( <> Date: Thu, 21 Mar 2024 00:24:08 -0500 Subject: [PATCH 14/47] added a new input that turns into a dropdown when focused on in the Send flow --- package-lock.json | 22 +- package.json | 4 +- src/pages/wallet/SendPage/Address.module.scss | 52 ++++ src/pages/wallet/SendPage/Address.tsx | 238 +++++++++++++++--- src/pages/wallet/SendPage/Token.tsx | 1 - 5 files changed, 263 insertions(+), 54 deletions(-) create mode 100644 src/pages/wallet/SendPage/Address.module.scss diff --git a/package-lock.json b/package-lock.json index 29b073509..b4a7e3d6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "@0xsquid/sdk": "^1.14.11", "@amplitude/analytics-browser": "^2.1.2", "@ledgerhq/hw-transport-web-ble": "^6.27.1", - "@mui/icons-material": "^5.8.0", + "@mui/icons-material": "^5.15.14", "@mui/material": "^5.9.1", "@sentry/react": "^7.53.1", "@terra-money/feather.js": "1.0.11", @@ -18,7 +18,7 @@ "@terra-money/log-finder-ruleset": "^3.0.3", "@terra-money/msg-reader": "^3.0.1", "@terra-money/station-connector": "^1.0.17-beta.6", - "@terra-money/station-ui": "^1.0.6", + "@terra-money/station-ui": "file:../station-ui/terra-money-station-ui-1.0.10.tgz", "@terra-money/terra-utils": "^1.2.0-beta.8", "@terra-money/terra.js": "^3.1.9", "@terra-money/terra.proto": "^2.0.0", @@ -2203,8 +2203,9 @@ "license": "MIT" }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "license": "MIT", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -5558,10 +5559,11 @@ } }, "node_modules/@mui/icons-material": { - "version": "5.14.19", - "license": "MIT", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz", + "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==", "dependencies": { - "@babel/runtime": "^7.23.4" + "@babel/runtime": "^7.23.9" }, "engines": { "node": ">=12.0.0" @@ -8069,9 +8071,9 @@ } }, "node_modules/@terra-money/station-ui": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@terra-money/station-ui/-/station-ui-1.0.6.tgz", - "integrity": "sha512-jhWB5+YkwolslytgMxatsloUpJrdXMj3NRyQb/mlx9szlmwyeQq+Qq3TDpmpW3np1G6VVm3qtFLusflJfHQIJw==", + "version": "1.0.10", + "resolved": "file:../station-ui/terra-money-station-ui-1.0.10.tgz", + "integrity": "sha512-+cLbCwvTvhONY2vh6AZeeMyXgyfbsXyjXWCt3jAUDHbj1n3386x63eNwJexGbOTOZ15igmY1Do3DuibZOFcWfQ==", "dependencies": { "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.1", diff --git a/package.json b/package.json index 9bbd98ccf..acb36b0aa 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@0xsquid/sdk": "^1.14.11", "@amplitude/analytics-browser": "^2.1.2", "@ledgerhq/hw-transport-web-ble": "^6.27.1", - "@mui/icons-material": "^5.8.0", + "@mui/icons-material": "^5.15.14", "@mui/material": "^5.9.1", "@sentry/react": "^7.53.1", "@terra-money/feather.js": "1.0.11", @@ -27,7 +27,7 @@ "@terra-money/log-finder-ruleset": "^3.0.3", "@terra-money/msg-reader": "^3.0.1", "@terra-money/station-connector": "^1.0.17-beta.6", - "@terra-money/station-ui": "^1.0.6", + "@terra-money/station-ui": "file:../station-ui/terra-money-station-ui-1.0.10.tgz", "@terra-money/terra-utils": "^1.2.0-beta.8", "@terra-money/terra.js": "^3.1.9", "@terra-money/terra.proto": "^2.0.0", diff --git a/src/pages/wallet/SendPage/Address.module.scss b/src/pages/wallet/SendPage/Address.module.scss new file mode 100644 index 000000000..ff40cebdf --- /dev/null +++ b/src/pages/wallet/SendPage/Address.module.scss @@ -0,0 +1,52 @@ +.flex__column__container { + position: relative; +} + +.blur__bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; + background: rgba(19, 19, 22, 0.9); + + backdrop-filter: blur(4px); +} + +.input__wrapper__real { + &.is__focused { + position: absolute; + width: 100%; + top: 8px; + filter: drop-shadow(0 40px 20px hsl(230, 9%, 7%)); + z-index: 10000; + + height: 250px; + } +} + +.options { + position: absolute; + background-color: var(--token-dark-500); + border: 1px solid var(--token-dark-700); + border-radius: 8px; + z-index: 102; + width: 100%; + max-height: 200px; + box-shadow: 0px 5px 7px 0px #0000009c; + top: 70px; + overflow: hidden; + + .options__container { + padding: 16px; + overflow: auto; + max-height: 200px; + + .children { + display: flex; + flex-direction: column; + gap: 24px; + } + } +} diff --git a/src/pages/wallet/SendPage/Address.tsx b/src/pages/wallet/SendPage/Address.tsx index e78d66c79..ffd978cd7 100644 --- a/src/pages/wallet/SendPage/Address.tsx +++ b/src/pages/wallet/SendPage/Address.tsx @@ -1,7 +1,8 @@ -import { useState, useEffect } from "react" -import { useSend } from "./SendContext" +import { useState, useEffect, useRef } from "react" +import { useLocation } from "react-router-dom" +import { useTranslation } from "react-i18next" import { AccAddress } from "@terra-money/feather.js" -import { getChainIDFromAddress } from "utils/bech32" +import classNames from "classnames/bind" import { InputWrapper, SectionHeader, @@ -9,23 +10,48 @@ import { Tabs, Button, FlexColumn, + Grid, + WalletListItem, } from "@terra-money/station-ui" -import { AddressBookList } from "./Components/AddressBookList" -import MyWallets from "./Components/MyWallets" import validate from "txs/validate" +import { getChainIDFromAddress } from "utils/bech32" import { useRecentRecipients } from "utils/localStorage" -import { useTranslation } from "react-i18next" import { WalletName } from "types/network" -import { useLocation } from "react-router-dom" +import { useAuth } from "auth" +import { useAddressBook } from "data/settings/AddressBook" +import { AddressBookList } from "./Components/AddressBookList" +import MyWallets from "./Components/MyWallets" +import { useSend } from "./SendContext" +import styles from "./Address.module.scss" + +const cx = classNames.bind(styles) const Address = () => { const { form, goToStep, getWalletName, networks } = useSend() const { state: denom } = useLocation() - const { recipients } = useRecentRecipients() const { register, setValue, formState, watch, trigger } = form const { errors } = formState const { recipient } = watch() const { t } = useTranslation() + const [recipientInputFocused, setRecipientInputFocused] = useState(false) + const { list: addressList } = useAddressBook() + const { recipients } = useRecentRecipients() + const { wallets } = useAuth() + + const ref = useRef(null) + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside) + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, []) + + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + setRecipientInputFocused(false) + } + } useEffect(() => { setValue("asset", denom) // pre-selected from asset page @@ -70,40 +96,170 @@ const Address = () => { } return ( - - - - - {formState.isValid && ( - + ) : ( + + ) + } + return ( <> {recipientInputFocused && } @@ -104,21 +101,15 @@ const Address = () => { align="stretch" className={styles.flex__column__container} > - {recipientInputFocused && ( // This is here to fill the space of the input behind the blurred background + {/* This is here to fill the space of the input behind the blurred background */} + {recipientInputFocused && (
- +
)} @@ -132,10 +123,11 @@ const Address = () => { } onFocus={() => setRecipientInputFocused(true)} onKeyDown={(e) => { if (e.key === "Enter") { @@ -143,7 +135,6 @@ const Address = () => { } }} /> - {recipientInputFocused && (
@@ -236,13 +227,6 @@ const Address = () => { )}
- {formState.isValid && ( - + ) +} + +export default AddressBookButton diff --git a/src/pages/wallet/SendPage/Components/MyWallets.tsx b/src/pages/wallet/SendPage/Components/MyWallets.tsx index 996cbde9c..9b378a700 100644 --- a/src/pages/wallet/SendPage/Components/MyWallets.tsx +++ b/src/pages/wallet/SendPage/Components/MyWallets.tsx @@ -1,52 +1,28 @@ -import { useAddressBook } from "data/settings/AddressBook" -import { AddressBookList } from "./AddressBookList" import { LocalWalletList } from "./LocalWalletList" -import { FavoriteIcon } from "@terra-money/station-ui" import { useAuth } from "auth" import { ReactComponent as ActiveWalletIcon } from "styles/images/icons/ActiveWallet.svg" interface MyWalletsProps { onClick?: (address: string, index: number, memo?: string) => void - tab: string } -const MyWallets = ({ tab, onClick }: MyWalletsProps) => { - const { list: addressList } = useAddressBook() +const MyWallets = ({ onClick }: MyWalletsProps) => { const { wallets, connectedWallet } = useAuth() - return ( <> - {tab === "address" ? ( - <> - } - items={addressList.filter((i) => i.favorite)} - /> - !i.favorite)} - /> - - ) : ( - <> - {connectedWallet && ( - } - items={[connectedWallet]} - /> - )} - w.name !== connectedWallet?.name)} - onClick={onClick} - title="Other Wallets" - /> - + {connectedWallet && ( + } + items={[connectedWallet]} + /> )} + w.name !== connectedWallet?.name)} + onClick={onClick} + title="My Wallets" + /> ) } diff --git a/src/pages/wallet/SendPage/Confirm.tsx b/src/pages/wallet/SendPage/Confirm.tsx index a606ea8e4..0d24f0987 100644 --- a/src/pages/wallet/SendPage/Confirm.tsx +++ b/src/pages/wallet/SendPage/Confirm.tsx @@ -7,8 +7,12 @@ import { ActivityListItem, Banner, FlexColumn, + InputWrapper, + Input, + Grid, } from "@terra-money/station-ui" import { useSend } from "./SendContext" +import validate from "txs/validate" import { truncate } from "@terra-money/terra-utils" import { useCurrency } from "data/settings/Currency" import { toInput } from "txs/utils" @@ -21,6 +25,7 @@ import { queryKey } from "data/query" import { CoinInput } from "txs/utils" import style from "./Send.module.scss" import { useIsLedger } from "utils/ledger" +import styles from "./Send.module.scss" interface InfoProps { render: ( @@ -46,7 +51,7 @@ const Confirm = () => { } = useSend() const { t } = useTranslation() const currency = useCurrency() - const { handleSubmit, setValue } = form + const { handleSubmit, setValue, register, formState } = form const { addRecipient } = useRecentRecipients() const [error, setError] = useState(null) const { input, assetInfo, destination, recipient, chain, memo } = form.watch() @@ -128,14 +133,30 @@ const Confirm = () => { } /> - - + + + {recipient} + {fee.render(rows)} + + + ) } @@ -164,6 +185,7 @@ const Confirm = () => { {({ submit, fee }) => (
+ {error && } {submit.button} diff --git a/src/pages/wallet/SendPage/Send.module.scss b/src/pages/wallet/SendPage/Send.module.scss index 9a88c3a93..e78f6d25d 100644 --- a/src/pages/wallet/SendPage/Send.module.scss +++ b/src/pages/wallet/SendPage/Send.module.scss @@ -1,3 +1,10 @@ +@import "font_mixins"; + .green { color: var(--token-success-500) !important; } + +.address { + color: var(--token-light-100, #8b8e9c); + @include XX-Small; +} diff --git a/src/pages/wallet/SendPage/SendTx.tsx b/src/pages/wallet/SendPage/SendTx.tsx index dab33fd16..daf1ea79f 100644 --- a/src/pages/wallet/SendPage/SendTx.tsx +++ b/src/pages/wallet/SendPage/SendTx.tsx @@ -7,16 +7,14 @@ import Submit from "./Submit" import Confirm from "./Confirm" import SendContext from "./SendContext" import ExtensionPageV2 from "extension/components/ExtensionPageV2" +import AddressBook from "./AddressBook" const SendTx = () => { const { pathname } = useLocation() const { t } = useTranslation() const getBackPath = (pathname: string) => { - const step = Number(pathname.split("/").pop()) - if (step !== 1) { - return `/send/${step === 3 ? 1 : step - 1}` // skip chain step on back to avoid confusion - } + if (pathname === "/send/address-book") return "/send/1" } const routes = [ @@ -25,6 +23,7 @@ const SendTx = () => { { path: "/3", element: , title: "Send" }, { path: "/4", element: , title: "Send" }, { path: "/5", element: , title: "Confirm Send" }, + { path: "/address-book", element: , title: "Address Book" }, ] return ( diff --git a/src/pages/wallet/SendPage/Submit.tsx b/src/pages/wallet/SendPage/Submit.tsx index 2af122929..1f97a8796 100644 --- a/src/pages/wallet/SendPage/Submit.tsx +++ b/src/pages/wallet/SendPage/Submit.tsx @@ -71,6 +71,7 @@ const Submit = () => { ) const originChain = useMemo(() => ibcData?.chainIDs?.[0], [ibcData]) + console.log("formState.isValid", formState.errors) const showIBCWarning = useMemo(() => { return ( @@ -166,20 +167,6 @@ const Submit = () => { }} currencyAmount={`${currency.symbol} ${currencyAmount ?? 0}`} /> - - - {showFeeWarning && ( { const { t } = useTranslation() - const [tabKey, setTabKey] = useState("wallets") + const [tab, setTab] = useState("wallets") const navigate = useNavigate() + const { list: addressList } = useAddressBook() const handleOpen = (index?: number) => { navigate(`new`, { state: { index } }) @@ -18,17 +27,17 @@ const AddressBook = () => { { key: "wallets", label: t("My Wallets"), - onClick: () => setTabKey("wallets"), + onClick: () => setTab("wallets"), }, { key: "address", label: t("Address Book"), - onClick: () => setTabKey("address"), + onClick: () => setTab("address"), }, ] const onClick = (address: AccAddress, index: number) => { - if (tabKey === "wallets") { + if (tab === "wallets") { navigate("my-addresses", { state: address }) } else if (index !== undefined) { handleOpen(index) @@ -42,8 +51,24 @@ const AddressBook = () => { onClick={() => handleOpen()} icon={} /> - - + + {tab === "address" ? ( + <> + } + items={addressList.filter((i) => i.favorite)} + /> + !i.favorite)} + /> + + ) : ( + + )} ) } From a640eb2ef71d54a1cb66a14165cb1243cdcd9de8 Mon Sep 17 00:00:00 2001 From: plubber Date: Tue, 26 Mar 2024 11:54:45 -0400 Subject: [PATCH 27/47] Refactor Address component to improve code readability and remove unused imports --- src/pages/wallet/SendPage/Address.tsx | 77 +++++++++++---------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/src/pages/wallet/SendPage/Address.tsx b/src/pages/wallet/SendPage/Address.tsx index b36684d8c..b8e51db3b 100644 --- a/src/pages/wallet/SendPage/Address.tsx +++ b/src/pages/wallet/SendPage/Address.tsx @@ -7,12 +7,9 @@ import { InputWrapper, SectionHeader, InputInLine, - Tabs, - Button, FlexColumn, Grid, WalletListItem, - AddressBookIcon, } from "@terra-money/station-ui" import validate from "txs/validate" import { getChainIDFromAddress } from "utils/bech32" @@ -61,7 +58,7 @@ const Address = () => { const handleKnownWallet = ( recipient: AccAddress | WalletName, - _: number, + index?: number, memo?: string ) => { setValue("memo", memo) @@ -148,20 +145,16 @@ const Address = () => { title={t("My Wallets")} /> {wallets.map((w) => { - if (w.name.includes(recipient ?? "")) { - return ( - handleKnownWallet(w.name, 0)} - /> - ) - } else { - return null - } + return w.name.includes(recipient ?? "") ? ( + handleKnownWallet(w.name)} + /> + ) : null })} )} @@ -176,20 +169,16 @@ const Address = () => { /> {recipients.map((w) => { - if (w.name.includes(recipient ?? "")) { - return ( - handleKnownChain(w.recipient)} - /> - ) - } else { - return null - } + return w.name.includes(recipient ?? "") ? ( + handleKnownChain(w.recipient)} + /> + ) : null })} )} @@ -203,20 +192,16 @@ const Address = () => { title={t("Address Book")} /> {addressList.map((w) => { - if (w.name.includes(recipient ?? "")) { - return ( - handleKnownChain(w.recipient)} - /> - ) - } else { - return null - } + return w.name.includes(recipient ?? "") ? ( + handleKnownChain(w.recipient)} + /> + ) : null })} )} From 10b80ba124345df9ff55614ebd175c28d4905dd4 Mon Sep 17 00:00:00 2001 From: plubber Date: Tue, 26 Mar 2024 12:03:54 -0400 Subject: [PATCH 28/47] Add StationWalletList component to Address.tsx --- src/pages/wallet/SendPage/Address.tsx | 98 ++++++------------- .../wallet/SendPage/Components/WalletList.tsx | 48 +++++++++ .../SendPage/Components/WalletListItem.tsx | 32 ++++++ 3 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 src/pages/wallet/SendPage/Components/WalletList.tsx create mode 100644 src/pages/wallet/SendPage/Components/WalletListItem.tsx diff --git a/src/pages/wallet/SendPage/Address.tsx b/src/pages/wallet/SendPage/Address.tsx index b8e51db3b..bc912d77a 100644 --- a/src/pages/wallet/SendPage/Address.tsx +++ b/src/pages/wallet/SendPage/Address.tsx @@ -22,6 +22,7 @@ import MyWallets from "./Components/MyWallets" import { useSend } from "./SendContext" import styles from "./Address.module.scss" import AddressBookButton from "./Components/AddressBookButton" +import StationWalletList from "./Components/WalletList" const cx = classNames.bind(styles) @@ -137,74 +138,37 @@ const Address = () => {
- {wallets.filter((w) => w.name.includes(recipient ?? "")) - .length > 0 && ( - - - {wallets.map((w) => { - return w.name.includes(recipient ?? "") ? ( - handleKnownWallet(w.name)} - /> - ) : null - })} - - )} + ({ + emoji: w.icon ?? w.name[0], + name: w.name, + address: t("Multiple Addresses"), + }))} + onItemClick={(address) => handleKnownWallet(address)} + filter={recipient ?? ""} + /> - {recipients.filter((r) => - r.recipient.includes(recipient ?? "") - ).length > 0 && ( - - - - {recipients.map((w) => { - return w.name.includes(recipient ?? "") ? ( - handleKnownChain(w.recipient)} - /> - ) : null - })} - - )} - - {addressList.filter((r) => - r.name.includes(recipient ?? "") - ).length > 0 && ( - - - {addressList.map((w) => { - return w.name.includes(recipient ?? "") ? ( - handleKnownChain(w.recipient)} - /> - ) : null - })} - - )} + ({ + emoji: r.icon ?? r.name[0], + name: r.name, + address: r.recipient, + }))} + onItemClick={(address) => handleKnownChain(address)} + filter={recipient ?? ""} + /> + ({ + emoji: w.icon ?? w.name[0], + name: w.name, + address: w.recipient, + }))} + onItemClick={(address) => handleKnownChain(address)} + filter={recipient ?? ""} + />
diff --git a/src/pages/wallet/SendPage/Components/WalletList.tsx b/src/pages/wallet/SendPage/Components/WalletList.tsx new file mode 100644 index 000000000..3507cd990 --- /dev/null +++ b/src/pages/wallet/SendPage/Components/WalletList.tsx @@ -0,0 +1,48 @@ +import React from "react" +import { Grid, SectionHeader } from "@terra-money/station-ui" +import { useTranslation } from "react-i18next" +import WalletListItem from "./WalletListItem" +import { AccAddress } from "@terra-money/feather.js" +import { WalletName } from "types/network" + +interface WalletListProps { + title: string + items: { + emoji: string + name: string + address: AccAddress | WalletName | string + }[] + onItemClick: (address: AccAddress | WalletName) => void + filter: string +} + +const WalletList: React.FC = ({ + title, + items, + onItemClick, + filter, +}) => { + const { t } = useTranslation() + + const filteredItems = items.filter((item) => item.name.includes(filter ?? "")) + + if (filteredItems.length === 0) return null + + return ( + + + {filteredItems.map((item) => ( + onItemClick(item.address)} + /> + ))} + + ) +} + +export default WalletList diff --git a/src/pages/wallet/SendPage/Components/WalletListItem.tsx b/src/pages/wallet/SendPage/Components/WalletListItem.tsx new file mode 100644 index 000000000..94c21c827 --- /dev/null +++ b/src/pages/wallet/SendPage/Components/WalletListItem.tsx @@ -0,0 +1,32 @@ +import React from "react" +import { WalletListItem as StationWalletListItem } from "@terra-money/station-ui" +import { AccAddress } from "@terra-money/feather.js" +import { WalletName } from "types/network" + +interface WalletListItemProps { + emoji: string + name: string + address: AccAddress | WalletName | string + smallText?: boolean + onClick: () => void +} + +const WalletListItem: React.FC = ({ + emoji, + name, + address, + smallText = false, + onClick, +}) => { + return ( + + ) +} + +export default WalletListItem From 7907d546dec74cd2383b9c3de114a775a4cd27cf Mon Sep 17 00:00:00 2001 From: plubber Date: Wed, 27 Mar 2024 11:42:35 -0400 Subject: [PATCH 29/47] fix: requested items --- src/auth/hooks/useAddress.ts | 20 ++++++++ src/pages/wallet/SendPage/Address.tsx | 48 ++++++++++++------- ...{WalletList.tsx => DropdownWalletList.tsx} | 6 +-- src/pages/wallet/SendPage/Send.module.scss | 2 + 4 files changed, 56 insertions(+), 20 deletions(-) rename src/pages/wallet/SendPage/Components/{WalletList.tsx => DropdownWalletList.tsx} (93%) diff --git a/src/auth/hooks/useAddress.ts b/src/auth/hooks/useAddress.ts index 5593ff174..5f7e8296b 100644 --- a/src/auth/hooks/useAddress.ts +++ b/src/auth/hooks/useAddress.ts @@ -6,6 +6,7 @@ import { useNetwork } from "data/wallet" import { useAddressBook } from "data/settings/AddressBook" import { AccAddress } from "@terra-money/feather.js" import { truncate } from "@terra-money/terra-utils" +import { getWallet } from "auth/scripts/keystore" /* auth | walle-provider */ const useAddress = () => { @@ -32,6 +33,25 @@ export const useAllInterchainAddresses = () => { }, {} as Record) return addresses } +export const useAllWalletAddresses = () => { + const { wallets } = useAuth() + const { networks } = useNetworks() + const networkName = useNetworkName() + const walletAddresses = wallets.map((w) => { + const wallet = getWallet(w.name) + const words = wallet?.words + if (!words) return {} + + const addresses = Object.values(networks[networkName] ?? {}) + .filter(({ coinType }) => !!words[coinType]) + .reduce((acc, { prefix, coinType, chainID }) => { + acc[chainID] = addressFromWords(words[coinType] as string, prefix) + return acc + }, {} as Record) + return { [w.name]: addresses } + }) + return walletAddresses +} export const useInterchainAddresses = () => { const { wallet } = useAuth() diff --git a/src/pages/wallet/SendPage/Address.tsx b/src/pages/wallet/SendPage/Address.tsx index bc912d77a..7a4b348d0 100644 --- a/src/pages/wallet/SendPage/Address.tsx +++ b/src/pages/wallet/SendPage/Address.tsx @@ -8,8 +8,6 @@ import { SectionHeader, InputInLine, FlexColumn, - Grid, - WalletListItem, } from "@terra-money/station-ui" import validate from "txs/validate" import { getChainIDFromAddress } from "utils/bech32" @@ -22,7 +20,8 @@ import MyWallets from "./Components/MyWallets" import { useSend } from "./SendContext" import styles from "./Address.module.scss" import AddressBookButton from "./Components/AddressBookButton" -import StationWalletList from "./Components/WalletList" +import DropdownWalletList from "./Components/DropdownWalletList" +import { useAllWalletAddresses } from "auth/hooks/useAddress" const cx = classNames.bind(styles) @@ -37,6 +36,7 @@ const Address = () => { const { list: addressList } = useAddressBook() const { recipients } = useRecentRecipients() const { wallets } = useAuth() + const walletAddresses = useAllWalletAddresses() const ref = useRef(null) @@ -82,7 +82,10 @@ const Address = () => { const InputExtra = () => { return formState.isValid ? ( - ) : ( @@ -138,18 +141,29 @@ const Address = () => {
- ({ - emoji: w.icon ?? w.name[0], - name: w.name, - address: t("Multiple Addresses"), - }))} - onItemClick={(address) => handleKnownWallet(address)} - filter={recipient ?? ""} + onItemClick={(address) => handleKnownChain(address)} + items={walletAddresses.flatMap((w) => + Object.entries(w).flatMap(([name, addresses]) => + Object.values(addresses) + .filter( + (address) => + recipient && + !AccAddress.validate(recipient) && + address.includes(recipient) + ) + .map((address) => ({ + emoji: + wallets.find((w) => w.name === name)?.icon ?? + name[0], + name, + address, + })) + ) + )} /> - - ({ emoji: r.icon ?? r.name[0], @@ -157,9 +171,9 @@ const Address = () => { address: r.recipient, }))} onItemClick={(address) => handleKnownChain(address)} - filter={recipient ?? ""} + filter={recipient} /> - ({ emoji: w.icon ?? w.name[0], @@ -167,7 +181,7 @@ const Address = () => { address: w.recipient, }))} onItemClick={(address) => handleKnownChain(address)} - filter={recipient ?? ""} + filter={recipient} />
diff --git a/src/pages/wallet/SendPage/Components/WalletList.tsx b/src/pages/wallet/SendPage/Components/DropdownWalletList.tsx similarity index 93% rename from src/pages/wallet/SendPage/Components/WalletList.tsx rename to src/pages/wallet/SendPage/Components/DropdownWalletList.tsx index 3507cd990..b2f399914 100644 --- a/src/pages/wallet/SendPage/Components/WalletList.tsx +++ b/src/pages/wallet/SendPage/Components/DropdownWalletList.tsx @@ -13,14 +13,14 @@ interface WalletListProps { address: AccAddress | WalletName | string }[] onItemClick: (address: AccAddress | WalletName) => void - filter: string + filter?: string } const WalletList: React.FC = ({ title, items, onItemClick, - filter, + filter = "", }) => { const { t } = useTranslation() @@ -33,7 +33,7 @@ const WalletList: React.FC = ({ {filteredItems.map((item) => ( Date: Wed, 27 Mar 2024 13:59:54 -0400 Subject: [PATCH 30/47] fix: storage 1 at a time bug --- src/data/queries/ibc.ts | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/data/queries/ibc.ts b/src/data/queries/ibc.ts index 21e738457..24f9409bf 100644 --- a/src/data/queries/ibc.ts +++ b/src/data/queries/ibc.ts @@ -84,6 +84,7 @@ export const useIBCBaseDenoms = (data: { denom: Denom; chainID: string }[]) => { cachedDenomTraces[denom] && Date.now() - cachedDenomTraces[denom].timestamp < oneWeekAgo ) { + console.log(denom, "cached") return cachedDenomTraces[denom].data } @@ -96,18 +97,14 @@ export const useIBCBaseDenoms = (data: { denom: Denom; chainID: string }[]) => { const channels = [] for (let i = 0; i < paths.length; i += 2) { - const chain = chains[0] - if (!network[chain]?.lcd) return - + if (!network[chainID]?.lcd) return const [port, channel] = [paths[i], paths[i + 1]] channels.unshift({ port, channel }) - - const { data } = await axios.get( + const res = await axios.get( `/ibc/core/channel/v1/channels/${channel}/ports/${port}/client_state`, - { baseURL: network[chain].lcd } + { baseURL: network[chainID].lcd } ) - - chains.unshift(data.identified_client_state.client_state.chain_id) + chains.unshift(res?.data?.identified_client_state.client_state.chain_id) } const result = { @@ -121,22 +118,34 @@ export const useIBCBaseDenoms = (data: { denom: Denom; chainID: string }[]) => { channels, } - setLocalSetting(SettingKey.DenomTrace, { - ...cachedDenomTraces, - [denom]: { data: result, timestamp: Date.now() }, - }) - return result } - return useQueries( + const queryResults = useQueries( data.map(({ denom, chainID }) => ({ + ...RefetchOptions.INFINITY, queryKey: [queryKey.ibc.denomTrace, denom, network], queryFn: () => fetchDenomTrace({ denom, chainID }), - ...RefetchOptions.INFINITY, enabled: isDenomIBC(denom) && !!network[chainID], })) ) + + const updatedDenomTraces = queryResults.reduce((acc, result) => { + if (result.data) { + const { ibcDenom, ...rest } = result.data + acc[ibcDenom] = { data: rest, timestamp: Date.now() } + } + return acc + }, {} as Record) + + setLocalSetting(SettingKey.DenomTrace, { + ...getLocalSetting>( + SettingKey.DenomTrace + ), + ...updatedDenomTraces, + }) + + return queryResults } export function calculateIBCDenom(baseDenom: string, path: string) { From 401548572e9fa2e8298729157c4ba8c667ba8db4 Mon Sep 17 00:00:00 2001 From: plubber Date: Wed, 27 Mar 2024 14:25:32 -0400 Subject: [PATCH 31/47] Delete unused files and code --- src/app/components/PopoverNone.module.scss | 27 ---- src/app/components/PopoverNone.tsx | 30 ----- src/app/containers/TxMessage.tsx | 12 +- src/app/sections/Aside.tsx | 13 -- src/app/sections/ConnectWallet.tsx | 61 --------- src/app/sections/Connected.module.scss | 20 --- src/app/sections/Connected.tsx | 122 ------------------ src/app/sections/DevModeTooltips.tsx | 38 ------ src/app/sections/DevTools.tsx | 16 --- src/app/sections/Links.tsx | 45 ------- .../sections/settings/AdvancedSettings.tsx | 51 -------- src/txs/Tx.tsx | 99 ++++++-------- src/types/components.ts | 16 --- src/utils/chain.ts | 10 -- 14 files changed, 44 insertions(+), 516 deletions(-) delete mode 100644 src/app/components/PopoverNone.module.scss delete mode 100644 src/app/components/PopoverNone.tsx delete mode 100644 src/app/sections/Aside.tsx delete mode 100644 src/app/sections/ConnectWallet.tsx delete mode 100644 src/app/sections/Connected.module.scss delete mode 100644 src/app/sections/Connected.tsx delete mode 100644 src/app/sections/DevModeTooltips.tsx delete mode 100644 src/app/sections/DevTools.tsx delete mode 100644 src/app/sections/Links.tsx delete mode 100644 src/app/sections/settings/AdvancedSettings.tsx delete mode 100644 src/types/components.ts diff --git a/src/app/components/PopoverNone.module.scss b/src/app/components/PopoverNone.module.scss deleted file mode 100644 index 44c226d26..000000000 --- a/src/app/components/PopoverNone.module.scss +++ /dev/null @@ -1,27 +0,0 @@ -@import "mixins"; - -.component { - @include flex-column(space-between, stretch); - background: var(--card-bg); -} - -.inner { - padding: 24px; -} - -.footer { - @include flex; - - background: var(--bg); - border-top: solid 1px var(--card-border); - color: var(--text-muted); - font-weight: var(--bold); - line-height: 1; - padding: 12px; - transition: color var(--transition); - - &:hover { - color: var(--text); - text-decoration: none; - } -} diff --git a/src/app/components/PopoverNone.tsx b/src/app/components/PopoverNone.tsx deleted file mode 100644 index 7921a5c0b..000000000 --- a/src/app/components/PopoverNone.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { PropsWithChildren, ReactNode } from "react" -import { Link } from "react-router-dom" -import classNames from "classnames" -import styles from "./PopoverNone.module.scss" - -interface Props { - className?: string - footer?: { children: ReactNode; onClick: () => void; to?: string } -} -const cx = classNames.bind(styles) - -const PopoverNone = (props: PropsWithChildren) => { - const { className, children, footer } = props - - const renderFooter = () => { - if (!footer) return null - const { to } = footer - if (to) return - return - ) - - const list = [ - { - icon: , - to: "/auth/ledger", - children: t("Access with ledger"), - }, - ] - - return ( - - - - - {!!available.length && ( - - Use Station on the - browser to access with Ledger device - - )} - - - ) -} - -export default ConnectWallet diff --git a/src/app/sections/Connected.module.scss b/src/app/sections/Connected.module.scss deleted file mode 100644 index c3ff0de36..000000000 --- a/src/app/sections/Connected.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -.popover { - min-width: 260px; -} - -.modal { - color: var(--text); - font-size: 18px; - font-weight: var(--bold); - margin-bottom: 4px; -} - -.button { - min-width: 0; -} - -.button__text { - @media (max-width: 500px) { - display: none; - } -} diff --git a/src/app/sections/Connected.tsx b/src/app/sections/Connected.tsx deleted file mode 100644 index 516846568..000000000 --- a/src/app/sections/Connected.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useState } from "react" -import { useTranslation } from "react-i18next" -import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet" -import GroupsIcon from "@mui/icons-material/Groups" -import QrCodeIcon from "@mui/icons-material/QrCode" -import UsbIcon from "@mui/icons-material/Usb" -import BluetoothIcon from "@mui/icons-material/Bluetooth" -import { truncate } from "@terra-money/terra-utils" -import { useAddress } from "data/wallet" -import { useTnsName } from "data/external/tns" -import { Button, Copy } from "components/general" -import CopyStyles from "components/general/Copy.module.scss" -import { Flex, Grid } from "components/layout" -import { Tooltip, Popover } from "components/display" -import { isWallet, useAuth } from "auth" -import SwitchWallet from "auth/modules/select/SwitchWallet" -import PopoverNone from "../components/PopoverNone" -import WalletQR from "./WalletQR" -import styles from "./Connected.module.scss" -import { ModalButton } from "components/feedback" -import Addresses from "pages/wallet/ReceivePage" - -const Connected = () => { - const { t } = useTranslation() - const address = useAddress() - const { wallet, getLedgerKey } = useAuth() - const { data: name } = useTnsName(address ?? "") - - /* hack to close popover */ - const [key, setKey] = useState(0) - const closePopover = () => setKey((key) => key + 1) - - if (!address) return null - - const footer = { - to: "/auth", - onClick: closePopover, - children: t("Manage wallets"), - } - - return ( - - - -
- ( - - - - )} - > - - -
- - - ( - - - - )} - /> - - {isWallet.ledger(wallet) && ( - - - - )} - -
- - -
- - } - placement="bottom-end" - theme="none" - > - -
- ) -} - -export default Connected diff --git a/src/app/sections/DevModeTooltips.tsx b/src/app/sections/DevModeTooltips.tsx deleted file mode 100644 index 3a748b036..000000000 --- a/src/app/sections/DevModeTooltips.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useTranslation } from "react-i18next" -export const ReplaceKeplrTooltip = () => { - const { t } = useTranslation() - - return ( -
-

- {t( - "When this option is enabled Station will try to replace Keplr on external websites." - )} -

-
- ) -} -export const DevModeTooltip = () => { - const { t } = useTranslation() - - return ( -
-

{t("Developer mode enables the following")}:

-
    -
  • {t("Copy token addresses from your wallet")}
  • -
-
- ) -} -export const GasAdjustmentTooltip = () => { - const { t } = useTranslation() - return ( -
-

- {t( - "Set a gas adjustment coefficient in case none is specified by the chain" - )} -

-
- ) -} diff --git a/src/app/sections/DevTools.tsx b/src/app/sections/DevTools.tsx deleted file mode 100644 index 755012927..000000000 --- a/src/app/sections/DevTools.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useQueryClient } from "react-query" -import { debug } from "utils/env" -import { Button } from "components/general" - -const DevTools = () => { - const queryClient = useQueryClient() - if (!debug.query) return null - - return ( - - ) -} - -export default DevTools diff --git a/src/app/sections/Links.tsx b/src/app/sections/Links.tsx deleted file mode 100644 index dc940f6ff..000000000 --- a/src/app/sections/Links.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useTranslation } from "react-i18next" -import DescriptionIcon from "@mui/icons-material/Description" -import BoltIcon from "@mui/icons-material/Bolt" -import { DOCUMENTATION, SETUP } from "config/constants" -import { ExternalLink } from "components/general" -import { Contacts } from "components/layout" -import styles from "./Links.module.scss" -import { capitalize } from "@mui/material" -import { useAddress } from "data/wallet" - -const Links = () => { - const { t } = useTranslation() - const isConnected = useAddress() - - const community = { - medium: "https://medium.com/terra-money", - discord: "https://terra.sc/stationdiscord", - telegram: "https://t.me/Station_Wallet", - twitter: "https://twitter.com/StationWallet", - github: "https://github.com/terra-money/station-wallet", - } - - return ( -
-
- {!isConnected && ( - - - {capitalize(t("setup"))} - - )} - - - {capitalize(t("documentation"))} - -
- -
- -
-
- ) -} - -export default Links diff --git a/src/app/sections/settings/AdvancedSettings.tsx b/src/app/sections/settings/AdvancedSettings.tsx deleted file mode 100644 index 08ef47dae..000000000 --- a/src/app/sections/settings/AdvancedSettings.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { - useDevMode, - //useReplaceKeplr -} from "utils/localStorage" -import SettingsSelectorToggle from "components/layout/SettingsSelectorToggle" -import { FlexColumn, GasAdjustment } from "components/layout" -import { TooltipIcon } from "components/display" -import { - DevModeTooltip, - //ReplaceKeplrTooltip -} from "../DevModeTooltips" -//import { FormWarning } from "components/form" - -const AdvancedSettings = () => { - const { devMode, changeDevMode } = useDevMode() - //const { replaceKeplr, toggleReplaceKeplr } = useReplaceKeplr() - - return ( - - {/* - } />} - options={[ - { - value: "replaceKeplr", - selected: replaceKeplr, - label: "Replace Keplr", - }, - ]} - /> - {replaceKeplr && ( - - To prevent collisions between Keplr and Station, when this fature is - enabled uninstall or disable the Keplr extension on your browser. - - )} - */} - } />} - options={[ - { value: "devMode", selected: devMode, label: "Developer Mode" }, - ]} - /> - - - ) -} - -export default AdvancedSettings diff --git a/src/txs/Tx.tsx b/src/txs/Tx.tsx index a326632b5..c37d92d3e 100644 --- a/src/txs/Tx.tsx +++ b/src/txs/Tx.tsx @@ -23,16 +23,13 @@ import { useIsWalletEmpty } from "data/queries/bank" import { Pre } from "components/general" import { Grid, Flex } from "components/layout" import { Read } from "components/token" -import ConnectWallet from "app/sections/ConnectWallet" import useToPostMultisigTx from "pages/multisig/utils/useToPostMultisigTx" import { isWallet, useAuth } from "auth" import { toInput, CoinInput, calcTaxes } from "./utils" import styles from "./Tx.module.scss" -import { useInterchainAddresses } from "auth/hooks/useAddress" import { getShouldTax, useTaxCap, useTaxRate } from "data/queries/treasury" import { Banner, - Button, Checkbox, Input, InputWrapper, @@ -120,7 +117,6 @@ function Tx(props: Props) { const { t } = useTranslation() const networks = useNetwork() const { wallet, validatePassword, ...auth } = useAuth() - const addresses = useInterchainAddresses() const isWalletEmpty = useIsWalletEmpty() const setLatestTx = useSetRecoilState(latestTxState) const isBroadcasting = useRecoilValue(isBroadcastingState) @@ -356,60 +352,47 @@ function Tx(props: Props) { const submitButton = ( <> {walletError && } - - {!addresses ? ( - ( - - diff --git a/src/app/NetworkLoading.tsx b/src/app/NetworkLoading.tsx index baec205dd..773320aad 100644 --- a/src/app/NetworkLoading.tsx +++ b/src/app/NetworkLoading.tsx @@ -3,7 +3,7 @@ import styles from "./NetworkError.module.scss" import { useTheme } from "data/settings/Theme" import Overlay from "./components/Overlay" import { useEffect, useState } from "react" -import { Button } from "components/general" +import { Button } from "@terra-money/station-ui" import ReplayIcon from "@mui/icons-material/Replay" interface Props { @@ -38,7 +38,7 @@ const NetworkLoading = ({ title, timeout }: Props) => { {timeout && showTimeout && (

Oops, something went wrong, this is taking too much time.

-
diff --git a/src/app/components/AddressTable.module.scss b/src/app/components/AddressTable.module.scss deleted file mode 100644 index e0f25d6b6..000000000 --- a/src/app/components/AddressTable.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@import "mixins"; - -.address { - @include flex(space-between); -} - -.connect { - padding: 10px; -} - -.chain { - text-transform: capitalize; - @include flex(align-items); -} - -.chain .name { - margin-left: 10px; - text-overflow: ellipsis; - overflow: hidden; - max-width: 80px; - font-weight: var(--bold); -} diff --git a/src/app/sections/Aside.module.scss b/src/app/sections/Aside.module.scss deleted file mode 100644 index e2406e383..000000000 --- a/src/app/sections/Aside.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.aside { - padding: 28px 20px; -} diff --git a/src/app/sections/LastHeight.module.scss b/src/app/sections/LastHeight.module.scss deleted file mode 100644 index 0f01ebe9a..000000000 --- a/src/app/sections/LastHeight.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@import "mixins"; - -.component { - font-size: 11px; -} - -.indicator { - border-radius: 50%; - width: 8px; - height: 8px; -} - -.success { - background: var(--success); -} - -.loading { - background: var(--warning); -} - -.text, -.link { - color: var(--menu-text); - opacity: 0.75; -} - -.link { - transition: hover var(--transition); - - &:hover { - opacity: 1; - } -} diff --git a/src/app/sections/LastHeight.tsx b/src/app/sections/LastHeight.tsx deleted file mode 100644 index eb4ffe203..000000000 --- a/src/app/sections/LastHeight.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useTranslation } from "react-i18next" -import classNames from "classnames" -import { formatNumber } from "@terra-money/terra-utils" -import { useTerraObserver } from "data/Terra/TerraObserver" -import { FinderLink } from "components/general" -import { Flex } from "components/layout" -import styles from "./LastHeight.module.scss" -import { useChainID } from "data/wallet" - -const cx = classNames.bind(styles) - -const LastHeight = () => { - const { t } = useTranslation() - const { block } = useTerraObserver() - const chainID = useChainID() - - const height = block?.header.height - - return ( - -
- - {height ? ( - - #{formatNumber(height, { comma: true })} - - ) : ( -

{t("Loading...")}

- )} - - ) -} - -export default LastHeight diff --git a/src/app/sections/Links.module.scss b/src/app/sections/Links.module.scss deleted file mode 100644 index fb19b5db0..000000000 --- a/src/app/sections/Links.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@import "mixins"; - -.links { - --gutter: 10px; -} - -/* tutorial */ -.tutorial { - border-bottom: 1px solid var(--menu-border); -} - -/* community */ -.community { - margin: var(--gutter) 0; -} - -/* item */ -.link { - @include flex(flex-start); - - gap: 4px; - color: var(--menu-text); - font-size: var(--font-size-small); - line-height: 1; - opacity: 0.75; - padding: var(--gutter) 0; - transition: opacity var(--transition); - width: 100%; - - &:hover { - opacity: 1; - } -} diff --git a/src/app/sections/NavButton.tsx b/src/app/sections/NavButton.tsx deleted file mode 100644 index 19dbeb731..000000000 --- a/src/app/sections/NavButton.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useRecoilState } from "recoil" -import { mobileIsMenuOpenState } from "components/layout" - -import MenuIcon from "@mui/icons-material/Menu" -import CloseIcon from "@mui/icons-material/Close" -import styles from "./Nav.module.scss" - -const NavButton = () => { - const [isOpen, setIsOpen] = useRecoilState(mobileIsMenuOpenState) - const toggle = () => setIsOpen((isOpen) => !isOpen) - return ( - - ) -} - -export default NavButton diff --git a/src/app/sections/Refresh.tsx b/src/app/sections/Refresh.tsx deleted file mode 100644 index df0966b59..000000000 --- a/src/app/sections/Refresh.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useQueryClient } from "react-query" -import RefreshIcon from "@mui/icons-material/Refresh" -import HeaderIconButton from "../components/HeaderIconButton" - -const Prefreneces = () => { - const queryClient = useQueryClient() - - return ( - queryClient.invalidateQueries()}> - - - ) -} - -export default Prefreneces diff --git a/src/app/sections/ValidatorButton.tsx b/src/app/sections/ValidatorButton.tsx deleted file mode 100644 index 61f4a8cb1..000000000 --- a/src/app/sections/ValidatorButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useAddress, useChainID } from "data/wallet" -import { useValidators } from "data/queries/staking" -import { getConnectedMoniker } from "data/queries/distribution" -import { LinkButton } from "components/general" - -const ValidatorButton = () => { - const chainID = useChainID() - const address = useAddress() - const { data: validators } = useValidators(chainID) - if (!address) return null - const moniker = getConnectedMoniker(address, validators ?? []) - if (!moniker) return null - - return ( - - {moniker} - - ) -} - -export default ValidatorButton diff --git a/src/app/sections/WalletQR.tsx b/src/app/sections/WalletQR.tsx deleted file mode 100644 index 3ada966ed..000000000 --- a/src/app/sections/WalletQR.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useTranslation } from "react-i18next" -import { RenderButton } from "types/components" -import { useAddress } from "data/wallet" -import { Grid } from "components/layout" -import { ModalButton } from "components/feedback" -import QRCode from "auth/components/QRCode" - -const WalletQR = ({ renderButton }: { renderButton: RenderButton }) => { - const { t } = useTranslation() - const address = useAddress() - - if (!address) return null - - return ( - - - -

{address}

-
-
- ) -} - -export default WalletQR diff --git a/src/auth/networks/ManageNetworksPage.tsx b/src/auth/networks/ManageNetworksPage.tsx deleted file mode 100644 index a51bc5f96..000000000 --- a/src/auth/networks/ManageNetworksPage.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Link } from "react-router-dom" -import AddIcon from "@mui/icons-material/Add" -import { Card, Page } from "components/layout" -import ManageNetworksForm from "./ManageNetworksForm" - -const ManageNetworksPage = () => { - const { t } = useTranslation() - - const add = ( - - - - ) - - return ( - - - - - - ) -} - -export default ManageNetworksPage diff --git a/src/components/display/Details.module.scss b/src/components/display/Details.module.scss deleted file mode 100644 index cba6bb180..000000000 --- a/src/components/display/Details.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.component { - background: var(--bg); - color: var(--text); - //border: var(--border-width) solid var(--card-border); - border-radius: var(--border-radius); - padding: var(--card-padding-small); - word-break: break-all; - - p { - color: var(--text); - } -} diff --git a/src/components/display/Details.tsx b/src/components/display/Details.tsx deleted file mode 100644 index 178b3173f..000000000 --- a/src/components/display/Details.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { PropsWithChildren } from "react" -import styles from "./Details.module.scss" - -const Details = ({ children }: PropsWithChildren<{}>) => { - return
{children}
-} - -export default Details diff --git a/src/components/display/index.ts b/src/components/display/index.ts index c2fbaf09d..fdc69863d 100644 --- a/src/components/display/index.ts +++ b/src/components/display/index.ts @@ -1,4 +1,3 @@ -export { default as Details } from "./Details" export { default as Dl } from "./Dl" export { default as List } from "./List" export * from "./List" diff --git a/src/components/general/Button.module.scss b/src/components/general/Button.module.scss deleted file mode 100644 index cce508b09..000000000 --- a/src/components/general/Button.module.scss +++ /dev/null @@ -1,100 +0,0 @@ -@use "sass:math"; -@import "mixins"; - -@mixin size($min-width, $height) { - //border-radius: math.div($height, 2); - min-width: $min-width; - height: $height; -} - -.button { - @include inline-flex; - @include size(150px, 48px); - - border-radius: 15px; - font-weight: var(--bold); - gap: 5px; - line-height: 1; - padding-left: 20px; - padding-right: 20px; - white-space: pre; - - &:hover { - text-decoration: none; - } -} - -.disabled, -.loading { - @include disabled; -} - -/* size */ -.small { - @include size(100px, 32px); - font-size: 14px; -} - -/* color */ -@mixin variant($bg-h, $bg-s, $bg-l, $color, $type) { - background: hsl($bg-h, $bg-s, $bg-l); - color: $color; - transition: background var(--transition); - - @if $type != default { - &:hover:not(:disabled) { - background: hsl($bg-h, $bg-s, calc($bg-l - 5%)); - } - } @else { - &:hover:not(:disabled) { - background: var( - --dark-button-hover-only, - hsl($bg-h, $bg-s, calc($bg-l - 5%)) - ); - } - } -} - -$colors: ( - default: ( - bg-h: var(--button-default-bg-h), - bg-s: var(--button-default-bg-s), - bg-l: var(--button-default-bg-l), - color: var(--button-default-text), - ), - primary: ( - bg-h: var(--button-primary-bg-h), - bg-s: var(--button-primary-bg-s), - bg-l: var(--button-primary-bg-l), - color: var(--button-primary-text), - ), - danger: ( - bg-h: var(--button-danger-bg-h), - bg-s: var(--button-danger-bg-s), - bg-l: var(--button-danger-bg-l), - color: var(--button-primary-text), - ), -); - -@each $color, $value in $colors { - .#{$color} { - @include variant( - map-get($value, bg-h), - map-get($value, bg-s), - map-get($value, bg-l), - map-get($value, color), - $type: $color - ); - } -} - -/* outline */ -.outline { - border: var(--border-width) solid currentColor; - color: currentColor; -} - -/* block */ -.block { - width: 100%; -} diff --git a/src/components/general/Button.tsx b/src/components/general/Button.tsx deleted file mode 100644 index f32ddd836..000000000 --- a/src/components/general/Button.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { ButtonHTMLAttributes, ForwardedRef, ReactNode } from "react" -import { forwardRef } from "react" -import classNames from "classnames/bind" -import { LoadingCircular } from "../feedback" -import styles from "./Button.module.scss" - -const cx = classNames.bind(styles) - -export interface ButtonConfig { - icon?: ReactNode - size?: "small" - color?: "default" | "primary" | "danger" - outline?: boolean - block?: boolean - loading?: boolean - disabled?: boolean -} - -type Props = ButtonConfig & ButtonHTMLAttributes - -const Button = forwardRef( - ({ children, ...props }: Props, ref?: ForwardedRef) => { - const { icon, size, color, outline, block, loading, ...attrs } = props - const className = cx(getClassName(props), props.className) - - return ( - - ) - } -) - -export default Button - -/* helpers */ -export const getClassName = (props: ButtonConfig) => { - const { size, outline, block, disabled, loading } = props - const color = props.color ?? (!outline && "default") - return cx(styles.button, size, color, { outline, block, disabled, loading }) -} diff --git a/src/components/general/WalletQR.tsx b/src/components/general/WalletQR.tsx deleted file mode 100644 index 889c2be3c..000000000 --- a/src/components/general/WalletQR.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useTranslation } from "react-i18next" -import { RenderButton } from "types/components" -import { Grid } from "components/layout" -import { ModalButton } from "components/feedback" -import QRCode from "auth/components/QRCode" -import { AccAddress } from "@terra-money/feather.js" - -const WalletQR = ({ - renderButton, - address, -}: { - renderButton: RenderButton - address: AccAddress -}) => { - const { t } = useTranslation() - - if (!address) return null - - return ( - - - -

{address}

-
-
- ) -} - -export default WalletQR diff --git a/src/components/general/index.ts b/src/components/general/index.ts index 235808d21..66a290e00 100644 --- a/src/components/general/index.ts +++ b/src/components/general/index.ts @@ -1,4 +1,3 @@ -export { default as Button } from "./Button" export { default as Copy } from "./Copy" export { ExternalLink, ExternalIconLink } from "./External" export * from "./External" @@ -9,4 +8,3 @@ export { default as LinkButton } from "./LinkButton" export { default as Pre } from "./Pre" export { default as ValidatorLink } from "./ValidatorLink" export { default as CopyIcon } from "./CopyIcon" -export { default as WalletQR } from "./WalletQR" diff --git a/src/extension/auth/AddWalletButton.tsx b/src/extension/auth/AddWalletButton.tsx deleted file mode 100644 index a95badbc6..000000000 --- a/src/extension/auth/AddWalletButton.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useTranslation } from "react-i18next" -import AddIcon from "@mui/icons-material/Add" -import { ModalButton } from "components/feedback" -import AddWallet from "./AddWallet" -import styles from "./ConnectedWallet.module.scss" -import { Button } from "components/general" - -const AddWalletButton = () => { - const { t } = useTranslation() - - return ( - ( - - )} - > - - - ) -} - -export default AddWalletButton diff --git a/src/extension/components/ConfirmButtons.tsx b/src/extension/components/ConfirmButtons.tsx index 2b9669e54..1ffe19c32 100644 --- a/src/extension/components/ConfirmButtons.tsx +++ b/src/extension/components/ConfirmButtons.tsx @@ -1,6 +1,6 @@ import { ButtonHTMLAttributes } from "react" -import { Button } from "components/general" import { Flex } from "components/layout" +import { Button } from "@terra-money/station-ui" import styles from "./ConfirmButtons.module.scss" type ButtonAttrs = ButtonHTMLAttributes @@ -11,14 +11,16 @@ interface Props { const ConfirmButtons = ({ buttons }: Props) => { if (buttons.length === 1) - return - )} - /> - - - - {extra} -
- ) -} - -export default WalletCard diff --git a/src/pages/contract/ContractItemActions.tsx b/src/pages/contract/ContractItemActions.tsx index 0c2f1cb58..d136c1e6b 100644 --- a/src/pages/contract/ContractItemActions.tsx +++ b/src/pages/contract/ContractItemActions.tsx @@ -1,10 +1,11 @@ import { useTranslation } from "react-i18next" import { useAddress } from "data/wallet" -import { Button, LinkButton } from "components/general" +import { LinkButton } from "components/general" import { ModalButton } from "components/feedback" import { ExtraActions } from "components/layout" import ContractQuery from "./ContractQuery" import { useContract } from "./Contract" +import { Button } from "@terra-money/station-ui" const ContractItemActions = () => { const { t } = useTranslation() @@ -16,7 +17,7 @@ const ContractItemActions = () => { ( - )} diff --git a/src/pages/history/HistoryList.legacy.tsx b/src/pages/history/HistoryList.legacy.tsx index cbcfcf172..f71da1f34 100644 --- a/src/pages/history/HistoryList.legacy.tsx +++ b/src/pages/history/HistoryList.legacy.tsx @@ -4,11 +4,11 @@ import { useInfiniteQuery } from "react-query" import axios from "axios" import { queryKey } from "data/query" import { useNetwork } from "data/wallet" -import { Button } from "components/general" import { Card, Col, Page } from "components/layout" import { Empty } from "components/feedback" import HistoryItem from "./HistoryItem" import { useInterchainAddresses } from "auth/hooks/useAddress" +import { Button } from "@terra-money/station-ui" interface Props { chainID: string @@ -81,6 +81,7 @@ const HistoryList = ({ chainID }: Props) => { ))}