diff --git a/package-lock.json b/package-lock.json index c137cf6f7..f5bfe79e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@ledgerhq/hw-transport-web-ble": "^6.27.1", "@mui/icons-material": "^5.8.0", "@mui/material": "^5.9.1", + "@sentry/react": "^7.53.1", "@terra-money/feather.js": "^1.0.4", "@terra-money/ledger-station-js": "^1.3.7", "@terra-money/log-finder-ruleset": "^3.0.0", @@ -3670,6 +3671,125 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "node_modules/@sentry-internal/tracing": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.53.1.tgz", + "integrity": "sha512-a4H4rvVdz0XDGgNfRqc7zg6rMt2P1P05xBmgfIfztYy94Vciw1QMdboNiT7einr8ra8wogdEaK4Pe2AzYAPBJQ==", + "dependencies": { + "@sentry/core": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/browser": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.53.1.tgz", + "integrity": "sha512-1zas2R6riJaj0k7FoeieCW0SuC7UyKaBGA6jEG2LsgIqyD7IDOlF3BPZ4Yt08GFav0ImpyhGn5Vbrq5JLbeQdw==", + "dependencies": { + "@sentry-internal/tracing": "7.53.1", + "@sentry/core": "7.53.1", + "@sentry/replay": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/core": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.53.1.tgz", + "integrity": "sha512-DAH8IJNORJJ7kQLqsZuhMkN6cwJjXzFuuUoZor7IIDHIHjtl51W+2F3Stg3+I3ZoKDfJfUNKqhipk2WZjG0FBg==", + "dependencies": { + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/react": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.53.1.tgz", + "integrity": "sha512-eEOY/peBepSD/nhPn4SU77aYdjQfAI1svOqpG4sbpjaGZU1P6L7+IIGmip8l2T68oPEeKDaiH9Qy/3uxu55B/Q==", + "dependencies": { + "@sentry/browser": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "hoist-non-react-statics": "^3.3.2", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "15.x || 16.x || 17.x || 18.x" + } + }, + "node_modules/@sentry/react/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/replay": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.53.1.tgz", + "integrity": "sha512-5He5JLJiYLeWtXHC53z2ZzfbgAedafbHNZVS4+MBCOtydCk7cnuyJ0gGV6Rfxej/lZSNXZxOdW7HeMhzBtZCxw==", + "dependencies": { + "@sentry/core": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/types": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.53.1.tgz", + "integrity": "sha512-/ijchRIu+jz3+j/zY+7KRPfLSCY14fTx5xujjbOdmEKjmIHQmwPBdszcQm40uwofrR8taV4hbt5MFN+WnjCkCw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.53.1.tgz", + "integrity": "sha512-DKJA1LSUOEv4KOR828MzVuLh+drjeAgzyKgN063OEKmnirgjgRgNNS8wUgwpG0Tn2k6ANZGCwrdfzPeSBxshKg==", + "dependencies": { + "@sentry/types": "7.53.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -12156,7 +12276,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "devOptional": true, "dependencies": { "react-is": "^16.7.0" } @@ -12164,8 +12283,7 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "devOptional": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/hoopy": { "version": "0.1.4", @@ -30005,6 +30123,111 @@ } } }, + "@sentry-internal/tracing": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.53.1.tgz", + "integrity": "sha512-a4H4rvVdz0XDGgNfRqc7zg6rMt2P1P05xBmgfIfztYy94Vciw1QMdboNiT7einr8ra8wogdEaK4Pe2AzYAPBJQ==", + "requires": { + "@sentry/core": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/browser": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.53.1.tgz", + "integrity": "sha512-1zas2R6riJaj0k7FoeieCW0SuC7UyKaBGA6jEG2LsgIqyD7IDOlF3BPZ4Yt08GFav0ImpyhGn5Vbrq5JLbeQdw==", + "requires": { + "@sentry-internal/tracing": "7.53.1", + "@sentry/core": "7.53.1", + "@sentry/replay": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/core": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.53.1.tgz", + "integrity": "sha512-DAH8IJNORJJ7kQLqsZuhMkN6cwJjXzFuuUoZor7IIDHIHjtl51W+2F3Stg3+I3ZoKDfJfUNKqhipk2WZjG0FBg==", + "requires": { + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/react": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.53.1.tgz", + "integrity": "sha512-eEOY/peBepSD/nhPn4SU77aYdjQfAI1svOqpG4sbpjaGZU1P6L7+IIGmip8l2T68oPEeKDaiH9Qy/3uxu55B/Q==", + "requires": { + "@sentry/browser": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1", + "hoist-non-react-statics": "^3.3.2", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/replay": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.53.1.tgz", + "integrity": "sha512-5He5JLJiYLeWtXHC53z2ZzfbgAedafbHNZVS4+MBCOtydCk7cnuyJ0gGV6Rfxej/lZSNXZxOdW7HeMhzBtZCxw==", + "requires": { + "@sentry/core": "7.53.1", + "@sentry/types": "7.53.1", + "@sentry/utils": "7.53.1" + } + }, + "@sentry/types": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.53.1.tgz", + "integrity": "sha512-/ijchRIu+jz3+j/zY+7KRPfLSCY14fTx5xujjbOdmEKjmIHQmwPBdszcQm40uwofrR8taV4hbt5MFN+WnjCkCw==" + }, + "@sentry/utils": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.53.1.tgz", + "integrity": "sha512-DKJA1LSUOEv4KOR828MzVuLh+drjeAgzyKgN063OEKmnirgjgRgNNS8wUgwpG0Tn2k6ANZGCwrdfzPeSBxshKg==", + "requires": { + "@sentry/types": "7.53.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -36872,7 +37095,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "devOptional": true, "requires": { "react-is": "^16.7.0" }, @@ -36880,8 +37102,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "devOptional": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, diff --git a/package.json b/package.json index 50e7a9824..15671f899 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@ledgerhq/hw-transport-web-ble": "^6.27.1", "@mui/icons-material": "^5.8.0", "@mui/material": "^5.9.1", + "@sentry/react": "^7.53.1", "@terra-money/feather.js": "^1.0.4", "@terra-money/ledger-station-js": "^1.3.7", "@terra-money/log-finder-ruleset": "^3.0.0", @@ -25,8 +26,8 @@ "@terra-money/terra.js": "^3.1.7", "@terra-money/terra.proto": "^2.0.0", "@terra-money/use-wallet": "4.0.0-beta.3", - "@terra-money/wallet-provider": "4.0.0-beta.3", "@terra-money/wallet-controller": "4.0.0-beta.3", + "@terra-money/wallet-provider": "4.0.0-beta.3", "@tippyjs/react": "^4.2.6", "axios": "^0.27.2", "bech32": "^2.0.0", diff --git a/src/app/InitBankBalance.tsx b/src/app/InitBankBalance.tsx index 9c4976889..55a3bf1f5 100644 --- a/src/app/InitBankBalance.tsx +++ b/src/app/InitBankBalance.tsx @@ -29,16 +29,21 @@ const InitBankBalance = ({ children }: PropsWithChildren<{}>) => { [] as CoinBalance[] ) - native.list.forEach(({ denom }) => { - if (!bankBalance.find((balance) => balance.denom === denom)) { - const token = whitelist[networkName][denom] + native.list.forEach(({ id }) => { + const [chain, denom] = id.split(":") + if ( + !bankBalance.find( + (balance) => balance.denom === denom && balance.chain === chain + ) + ) { + const token = whitelist[networkName][id] if (!token || !token.chains || token.chains.length === 0) return bankBalance.push({ denom, amount: "0", - chain: token.chains[0], + chain, }) } }) diff --git a/src/app/InitChains.tsx b/src/app/InitChains.tsx index fc12f1a0b..b3786f145 100644 --- a/src/app/InitChains.tsx +++ b/src/app/InitChains.tsx @@ -11,10 +11,10 @@ const InitChains = ({ children }: PropsWithChildren<{}>) => { useEffect(() => { axios - .get("/coins.json", { baseURL: STATION_ASSETS }) + .get("/denoms.json", { baseURL: STATION_ASSETS }) .then(({ data }) => setWhitelist(data)) axios - .get("/ibc_denoms.json", { baseURL: STATION_ASSETS }) + .get("/ibc.json", { baseURL: STATION_ASSETS }) .then(({ data }) => setIbcDenoms(data)) axios .get("/station/coins.json", { baseURL: ASSETS }) diff --git a/src/app/sections/Links.tsx b/src/app/sections/Links.tsx index 3e1ae91bb..3676cc659 100644 --- a/src/app/sections/Links.tsx +++ b/src/app/sections/Links.tsx @@ -15,11 +15,10 @@ const Links = () => { const { name } = useTheme() const community = { - medium: "https://medium.com/terra-money", discord: "https://terra.sc/discord", telegram: "https://t.me/TerraNetworkLobby", twitter: "https://twitter.com/terra_money", - github: "https://github.com/terra-money", + github: "https://github.com/terra-money/station", } return ( diff --git a/src/auth/modules/create/RecoverWalletPage.tsx b/src/auth/modules/create/RecoverWalletPage.tsx index b0672920a..7770bc6f5 100644 --- a/src/auth/modules/create/RecoverWalletPage.tsx +++ b/src/auth/modules/create/RecoverWalletPage.tsx @@ -6,7 +6,7 @@ const RecoverWalletPage = () => { const { t } = useTranslation() return ( - + diff --git a/src/components/form/Input.tsx b/src/components/form/Input.tsx index c88104746..489d9d13c 100644 --- a/src/components/form/Input.tsx +++ b/src/components/form/Input.tsx @@ -47,6 +47,7 @@ const Input = forwardRef( {actionButton && ( diff --git a/src/pages/wallet/Coins.tsx b/src/pages/wallet/Coins.tsx index 32134ad3d..7b6b158e1 100644 --- a/src/pages/wallet/Coins.tsx +++ b/src/pages/wallet/Coins.tsx @@ -32,12 +32,17 @@ const Coins = () => { */}
- {coins.map(({ denom, ...item }) => ( + {coins.map(({ denom, chain, ...item }) => ( ))} diff --git a/src/pages/wallet/IbcSendBack.tsx b/src/pages/wallet/IbcSendBack.tsx new file mode 100644 index 000000000..9bd2c7a6c --- /dev/null +++ b/src/pages/wallet/IbcSendBack.tsx @@ -0,0 +1,25 @@ +import { RenderButton } from "types/components" +import { ModalButton } from "components/feedback" +import IbcSendBackTx from "./IbcSendBackTx" + +interface Props { + children: RenderButton + title: string + token: string + chainID: string +} + +const IbcSendBack = ({ + children: renderButton, + title, + token, + chainID, +}: Props) => { + return ( + + + + ) +} + +export default IbcSendBack diff --git a/src/pages/wallet/IbcSendBackTx.module.scss b/src/pages/wallet/IbcSendBackTx.module.scss new file mode 100644 index 000000000..d50cf37cf --- /dev/null +++ b/src/pages/wallet/IbcSendBackTx.module.scss @@ -0,0 +1,63 @@ +@import "mixins"; + +.steps__container { + @include flex(space-between); + margin-bottom: 1.2rem; + + .chain__pill, + .chain__pill__active { + border-radius: 50%; + padding: 8px; + background: hsl( + var(--button-default-bg-h), + var(--button-default-bg-s), + var(--button-default-bg-l) + ); + img { + width: 26px; + } + } + + .chain__pill__active { + background: hsl( + var(--button-primary-bg-h), + var(--button-primary-bg-s), + var(--button-primary-bg-l) + ); + } + + .chain__path, + .chain__path__active { + height: 8px; + border-radius: 4px; + width: 100%; + background: hsl( + var(--button-default-bg-h), + var(--button-default-bg-s), + var(--button-default-bg-l) + ); + color: hsl( + var(--button-primary-bg-h), + var(--button-primary-bg-s), + var(--button-primary-bg-l) + ); + margin: 0 8px; + } + + .chain__path__active { + background: hsl( + var(--button-primary-bg-h), + var(--button-primary-bg-s), + var(--button-primary-bg-l) + ); + } +} + +/* color */ +.success { + color: var(--success); +} + +.danger { + color: var(--danger); +} diff --git a/src/pages/wallet/IbcSendBackTx.tsx b/src/pages/wallet/IbcSendBackTx.tsx new file mode 100644 index 000000000..293553037 --- /dev/null +++ b/src/pages/wallet/IbcSendBackTx.tsx @@ -0,0 +1,347 @@ +import { AccAddress, Coin, MsgTransfer } from "@terra-money/feather.js" +import { toAmount } from "@terra-money/terra-utils" +import { useInterchainAddresses } from "auth/hooks/useAddress" +import { Form, FormItem, Input } from "components/form" +import { useBankBalance } from "data/queries/bank" +import { calculateIBCDenom, useIBCBaseDenom } from "data/queries/ibc" +import { queryKey } from "data/query" +import { useNativeDenoms } from "data/token" +import { useCallback, useEffect, useMemo, useState } from "react" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import Tx from "txs/Tx" +import { CoinInput, getPlaceholder, toInput } from "txs/utils" +import validate from "txs/validate" +import styles from "./IbcSendBackTx.module.scss" +import { useNetwork } from "data/wallet" +import { FlexColumn } from "components/layout" +import { useThemeAnimation } from "data/settings/Theme" +import DoneAllIcon from "@mui/icons-material/DoneAll" +import { LinearProgress } from "@mui/material" +import { useInterchainLCDClient } from "data/queries/lcdClient" +import { useQueryClient } from "react-query" +import ReportIcon from "@mui/icons-material/Report" + +interface Props { + token: string + chainID: string +} + +interface TxValues { + input?: number +} + +function Steps({ + step, + chains, + isLoading, +}: { + step: number + chains: string[] + isLoading: boolean +}) { + const networks = useNetwork() + + return ( +
+ {chains.map((chain, i) => ( + <> + {!!i && ( +
+ {i === step && isLoading && ( + + )} +
+ )} +
+ {chain} +
+ + ))} +
+ ) +} + +function IbcSendBackTx({ token, chainID }: Props) { + const balances = useBankBalance() + const lcd = useInterchainLCDClient() + const readNativeDenom = useNativeDenoms() + const animation = useThemeAnimation() + const { t } = useTranslation() + const addresses = useInterchainAddresses() + const { data: ibcDetails, ...state } = useIBCBaseDenom(token, chainID, true) + const form = useForm({ mode: "onChange" }) + const { register, trigger, watch, setValue, handleSubmit, formState } = form + const { errors } = formState + const { input } = watch() + const queryClient = useQueryClient() + + const [step, setStep] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(undefined) + const [waitUntil, setWaitUntil] = useState< + undefined | { chainID: string; denom: string; balance: number } + >(undefined) + + async function getBalance(denom: string, chainID: string) { + if (!addresses) return 0 + + if (AccAddress.validate(denom)) { + const { balance } = await lcd.wasm.contractQuery<{ balance: Amount }>( + denom, + { balance: { address: addresses[chainID] } } + ) + + return Number(balance ?? 0) + } else { + const [balances] = await lcd.bank.balance(addresses[chainID]) + + const tokenBalance = + balances.toData().find(({ denom: d }) => denom === d)?.amount ?? "0" + + return Number(tokenBalance) + } + } + + useEffect(() => { + // around 3 minutes with a 10 seconds interval + let maxIterations = 18 + + if (waitUntil) { + ;(async () => { + while (maxIterations--) { + console.log(waitUntil) + const tokenBalance = await getBalance( + waitUntil.denom, + waitUntil.chainID + ) + console.log(tokenBalance) + + if (Number(tokenBalance) > waitUntil.balance) { + setWaitUntil(undefined) + setIsLoading(false) + // refetch all balances for the next form + queryClient.invalidateQueries(queryKey.bank.balances) + return + } + + await new Promise((resolve) => setTimeout(resolve, 10_000)) + } + + // if this get's executed, it means that the transaction failed + setWaitUntil(undefined) + setError("Transaction failed.") + })() + } + + return () => { + maxIterations = 0 + } + }, [waitUntil]) // eslint-disable-line react-hooks/exhaustive-deps + + const IBCdenom = useMemo( + () => + ibcDetails && + calculateIBCDenom( + ibcDetails.baseDenom, + ibcDetails.channels + .slice(step) + .reduce( + (acc, cur) => + acc + ? [cur.port, cur.channel, acc].join("/") + : [cur.port, cur.channel].join("/"), + "" + ) + ), + [step, ibcDetails] + ) + + const chains = useMemo( + () => ibcDetails?.chainIDs.slice().reverse() ?? [], + [ibcDetails] + ) + const decimals = readNativeDenom(ibcDetails?.baseDenom ?? "").decimals + + const createTx = useCallback( + ({ input }: TxValues) => { + if (!ibcDetails || !addresses || !IBCdenom || input === undefined) return + + const msgs = [ + new MsgTransfer( + ibcDetails.channels[step].port, + ibcDetails.channels[step].channel, + new Coin(IBCdenom, input * 10 ** decimals), + addresses[chains[step]], + addresses[chains[step + 1]], + undefined, + (Date.now() + 120 * 1000) * 1e6 + ), + ] + + return { msgs, chainID: chains[step] } + }, + [ibcDetails, addresses, step, IBCdenom, chains, decimals] + ) + + const onChangeMax = useCallback( + async (input: number) => { + setValue("input", input) + await trigger("input") + }, + [setValue, trigger] + ) + + const coins = [{ input, denom: IBCdenom }] as CoinInput[] + const amount = toAmount(input, { decimals }) + const balance = + balances.find( + ({ denom, chain }) => denom === IBCdenom && chain === chains[step] + )?.amount ?? "0" + + const tx = { + token: IBCdenom ?? "", + baseDenom: ibcDetails?.baseDenom, + decimals, + amount, + coins, + chain: chains[step], + disabled: false, + balance, + estimationTxValues: { input: 1 }, + createTx, + onChangeMax, + onPost: () => { + const nextDenom = calculateIBCDenom( + ibcDetails?.baseDenom ?? "", + (ibcDetails?.channels ?? []) + .slice(step + 1) + .reduce( + (acc, cur) => + acc + ? [cur.port, cur.channel, acc].join("/") + : [cur.port, cur.channel].join("/"), + "" + ) + ) + + // wait until balance on the other chain is increased + getBalance(nextDenom, chains[step + 1]).then((balance) => + setWaitUntil({ chainID: chains[step + 1], denom: nextDenom, balance }) + ) + + setStep((step) => step + 1) + setIsLoading(true) + }, + hideLoader: true, + taxRequired: true, + queryKeys: [queryKey.bank.balances, queryKey.bank.balance], + gasAdjustment: 2, + } + + function renderForm() { + return ( + // @ts-expect-error + + {({ max, fee, submit }) => ( +
+ + + + + {fee.render()} + + {submit.button} +
+ )} +
+ ) + } + + return ( +
+ + {(() => { + if (error) { + return ( + + +

{error}

+
+ ) + } + + if (state.isLoading || isLoading) { + return ( + + {t("Loading...")} + {state.isLoading &&

{t("Loading...")}

} + {isLoading && ( + <> +

{t("Waiting for on-chain confirmation...")}

+

{t("This may take a few minutes")}

+ + )} +
+ ) + } + + if (step === chains.length - 1) { + return ( + + +

{t("Success!")}

+
+ ) + } + + return renderForm() + })()} +
+ ) +} + +export default IbcSendBackTx diff --git a/src/pages/wallet/SendPage.tsx b/src/pages/wallet/SendPage.tsx index 3cbcae752..7546967ae 100644 --- a/src/pages/wallet/SendPage.tsx +++ b/src/pages/wallet/SendPage.tsx @@ -345,11 +345,13 @@ const SendPage = () => { })} autoFocus > - {availableAssets.map(({ denom, symbol }, i) => ( - - ))} + {availableAssets + .filter(({ symbol }) => !symbol.endsWith("...")) + .map(({ denom, symbol }, i) => ( + + ))} {availableChains && ( diff --git a/src/styles/images/community/Medium.svg b/src/styles/images/community/Medium.svg deleted file mode 100644 index 78af371b4..000000000 --- a/src/styles/images/community/Medium.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/txs/Tx.tsx b/src/txs/Tx.tsx index e0bdfb482..59ffe850d 100644 --- a/src/txs/Tx.tsx +++ b/src/txs/Tx.tsx @@ -48,6 +48,7 @@ import { useNativeDenoms } from "data/token" interface Props { /* Only when the token is paid out of the balance held */ token?: Token + baseDenom?: string decimals?: number amount?: Amount coins?: CoinInput[] @@ -68,6 +69,7 @@ interface Props { /* on tx success */ onPost?: () => void + hideLoader?: boolean onSuccess?: () => void redirectAfterTx?: { label: string; path: string } queryKeys?: QueryKey[] @@ -81,7 +83,8 @@ interface RenderProps { } function Tx(props: Props) { - const { token, decimals, amount, balance, chain } = props + const { token, decimals, amount, balance, chain, baseDenom, hideLoader } = + props const { estimationTxValues, createTx, gasAdjustment: txGasAdjustment } = props const { children, onChangeMax } = props const { onPost, redirectAfterTx, queryKeys, onSuccess } = props @@ -124,9 +127,10 @@ function Tx(props: Props) { const key = { address: addresses?.[chain], - network: networks, + //network: networks, gasAdjustment: gasAdjustment * (txGasAdjustment ?? 1), - msgs: simulationTx?.msgs.map((msg) => msg.toData(isClassic)), + estimationTxValues, + //msgs: simulationTx?.msgs.map((msg) => msg.toData(isClassic)), } const { data: estimatedGas, ...estimatedGasState } = useQuery( [queryKey.tx.create, key, isWalletEmpty], @@ -271,10 +275,10 @@ function Tx(props: Props) { navigate(toPostMultisigTx(unsignedTx)) } else if (wallet) { const { txhash } = await auth.post({ ...tx, fee }, password) - setLatestTx({ txhash, ...latestTxBase }) + !hideLoader && setLatestTx({ txhash, ...latestTxBase }) } else { const { result } = await post({ ...tx, fee }) - setLatestTx({ txhash: result.txhash, ...latestTxBase }) + !hideLoader && setLatestTx({ txhash: result.txhash, ...latestTxBase }) } onPost?.() @@ -325,7 +329,11 @@ function Tx(props: Props) { fontSize="inherit" className={styles.icon} /> - + ) @@ -355,26 +363,45 @@ function Tx(props: Props) { > {availableGasDenoms.map((denom) => ( ))} )} -
{gasFee.amount && }
+
+ {gasFee.amount && ( + + )} +
{balanceAfterTx && ( <>
{t("Balance")}
- +
{t("Balance after tx")}
diff --git a/src/txs/gov/DepositTx.tsx b/src/txs/gov/DepositTx.tsx index 4aab298b7..2f1ff3d46 100644 --- a/src/txs/gov/DepositTx.tsx +++ b/src/txs/gov/DepositTx.tsx @@ -11,7 +11,7 @@ const DepositTx = () => { const { data: proposal, ...state } = useProposal(id, chain) return ( - + diff --git a/src/txs/gov/SubmitProposalTx.tsx b/src/txs/gov/SubmitProposalTx.tsx index 5e7837269..7148273a8 100644 --- a/src/txs/gov/SubmitProposalTx.tsx +++ b/src/txs/gov/SubmitProposalTx.tsx @@ -12,7 +12,7 @@ const SubmitProposalTx = () => { } return ( - + {(chain) => render(chain)} ) diff --git a/src/txs/gov/VoteTx.tsx b/src/txs/gov/VoteTx.tsx index 5c7b9e697..ee46e52fe 100644 --- a/src/txs/gov/VoteTx.tsx +++ b/src/txs/gov/VoteTx.tsx @@ -11,7 +11,7 @@ const VoteTx = () => { const { data: proposal, ...state } = useProposal(id, chain) return ( - + diff --git a/src/txs/stake/StakeTx.tsx b/src/txs/stake/StakeTx.tsx index 355da4152..223dd6a8c 100644 --- a/src/txs/stake/StakeTx.tsx +++ b/src/txs/stake/StakeTx.tsx @@ -109,7 +109,7 @@ const StakeTx = () => { } return ( - + { } return ( - + {render()} ) diff --git a/src/txs/stake/WithdrawRewardsForm.module.scss b/src/txs/stake/WithdrawRewardsForm.module.scss index 98e1e8564..26dde0a6b 100644 --- a/src/txs/stake/WithdrawRewardsForm.module.scss +++ b/src/txs/stake/WithdrawRewardsForm.module.scss @@ -16,7 +16,7 @@ } .button { - font-size: 10px; + font-size: var(--font-size-small); font-weight: var(--bold); padding: 6px 0; } @@ -32,3 +32,23 @@ @include flex(space-between); flex: 1; } + +.title { + padding-bottom: 10px; + + dt, + dd { + font-weight: var(--bold); + } +} + +.actions { + display: flex; + + .button { + color: var(--button-primary-bg); + margin-left: auto; + order: 2; + padding: 0px 5px 0px 0px; + } +} diff --git a/src/txs/stake/WithdrawRewardsForm.tsx b/src/txs/stake/WithdrawRewardsForm.tsx index 870c19418..0b29d19fe 100644 --- a/src/txs/stake/WithdrawRewardsForm.tsx +++ b/src/txs/stake/WithdrawRewardsForm.tsx @@ -2,8 +2,6 @@ import { useCallback, useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { useForm } from "react-hook-form" import BigNumber from "bignumber.js" -import ExpandMoreIcon from "@mui/icons-material/ExpandMore" -import ExpandLessIcon from "@mui/icons-material/ExpandLess" import { Validator, ValAddress } from "@terra-money/feather.js" import { Rewards } from "@terra-money/feather.js" import { MsgWithdrawDelegatorReward } from "@terra-money/feather.js" @@ -11,7 +9,6 @@ import { queryKey } from "data/query" import { useCurrency } from "data/settings/Currency" import { useNetwork } from "data/wallet" import { useMemoizedCalcValue } from "data/queries/coingecko" -import { getFindMoniker } from "data/queries/staking" import { calcRewardsValues } from "data/queries/distribution" import { WithTokenItem } from "data/token" import { ValidatorLink } from "components/general" @@ -22,6 +19,7 @@ import { Empty } from "components/feedback" import styles from "./WithdrawRewardsForm.module.scss" import Tx from "txs/Tx" import { useInterchainAddresses } from "auth/hooks/useAddress" +import { Read } from "components/token" interface Props { rewards: Rewards @@ -35,7 +33,6 @@ const WithdrawRewardsForm = ({ rewards, validators, chain }: Props) => { const addresses = useInterchainAddresses() const address = addresses && addresses[chain] const calcValue = useMemoizedCalcValue() - const findMoniker = getFindMoniker(validators) const { byValidator } = useMemo( () => calcRewardsValues(rewards, currency.id, (coin) => Number(coin.amount)), @@ -62,15 +59,12 @@ const WithdrawRewardsForm = ({ rewards, validators, chain }: Props) => { setState(init(true)) }, [init]) - const selectable = byValidator.length > 1 + const selectable = byValidator.length >= 1 const selected = useMemo( () => Object.keys(state).filter((address) => state[address]), [state] ) - const [isOpen, setIsOpen] = useState(true) - const toggle = () => setIsOpen(!isOpen) - /* calc */ const selectedTotal = selected.reduce>( (prev, address) => { @@ -95,15 +89,6 @@ const WithdrawRewardsForm = ({ rewards, validators, chain }: Props) => { {} ) - const selectedValidatorsText = !selected.length - ? t("Not selected") - : selected.length === 1 - ? findMoniker(selected[0]) - : t("{{moniker}} and {{length}} others", { - moniker: findMoniker(selected[0]), - length: selected.length - 1, - }) - /* form */ const { handleSubmit, reset } = useForm({ mode: "onChange" }) @@ -130,7 +115,7 @@ const WithdrawRewardsForm = ({ rewards, validators, chain }: Props) => { onSuccess: () => reset(), } - if (Object.keys(selectedTotal).length === 0) { + if (!selectable) { return {t("No rewards on selected chain")} } @@ -139,71 +124,56 @@ const WithdrawRewardsForm = ({ rewards, validators, chain }: Props) => { {({ fee, submit }) => (
-
-
{t("Validators")}
-
- {selectable ? ( - - ) : ( - selectedValidatorsText - )} -
-
- - {selectable && isOpen && ( - - - {Object.values(state).some((state) => !state) ? ( - - ) : ( - + ) : ( + + )} + + +
+
{t("Validators")}
+
{t("Rewards")}
+
+
+ {byValidator.map(({ address, list: [{ denom, amount }] }) => { + const checked = state[address] + return ( + + setState({ ...state, [address]: !checked }) + } + key={address} > - {t("Deselect all")} - - )} - - -
- {byValidator.map(({ address, sum }) => { - const checked = state[address] - - return ( - - setState({ ...state, [address]: !checked }) - } - key={address} - > -
+
+
-
-
- ) - })} -
- - )} - - - + +
+ +
+ +
+ ) + })} +
+
+ {selected.length ? : undefined} {Object.entries(selectedTotal).map(([denom, amount]) => ( @@ -221,7 +191,6 @@ const WithdrawRewardsForm = ({ rewards, validators, chain }: Props) => {
- {fee.render()} {submit.button}
diff --git a/src/txs/wasm/InstantiateContractTx.tsx b/src/txs/wasm/InstantiateContractTx.tsx index 503c7f835..2aa2ea407 100644 --- a/src/txs/wasm/InstantiateContractTx.tsx +++ b/src/txs/wasm/InstantiateContractTx.tsx @@ -7,7 +7,7 @@ const InstantiateContractTx = () => { const { t } = useTranslation() return ( - + {(chainID) => ( diff --git a/src/txs/wasm/StoreCodeTx.tsx b/src/txs/wasm/StoreCodeTx.tsx index 923beaa42..72597c27d 100644 --- a/src/txs/wasm/StoreCodeTx.tsx +++ b/src/txs/wasm/StoreCodeTx.tsx @@ -6,7 +6,7 @@ const StoreCodeTx = () => { const { t } = useTranslation() return ( - + {(chainID) => } diff --git a/src/types/settings.d.ts b/src/types/settings.d.ts index 4a4c0170d..36727ce58 100644 --- a/src/types/settings.d.ts +++ b/src/types/settings.d.ts @@ -10,6 +10,7 @@ type CustomTokens = Record interface NativeTokenBasicInfo { denom: CoinDenom + id: string } interface CustomTokensByNetwork { diff --git a/src/utils/chain.ts b/src/utils/chain.ts index 8cab487c1..8112a0776 100644 --- a/src/utils/chain.ts +++ b/src/utils/chain.ts @@ -60,4 +60,5 @@ export const useTerraChainName = () => export const isNativeToken = (denom: string) => !denom.startsWith("ibc/") && !denom.startsWith("factory/") && + !denom.startsWith("gamm/") && !AccAddress.validate(denom) diff --git a/src/utils/localStorage/keys.ts b/src/utils/localStorage/keys.ts index 3da131b69..5023f08e5 100644 --- a/src/utils/localStorage/keys.ts +++ b/src/utils/localStorage/keys.ts @@ -12,7 +12,7 @@ export enum SettingKey { Chain = "Chain", CustomLCD = "CustomLCD", HideLowBalTokens = "HideLowBalTokens", - CustomTokens = "CustomTokens", // Wallet + CustomTokens = "CustomTokensInterchain", // Wallet MinimumValue = "MinimumValue", // Wallet (UST value to show on the list) WithdrawAs = "WithdrawAs", // Rewards (Preferred denom to withdraw rewards) EnabledNetworks = "EnabledNetworks", @@ -27,22 +27,26 @@ export const isSystemDarkMode = export const DefaultTheme = themes[1] -export const DefaultCustomTokensItem = { +export const DefaultCustomTokensItem = (chainID: string) => ({ cw20: [], cw721: [], native: [ { denom: "uluna", + id: `${chainID}:uluna`, }, ], -} +}) + export const DefaultDisplayChains = { mainnet: ["phoenix-1", "osmosis-1"], testnet: ["pisco-1"], classic: ["columbus-5"], } -export const DefaultCustomTokens = { mainnet: DefaultCustomTokensItem } +export const DefaultCustomTokens = { + mainnet: DefaultCustomTokensItem("phoenix-1"), +} export const DefaultSettings = { [SettingKey.Theme]: DefaultTheme, diff --git a/src/utils/sentry/setupSentry.ts b/src/utils/sentry/setupSentry.ts new file mode 100644 index 000000000..25832970d --- /dev/null +++ b/src/utils/sentry/setupSentry.ts @@ -0,0 +1,16 @@ +import * as Sentry from "@sentry/react" + +const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN +const ENVIRONMENT = process.env.NODE_ENV + +export function initSentry() { + Sentry.init({ + dsn: SENTRY_DSN, + integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()], + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + // Session Replay + replaysSessionSampleRate: ENVIRONMENT === "development" ? 1.0 : 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. + }) +}