diff --git a/public/firefox.manifest.json b/public/firefox.manifest.json index 4a7cf55e5..b133a8c9b 100644 --- a/public/firefox.manifest.json +++ b/public/firefox.manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Station Wallet", - "version": "7.1.1", + "version": "7.1.2", "background": { "scripts": ["background.js"], "persistent": true diff --git a/public/manifest.json b/public/manifest.json index 2b7617f37..26e8a8aa6 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Station Wallet", - "version": "7.1.1", + "version": "7.1.2", "background": { "service_worker": "background.js", "type": "module" diff --git a/scripts/contentScript.js b/scripts/contentScript.js index 27e626685..ff7bbc545 100644 --- a/scripts/contentScript.js +++ b/scripts/contentScript.js @@ -37,10 +37,7 @@ function checkWebpage() { } // update every 10min - if ( - !blacklist || - blacklist.updatedAt < Date.now() - 1000 * 60 * 10 - ) { + if (!blacklist || blacklist.updatedAt < Date.now() - 1000 * 60 * 10) { const BLACKLIST_URL = "https://assets.terra.money/blacklist.json" const response = await fetch(BLACKLIST_URL) const list = await response.json() @@ -155,6 +152,26 @@ async function setupStreams() { extensionStream.pipe(pageStream) pageStream.pipe(extensionStream) + + extensionPort.onDisconnect.addListener((port) => { + reconnectStream(pageStream) + }) +} + +async function reconnectStream(pageStream) { + + const extensionPort = extension.runtime.connect({ + name: "TerraStationExtension", + }) + + const extensionStream = new PortStream(extensionPort) + + extensionStream.pipe(pageStream) + pageStream.pipe(extensionStream) + + extensionPort.onDisconnect.addListener((port) => { + reconnectStream(pageStream) + }) } /** diff --git a/src/app/InitChains.tsx b/src/app/InitChains.tsx index c50741be0..fc12f1a0b 100644 --- a/src/app/InitChains.tsx +++ b/src/app/InitChains.tsx @@ -2,40 +2,31 @@ import axios from "axios" import { ASSETS, STATION_ASSETS } from "config/constants" import { WhitelistProvider, WhitelistData } from "data/queries/chains" import { PropsWithChildren, useEffect, useState } from "react" -import NetworkLoading from "./NetworkLoading" const InitChains = ({ children }: PropsWithChildren<{}>) => { - const [data, setData] = useState() - useEffect(() => { - ;(async () => { - const [whitelist, ibcDenoms, legacyWhitelist] = await Promise.all([ - (async () => { - const { data } = await axios.get("/coins.json", { - baseURL: STATION_ASSETS, - }) - return data - })(), - (async () => { - const { data } = await axios.get("/ibc_denoms.json", { - baseURL: STATION_ASSETS, - }) - return data - })(), - (async () => { - const { data } = await axios.get("/station/coins.json", { - baseURL: ASSETS, - }) - return data - })(), - ]) + const [whitelist, setWhitelist] = useState() + const [ibcDenoms, setIbcDenoms] = useState() + const [legacyWhitelist, setLegacyWhitelist] = + useState() - setData({ whitelist, ibcDenoms, legacyWhitelist }) - })() + useEffect(() => { + axios + .get("/coins.json", { baseURL: STATION_ASSETS }) + .then(({ data }) => setWhitelist(data)) + axios + .get("/ibc_denoms.json", { baseURL: STATION_ASSETS }) + .then(({ data }) => setIbcDenoms(data)) + axios + .get("/station/coins.json", { baseURL: ASSETS }) + .then(({ data }) => setLegacyWhitelist(data)) }, []) - if (!data) return - - return {children} + if (!(whitelist && ibcDenoms && legacyWhitelist)) return null + return ( + + {children} + + ) } export default InitChains diff --git a/src/app/InitNetworks.tsx b/src/app/InitNetworks.tsx index 4495de4c8..c14e189b9 100644 --- a/src/app/InitNetworks.tsx +++ b/src/app/InitNetworks.tsx @@ -2,10 +2,10 @@ import { PropsWithChildren, useEffect, useState } from "react" import axios from "axios" import { STATION_ASSETS } from "config/constants" import createContext from "utils/createContext" -import NetworkLoading from "./NetworkLoading" -import { incomingRequest } from "extension/utils" -import { randomAddress } from "utils/bech32" import { useCustomLCDs } from "utils/localStorage" +import { useValidNetworks } from "data/queries/tendermint" +import { WithFetching } from "components/feedback" +import { combineState } from "data/query" export const [useNetworks, NetworksProvider] = createContext<{ networks: InterchainNetworks @@ -15,7 +15,6 @@ export const [useNetworks, NetworksProvider] = createContext<{ const InitNetworks = ({ children }: PropsWithChildren<{}>) => { const [networks, setNetworks] = useState() - const [enabledNetworks, setEnabledNetworks] = useState([]) const { customLCDs } = useCustomLCDs() useEffect(() => { @@ -26,94 +25,59 @@ const InitNetworks = ({ children }: PropsWithChildren<{}>) => { baseURL: STATION_ASSETS, } ) - setNetworks(chains) } fetchChains() }, []) - useEffect(() => { - const testChains = async () => { - if (!networks) return - const testBase = { + const testBase = networks + ? Object.values({ ...networks.mainnet, ...networks.testnet, ...networks.classic, - //...networks.localterra, - } - - // skip network check if there is an incoming request - if (await incomingRequest()) { - setEnabledNetworks(Object.keys(testBase)) - return - } + }).map((chain) => { + const lcd = customLCDs[chain.chainID] ?? chain.lcd + return { ...chain, lcd } + }) + : [] - const stored = localStorage.getItem("enabledNetworks") - const cached = stored && JSON.parse(stored) - - if (cached && cached.time > Date.now() - 10 * 60 * 1000) { - setEnabledNetworks(cached.networks) - return - } - - const result = await Promise.all( - Object.values(testBase).map(async (network) => { - if (network.prefix === "terra") return network.chainID - try { - const { data } = await axios.get( - `/cosmos/bank/v1beta1/balances/${randomAddress(network.prefix)}`, - { - baseURL: customLCDs[network.chainID] || network.lcd, - timeout: 3_000, - } - ) - return Array.isArray(data.balances) && network.chainID - } catch (e) { - console.error(e) - return null - } - }) - ) + const validationResult = useValidNetworks(testBase) - localStorage.setItem( - "enabledNetworks", - JSON.stringify({ - time: Date.now(), - networks: result.filter((r) => typeof r === "string") as string[], - }) - ) - setEnabledNetworks( - result.filter((r) => typeof r === "string") as string[] - ) - } - - testChains() - }, [networks]) // eslint-disable-line + const validNetworks = validationResult.reduce( + (acc, { data }) => (data ? [...acc, data] : acc), + [] as string[] + ) + const validationState = combineState(...validationResult) - if (!networks || !enabledNetworks.length) return + if (!networks) return null return ( - - Object.fromEntries( - Object.entries(networks).filter( - ([chainID]) => - chainID === "localterra" || enabledNetworks.includes(chainID) - ) - ), - filterDisabledNetworks: (networks) => - Object.fromEntries( - Object.entries(networks).filter( - ([chainID]) => !enabledNetworks.includes(chainID) - ) - ), - }} - > - {children} - + + {(progress) => ( + + Object.fromEntries( + Object.entries(networks).filter( + ([chainID]) => + chainID === "localterra" || validNetworks.includes(chainID) + ) + ), + filterDisabledNetworks: (networks) => + Object.fromEntries( + Object.entries(networks).filter( + ([chainID]) => !validNetworks.includes(chainID) + ) + ), + }} + > + {progress} + {children} + + )} + ) } diff --git a/src/app/InitQueryClient.tsx b/src/app/InitQueryClient.tsx new file mode 100644 index 000000000..85b68bc95 --- /dev/null +++ b/src/app/InitQueryClient.tsx @@ -0,0 +1,25 @@ +import { useNetworkName } from "data/wallet" +import { PropsWithChildren, useMemo } from "react" +import { QueryClient, QueryClientProvider } from "react-query" + +const InitQueryClient = ({ children }: PropsWithChildren<{}>) => { + const queryClient = useQueryClient() + const networkName = useNetworkName() + + return ( + + {children} + + ) +} + +const useQueryClient = () => { + const name = useNetworkName() + + return useMemo(() => { + if (!name) throw new Error() + return new QueryClient() + }, [name]) +} + +export default InitQueryClient diff --git a/src/app/InitWallet.tsx b/src/app/InitWallet.tsx index 7aa2b22e2..451d8c026 100644 --- a/src/app/InitWallet.tsx +++ b/src/app/InitWallet.tsx @@ -1,19 +1,14 @@ -import { PropsWithChildren, useEffect, useMemo } from "react" -import { QueryClient, QueryClientProvider } from "react-query" -import { useNetworkName } from "data/wallet" +import { PropsWithChildren, useEffect } from "react" import { isWallet, useAuth } from "auth" import Online from "./containers/Online" const InitWallet = ({ children }: PropsWithChildren<{}>) => { useOnNetworkChange() - const queryClient = useQueryClient() - const networkName = useNetworkName() - return ( - + <> {children} - + ) } @@ -29,12 +24,3 @@ const useOnNetworkChange = () => { if (shouldDisconnect) disconnect() }, [disconnect, shouldDisconnect]) } - -const useQueryClient = () => { - const name = useNetworkName() - - return useMemo(() => { - if (!name) throw new Error() - return new QueryClient() - }, [name]) -} diff --git a/src/app/sections/LCDSetting.tsx b/src/app/sections/LCDSetting.tsx index db6821a91..d844ded53 100644 --- a/src/app/sections/LCDSetting.tsx +++ b/src/app/sections/LCDSetting.tsx @@ -1,6 +1,6 @@ import { useNetworkName, useNetworkOptions } from "data/wallet" import { useForm } from "react-hook-form" -import { Form, FormItem, Input, Select } from "components/form" +import { Form, FormItem, Input } from "components/form" import { useTranslation } from "react-i18next" import { useNetworks } from "app/InitNetworks" import ChainSelector from "components/form/ChainSelector" diff --git a/src/config/constants.ts b/src/config/constants.ts index cd2b968bb..55f0a85e0 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -42,3 +42,5 @@ export const TERRASWAP_COMMISSION_RATE = 0.003 export const SAMPLE_ADDRESS = "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v" export const CURRENCY_KEY = "e484bb7eb1a1cb1471fd5ee925e9b1bc" + +export const VALIDATION_TIMEOUT = 3_000 diff --git a/src/data/queries/coingecko.ts b/src/data/queries/coingecko.ts index 769638dc3..db1bb0cef 100644 --- a/src/data/queries/coingecko.ts +++ b/src/data/queries/coingecko.ts @@ -44,6 +44,12 @@ const AXELAR_TOKENS: Record = { "ibc/CBF67A2BCF6CAE343FDF251E510C8E18C361FC02B23430C121116E0811835DEF": "uusdt", } +const STAKED_TOKENS: Record = { + terra1jltsv4zjps5veugu6xc0gkurrjx33klhyxse80hy8pszzvhslx0s2n7jkk: "sORD", + terra1lertn5hx2gpw940a0sspds6kydja3c07x0mfg0xu66gvu9p4l30q7ttd2p: "sCOR", + terra15rqy5xh7sclu3yltuz8ndl8lzudcqcv3laldxxsxaph085v6mdpqdjrucv: "sATR", + terra14y9aa87v4mjvpf0vu8xm7nvldvjvk4h3wly2240u0586j4l6qm2q7ngp7t: "sHAR", +} export const useExchangeRates = () => { const currency = useCurrency() @@ -104,6 +110,16 @@ export const useExchangeRates = () => { } }) + // add staked tokens and set price to 100 + Object.entries(STAKED_TOKENS).forEach(([key]) => { + if (!priceObject[key]) { + priceObject[key] = { + price: 100, + change: 0, + } + } + }) + return priceObject }, { ...RefetchOptions.DEFAULT } diff --git a/src/data/queries/tendermint.ts b/src/data/queries/tendermint.ts index 473bae235..30342fd6b 100644 --- a/src/data/queries/tendermint.ts +++ b/src/data/queries/tendermint.ts @@ -1,7 +1,9 @@ -import { useQuery } from "react-query" +import { useQueries, useQuery } from "react-query" import axios from "axios" import { queryKey, RefetchOptions } from "../query" import { useNetworks } from "app/InitNetworks" +import { VALIDATION_TIMEOUT } from "config/constants" +import { randomAddress } from "utils/bech32" export const useLocalNodeInfo = (chainID: string) => { const { networks } = useNetworks() @@ -67,3 +69,33 @@ export const useValidateLCD = ( { ...RefetchOptions.INFINITY, enabled } ) } + +interface Network { + chainID: string + prefix: string + lcd: string +} + +export const useValidNetworks = (networks: Network[]) => { + return 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, + } + ) + + if (Array.isArray(data.balances)) return chainID + }, + ...RefetchOptions.INFINITY, + } + }) + ) +} diff --git a/src/index.tsx b/src/index.tsx index b2437889d..7bf6b0cda 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,6 +20,7 @@ import ElectronVersion from "app/ElectronVersion" import App from "extension/App" import InitChains from "app/InitChains" import WithNodeInfo from "app/WithNodeInfo" +import InitQueryClient from "app/InitQueryClient" const connectorOpts = { bridge: BRIDGE } @@ -30,17 +31,19 @@ getChainOptions().then((chainOptions) => - - - - - - - - - - - + + + + + + + + + + + + + {debug.query && } diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts index ecf9033d4..a1c52dbfe 100644 --- a/src/utils/localStorage.ts +++ b/src/utils/localStorage.ts @@ -21,6 +21,8 @@ export enum SettingKey { CustomTokens = "CustomTokens", // Wallet MinimumValue = "MinimumValue", // Wallet (UST value to show on the list) WithdrawAs = "WithdrawAs", // Rewards (Preferred denom to withdraw rewards) + EnabledNetworks = "EnabledNetworks", + NetworkCacheTime = "NetworkCacheTime", } const isSystemDarkMode = @@ -52,10 +54,12 @@ export const DefaultSettings = { [SettingKey.AddressBook]: [] as AddressBook[], [SettingKey.CustomTokens]: DefaultCustomTokens as CustomTokens, [SettingKey.MinimumValue]: 0, + [SettingKey.NetworkCacheTime]: 0, [SettingKey.HideNonWhitelistTokens]: true, [SettingKey.HideLowBalTokens]: true, [SettingKey.WithdrawAs]: "", [SettingKey.Network]: "", + [SettingKey.EnabledNetworks]: { time: 0, networks: [] as string[] }, [SettingKey.CustomLCD]: {}, }