From 481191121842b5d0ebcd79b1a576d7a0fb3acd90 Mon Sep 17 00:00:00 2001 From: jas2212 <93518923+AbbasAliLokhandwala@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:59:08 +0530 Subject: [PATCH] Proposal activity store (#389) Co-authored-by: sh-wallet <137136919+sh-wallet@users.noreply.github.com> Co-authored-by: agent-dominatrix <128386379+agent-dominatrix@users.noreply.github.com> --- .../buttons/use-max-button/index.tsx | 14 +- .../src/components-v2/form/coin-input.tsx | 44 +-- .../src/components-v2/form/stake-input.tsx | 21 +- .../components-v2/line-graph/chart-options.ts | 4 +- .../src/components-v2/line-graph/index.tsx | 5 + .../components-v2/line-graph/line-graph.tsx | 11 +- .../components-v2/tabs/tabsPanel-2/index.tsx | 12 +- packages/fetch-extension/src/config.ts | 50 +++- packages/fetch-extension/src/config.ui.ts | 215 +++++++++++++- .../src/graphQL/activity-queries.ts | 5 +- .../activity/gov-proposals/activity-row.tsx | 49 +--- .../activity/gov-proposals/index.tsx | 263 ++++++++---------- .../src/pages-new/activity/index.tsx | 26 +- .../src/pages-new/activity/utils.ts | 65 +++++ .../src/pages-new/asset-view/index.tsx | 11 +- .../axelar-bridge-cosmos/coin-input.tsx | 4 +- .../axelar-bridge-cosmos/index.tsx | 1 + .../src/pages-new/main/balances/index.tsx | 2 +- .../pages-new/main/tokens/native-tokens.tsx | 2 +- .../pages-new/main/wallet-details/index.tsx | 29 ++ .../src/pages-new/more/proposals/index.tsx | 3 +- .../proposal-details/vote-dropdown.tsx | 4 +- .../src/pages-new/portfolio/stats/index.tsx | 39 ++- .../src/pages-new/send/send-phase-2.tsx | 2 +- .../src/pages-new/stake/dashboard/index.tsx | 39 ++- .../stake/dashboard/my-stake/my-stakes.tsx | 11 +- .../stake/dashboard/my-stake/my-validator.tsx | 15 +- .../pages-new/validator/delegate/index.tsx | 17 +- .../pages-new/validator/redelegate/index.tsx | 18 +- .../src/pages-new/validator/unstake/index.tsx | 17 +- .../src/utils/fetch-proposals.ts | 6 + packages/fetch-extension/src/utils/format.ts | 13 +- packages/stores/src/account/cosmos.ts | 54 ++++ packages/stores/src/account/utils.ts | 81 ++++++ packages/stores/src/activity/index.ts | 102 ++++++- 35 files changed, 954 insertions(+), 300 deletions(-) diff --git a/packages/fetch-extension/src/components-v2/buttons/use-max-button/index.tsx b/packages/fetch-extension/src/components-v2/buttons/use-max-button/index.tsx index a27f5a5e51..9209c1c09e 100644 --- a/packages/fetch-extension/src/components-v2/buttons/use-max-button/index.tsx +++ b/packages/fetch-extension/src/components-v2/buttons/use-max-button/index.tsx @@ -1,7 +1,8 @@ import React from "react"; import { ButtonV2 } from "../button"; import { IAmountConfig } from "@keplr-wallet/hooks"; -import { useStore } from "../../../stores"; +import { useLanguage } from "../../../languages"; +import { SUPPORTED_LOCALE_FIAT_CURRENCIES } from "../../../config.ui"; export const UseMaxButton = ({ amountConfig, @@ -12,7 +13,10 @@ export const UseMaxButton = ({ isToggleClicked: boolean; setIsToggleClicked: any; }) => { - const { priceStore } = useStore(); + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; + const disableToggleCurrency = + !SUPPORTED_LOCALE_FIAT_CURRENCIES.includes(fiatCurrency); const ChangeButtonElement = () => { return ( @@ -35,7 +39,7 @@ export const UseMaxButton = ({ /> <div>{`Change to ${ !isToggleClicked - ? priceStore.defaultVsCurrency.toUpperCase() + ? fiatCurrency.toUpperCase() : amountConfig.sendCurrency.coinDenom }`}</div> </div> @@ -62,7 +66,9 @@ export const UseMaxButton = ({ border: "1px solid rgba(255,255,255,0.4)", fontSize: "14px", }} - disabled={!amountConfig.sendCurrency["coinGeckoId"]} + disabled={ + !amountConfig.sendCurrency["coinGeckoId"] || disableToggleCurrency + } text={<ChangeButtonElement />} onClick={() => { setIsToggleClicked(!isToggleClicked); diff --git a/packages/fetch-extension/src/components-v2/form/coin-input.tsx b/packages/fetch-extension/src/components-v2/form/coin-input.tsx index 7a60f201d4..ae544cdf9c 100644 --- a/packages/fetch-extension/src/components-v2/form/coin-input.tsx +++ b/packages/fetch-extension/src/components-v2/form/coin-input.tsx @@ -22,6 +22,7 @@ import { useLanguage } from "../../languages"; import { useStore } from "../../stores"; import { Card } from "../card"; import { Dropdown } from "../dropdown"; +import { SUPPORTED_LOCALE_FIAT_CURRENCIES } from "../../config.ui"; export interface CoinInputProps { amountConfig: IAmountConfig; @@ -36,14 +37,16 @@ export interface CoinInputProps { export const CoinInput: FunctionComponent<CoinInputProps> = observer( ({ amountConfig, disableAllBalance }) => { const intl = useIntl(); - const [inputInUsd, setInputInUsd] = useState<string | undefined>(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); const [isToggleClicked, setIsToggleClicked] = useState<boolean>(false); const { priceStore } = useStore(); const language = useLanguage(); const fiatCurrency = language.fiatCurrency; - const convertToUsd = (currency: any) => { + const convertToFiatCurrency = (currency: any) => { const value = priceStore.calculatePrice(currency, fiatCurrency); const inUsd = value && value.shrink(true).maxDecimals(6).toString(); return inUsd; @@ -59,8 +62,8 @@ export const CoinInput: FunctionComponent<CoinInputProps> = observer( amountConfig.sendCurrency, new Int(amountInNumber) ); - const inputValueInUsd = convertToUsd(inputValue); - setInputInUsd(inputValueInUsd); + const inputValueInUsd = convertToFiatCurrency(inputValue); + setInputInFiatCurrency(inputValueInUsd); }, [amountConfig.amount]); const [randomId] = useState(() => { @@ -121,7 +124,6 @@ export const CoinInput: FunctionComponent<CoinInputProps> = observer( const isClicked = () => { setIsToggleClicked(!isToggleClicked); }; - console.log(inputInUsd, isToggleClicked); return ( <React.Fragment> @@ -140,9 +142,10 @@ export const CoinInput: FunctionComponent<CoinInputProps> = observer( )} id={`input-${randomId}`} type="number" + step="any" value={ isToggleClicked === true - ? parseDollarAmount(inputInUsd) + ? parseDollarAmount(inputInFiatCurrency).toString() : parseExponential( amountConfig.amount, amountConfig.sendCurrency.coinDecimals @@ -166,7 +169,7 @@ export const CoinInput: FunctionComponent<CoinInputProps> = observer( return; } isToggleClicked === true - ? parseDollarAmount(inputInUsd) + ? parseDollarAmount(inputInFiatCurrency) : amountConfig.setAmount(e.target.value); } }} @@ -176,14 +179,14 @@ export const CoinInput: FunctionComponent<CoinInputProps> = observer( <span> {isToggleClicked === true - ? "USD" + ? fiatCurrency.toUpperCase() : amountConfig.sendCurrency.coinDenom.split(" ")[0]} </span> </div> <div className={styleCoinInput["amount-usd"]}> {isToggleClicked === true ? `${amountConfig.amount} ${amountConfig.sendCurrency.coinDenom}` - : inputInUsd} + : inputInFiatCurrency} </div> {errorText != null ? ( <div className={styleCoinInput["errorText"]}>{errorText}</div> @@ -194,10 +197,16 @@ export const CoinInput: FunctionComponent<CoinInputProps> = observer( style={{ margin: "0px" }} className={styleCoinInput["widgetButton"]} onClick={isClicked} - disabled + disabled={ + !SUPPORTED_LOCALE_FIAT_CURRENCIES.includes(fiatCurrency) + } > <img src={require("@assets/svg/wireframe/chevron.svg")} alt="" /> - Change to USD + {`Change to ${ + !isToggleClicked + ? fiatCurrency.toUpperCase() + : amountConfig.sendCurrency.coinDenom + }`} </button> {!disableAllBalance ? ( <button @@ -228,7 +237,9 @@ export const TokenSelectorDropdown: React.FC<TokenDropdownProps> = ({ overrideSelectableCurrencies, }) => { const [isOpenTokenSelector, setIsOpenTokenSelector] = useState(false); - const [inputInUsd, setInputInUsd] = useState<string | undefined>(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); const { queriesStore, priceStore } = useStore(); const queryBalances = queriesStore @@ -257,14 +268,14 @@ export const TokenSelectorDropdown: React.FC<TokenDropdownProps> = ({ const language = useLanguage(); const fiatCurrency = language.fiatCurrency; - const convertToUsd = (currency: any) => { + const convertToFiatCurrency = (currency: any) => { const value = priceStore.calculatePrice(currency, fiatCurrency); const inUsd = value && value.shrink(true).maxDecimals(6).toString(); return inUsd; }; useEffect(() => { - const valueInUsd = convertToUsd(balance); - setInputInUsd(valueInUsd); + const valueInUsd = convertToFiatCurrency(balance); + setInputInFiatCurrency(valueInUsd); }, [amountConfig.sendCurrency]); const balancesMap = new Map( @@ -296,7 +307,8 @@ export const TokenSelectorDropdown: React.FC<TokenDropdownProps> = ({ > {" "} {`Available: ${balance.shrink(true).maxDecimals(6).toString()} `} - {inputInUsd && `(${inputInUsd} USD)`} + {inputInFiatCurrency && + `(${inputInFiatCurrency} ${fiatCurrency.toUpperCase()})`} </div> } /> diff --git a/packages/fetch-extension/src/components-v2/form/stake-input.tsx b/packages/fetch-extension/src/components-v2/form/stake-input.tsx index 4bbc5269ac..8ed3a325ee 100644 --- a/packages/fetch-extension/src/components-v2/form/stake-input.tsx +++ b/packages/fetch-extension/src/components-v2/form/stake-input.tsx @@ -13,6 +13,7 @@ import { CoinPretty, Dec, DecUtils, Int } from "@keplr-wallet/unit"; import { InputField } from "@components-v2/input-field"; import { parseDollarAmount } from "@utils/format"; import { useIntl } from "react-intl"; +import { useLanguage } from "../../languages"; export const StakeInput: FunctionComponent<{ label: string; @@ -20,7 +21,11 @@ export const StakeInput: FunctionComponent<{ amountConfig: IAmountConfig; }> = observer(({ label, amountConfig, isToggleClicked }) => { const { priceStore } = useStore(); - const [inputInUsd, setInputInUsd] = useState<string | undefined>(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; const intl = useIntl(); const error = amountConfig.error; @@ -61,8 +66,8 @@ export const StakeInput: FunctionComponent<{ return isDecimal !== null; }; - const convertToUsd = (currency: any) => { - const value = priceStore.calculatePrice(currency); + const convertToFiatCurrency = (currency: any) => { + const value = priceStore.calculatePrice(currency, fiatCurrency); return value && value.shrink(true).maxDecimals(6).toString(); }; @@ -76,8 +81,8 @@ export const StakeInput: FunctionComponent<{ amountConfig.sendCurrency, new Int(amountInNumber) ); - const inputValueInUsd = convertToUsd(inputValue); - setInputInUsd(inputValueInUsd); + const inputValueInUsd = convertToFiatCurrency(inputValue); + setInputInFiatCurrency(inputValueInUsd); }, [amountConfig.amount]); return ( @@ -86,7 +91,7 @@ export const StakeInput: FunctionComponent<{ label={label} value={ isToggleClicked - ? parseDollarAmount(inputInUsd).toString() + ? parseDollarAmount(inputInFiatCurrency).toString() : amountConfig.amount } placeholder={`0`} @@ -99,7 +104,7 @@ export const StakeInput: FunctionComponent<{ }} > {isToggleClicked - ? priceStore.defaultVsCurrency.toUpperCase() + ? fiatCurrency.toUpperCase() : amountConfig.sendCurrency.coinDenom} </div> } @@ -117,7 +122,7 @@ export const StakeInput: FunctionComponent<{ } } isToggleClicked - ? parseDollarAmount(inputInUsd) + ? parseDollarAmount(inputInFiatCurrency) : amountConfig.setAmount(value); } }} diff --git a/packages/fetch-extension/src/components-v2/line-graph/chart-options.ts b/packages/fetch-extension/src/components-v2/line-graph/chart-options.ts index 2c3fe63a56..0e2d12fbb7 100644 --- a/packages/fetch-extension/src/components-v2/line-graph/chart-options.ts +++ b/packages/fetch-extension/src/components-v2/line-graph/chart-options.ts @@ -24,7 +24,9 @@ export const chartOptions: ChartOptions = { label: (tooltipItem: any, data: any) => { const label = data.datasets[tooltipItem.datasetIndex].label || ""; const value = tooltipItem.yLabel || ""; - return ` ${label} ${value} USD`; + return ` ${label} ${value} ${data.datasets[ + tooltipItem.datasetIndex + ].vsCurrency.toUpperCase()}`; }, }, }, diff --git a/packages/fetch-extension/src/components-v2/line-graph/index.tsx b/packages/fetch-extension/src/components-v2/line-graph/index.tsx index 2d55326e56..51b8c02d1b 100644 --- a/packages/fetch-extension/src/components-v2/line-graph/index.tsx +++ b/packages/fetch-extension/src/components-v2/line-graph/index.tsx @@ -2,6 +2,7 @@ import { TabsPanel } from "@components-v2/tabs/tabsPanel-1"; import React, { useState } from "react"; import { LineGraph } from "./line-graph"; import style from "./style.module.scss"; +import { useLanguage } from "../../languages"; interface LineGraphViewProps { tokenName: string | undefined; @@ -39,6 +40,9 @@ export const LineGraphView: React.FC<LineGraphViewProps> = ({ }) => { const [activeTab, setActiveTab] = useState<any>(tabs[0]); const [loading, setLoading] = useState<boolean>(true); + const language = useLanguage(); + + const fiatCurrency = language.fiatCurrency; return ( <div className={style["graph-container"]}> @@ -51,6 +55,7 @@ export const LineGraphView: React.FC<LineGraphViewProps> = ({ setTokenState={setTokenState} loading={loading} setLoading={setLoading} + vsCurrency={fiatCurrency} /> {tokenState?.diff && ( <div style={{ marginBottom: "-18px" }}> diff --git a/packages/fetch-extension/src/components-v2/line-graph/line-graph.tsx b/packages/fetch-extension/src/components-v2/line-graph/line-graph.tsx index c0199eef92..a8195d324b 100644 --- a/packages/fetch-extension/src/components-v2/line-graph/line-graph.tsx +++ b/packages/fetch-extension/src/components-v2/line-graph/line-graph.tsx @@ -11,6 +11,7 @@ interface LineGraphProps { setTokenState: any; loading: boolean; setLoading: any; + vsCurrency: string; } interface PriceData { @@ -24,12 +25,13 @@ export const LineGraph: React.FC<LineGraphProps> = ({ setTokenState, loading, setLoading, + vsCurrency, }) => { const [prices, setPrices] = useState<PriceData[]>([]); const [error, setError] = useState<string | null>(null); const cacheKey = useMemo( - () => `${tokenName}_${duration}`, - [tokenName, duration] + () => `${tokenName}_${duration}_${vsCurrency}`, + [tokenName, duration, vsCurrency] ); const cachedPrices = useMemo(() => { @@ -58,7 +60,7 @@ export const LineGraph: React.FC<LineGraphProps> = ({ newPrices = cachedPrices.newPrices; } else { const apiUrl = `https://api.coingecko.com/api/v3/coins/${tokenName}/market_chart`; - const params = { vs_currency: "usd", days: duration }; + const params = { vs_currency: vsCurrency, days: duration }; const response = await axios.get(apiUrl, { params }); newPrices = response.data.prices.map((price: number[]) => ({ @@ -108,7 +110,7 @@ export const LineGraph: React.FC<LineGraphProps> = ({ }; fetchPrices(); - }, [duration, tokenName, cacheKey, cachedPrices, setTokenState]); + }, [duration, tokenName, vsCurrency, cacheKey, cachedPrices, setTokenState]); const chartData = { labels: prices.map((priceData: any) => { @@ -129,6 +131,7 @@ export const LineGraph: React.FC<LineGraphProps> = ({ priceData.price.toFixed(3).toString() ), fill: false, + vsCurrency, borderColor: (context: any) => { const chart = context.chart; const { ctx, chartArea } = chart; diff --git a/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/index.tsx b/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/index.tsx index 982bf601f5..9375dab7cd 100644 --- a/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/index.tsx +++ b/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/index.tsx @@ -10,6 +10,7 @@ interface Tab { export interface TabsProps { tabs: Tab[]; + activeTabId?: string; showTabsOnBottom?: boolean; setActiveTab?: any; onTabChange?: any; @@ -20,6 +21,7 @@ export interface TabsProps { export const TabsPanel: React.FC<TabsProps> = ({ tabs, + activeTabId, showTabsOnBottom, setActiveTab, onTabChange, @@ -27,7 +29,15 @@ export const TabsPanel: React.FC<TabsProps> = ({ tabHeight, tabStyle, }) => { - const [selectedTab, setSelectedTab] = useState<string | null>(tabs[0].id); + const [selectedTab, setSelectedTab] = useState<string | null>( + activeTabId || tabs[0].id + ); + + useEffect(() => { + if (activeTabId) { + setSelectedTab(activeTabId); + } + }, [activeTabId]); useEffect(() => { if (setActiveTab) { diff --git a/packages/fetch-extension/src/config.ts b/packages/fetch-extension/src/config.ts index 844d5ca8cd..1b13e9dfe5 100644 --- a/packages/fetch-extension/src/config.ts +++ b/packages/fetch-extension/src/config.ts @@ -1,11 +1,55 @@ import { Bech32Address } from "@keplr-wallet/cosmos"; import { ChainInfo } from "@keplr-wallet/types"; -const TEST_NETWORK_CONFIG: ChainInfo = { +const LOCAL_TEST_NETWORK_CONFIG: ChainInfo = { + rpc: "http://localhost:26657", + rest: "http://localhost:1317", + chainId: "test-local", + chainName: "Local Test Network", + hideInUI: true, + stakeCurrency: { + coinDenom: "stake", + coinMinimalDenom: "stake", + coinDecimals: 18, + coinGeckoId: "fetch-ai", + }, + bip44: { + coinType: 118, + }, + bech32Config: Bech32Address.defaultBech32Config("fetch"), + type: "testnet", + currencies: [ + { + coinDenom: "stake", + coinMinimalDenom: "stake", + coinDecimals: 18, + coinGeckoId: "fetch-ai", + }, + ], + feeCurrencies: [ + { + coinDenom: "stake", + coinMinimalDenom: "stake", + coinDecimals: 18, + coinGeckoId: "fetch-ai", + gasPriceStep: { + low: 0, + average: 5000000000, + high: 6250000000, + }, + }, + ], + features: ["cosmwasm"], + walletUrlForStaking: "https://browse-dorado.fetch.ai/validators", + govUrl: "http://localhost:1317/cosmos/gov/v1beta1/proposals", + chainSymbolImageUrl: require("./public/assets/svg/wireframe/dorado.svg"), +}; + +const REMOTE_TEST_NETWORK_CONFIG: ChainInfo = { rpc: "http://34.34.58.246:26657", rest: "http://34.34.58.246:1317", chainId: "test", - chainName: "Local Test Network", + chainName: "Remote Test Network", hideInUI: true, stakeCurrency: { coinDenom: "stake", @@ -2992,7 +3036,7 @@ export enum TXNTYPE { } if (process.env.NODE_ENV !== "production") { - EmbedChainInfos.push(TEST_NETWORK_CONFIG); + EmbedChainInfos.push(LOCAL_TEST_NETWORK_CONFIG, REMOTE_TEST_NETWORK_CONFIG); } export { EmbedChainInfos }; diff --git a/packages/fetch-extension/src/config.ui.ts b/packages/fetch-extension/src/config.ui.ts index 28ca4b5b98..67743c1ba0 100644 --- a/packages/fetch-extension/src/config.ui.ts +++ b/packages/fetch-extension/src/config.ui.ts @@ -49,7 +49,7 @@ export const FiatCurrencies: FiatCurrency[] = [ currency: "eur", symbol: "€", maxDecimals: 2, - locale: "de-DE", + locale: "en-IE", }, { currency: "gbp", @@ -73,7 +73,7 @@ export const FiatCurrencies: FiatCurrency[] = [ currency: "rub", symbol: "₽", maxDecimals: 0, - locale: "ru", + locale: "ru-RU", }, { currency: "krw", @@ -105,6 +105,217 @@ export const FiatCurrencies: FiatCurrency[] = [ maxDecimals: 1, locale: "en-IN", }, + { + currency: "ars", + symbol: "$", + maxDecimals: 2, + locale: "es-AR", + }, + { + currency: "bmd", + symbol: "BD$", + maxDecimals: 2, + locale: "en-BM", + }, + { + currency: "brl", + symbol: "R$", + maxDecimals: 2, + locale: "pt-BR", + }, + { + currency: "chf", + symbol: "CHF", + maxDecimals: 2, + locale: "de-CH", + }, + { + currency: "clp", + symbol: "$", + maxDecimals: 0, + locale: "es-CL", + }, + { + currency: "czk", + symbol: "Kč", + maxDecimals: 2, + locale: "cs-CZ", + }, + { + currency: "dkk", + symbol: "kr", + maxDecimals: 2, + locale: "da-DK", + }, + { + currency: "gel", + symbol: "₾", + maxDecimals: 2, + locale: "ka-GE", + }, + { + currency: "huf", + symbol: "Ft", + maxDecimals: 2, + locale: "hu-HU", + }, + { + currency: "idr", + symbol: "Rp", + maxDecimals: 2, + locale: "id-ID", + }, + { + currency: "ils", + symbol: "₪", + maxDecimals: 2, + locale: "he-IL", + }, + { + currency: "kwd", + symbol: "د.ك", + maxDecimals: 3, + locale: "ar-KW", + }, + { + currency: "lkr", + symbol: "Rs", + maxDecimals: 2, + locale: "si-LK", + }, + { + currency: "mmk", + symbol: "K", + maxDecimals: 0, + locale: "my-MM", + }, + { + currency: "mxn", + symbol: "MX$", + maxDecimals: 2, + locale: "es-MX", + }, + { + currency: "myr", + symbol: "RM", + maxDecimals: 2, + locale: "ms-MY", + }, + { + currency: "ngn", + symbol: "₦", + maxDecimals: 2, + locale: "en-NG", + }, + { + currency: "nok", + symbol: "kr", + maxDecimals: 2, + locale: "nb-NO", + }, + { + currency: "nzd", + symbol: "NZ$", + maxDecimals: 2, + locale: "en-NZ", + }, + { + currency: "php", + symbol: "₱", + maxDecimals: 2, + locale: "en-PH", + }, + { + currency: "pkr", + symbol: "₨", + maxDecimals: 2, + locale: "en-PK", + }, + { + currency: "pln", + symbol: "zł", + maxDecimals: 2, + locale: "pl-PL", + }, + { + currency: "sar", + symbol: "ر.س", + maxDecimals: 2, + locale: "ar-SA", + }, + { + currency: "sek", + symbol: "kr", + maxDecimals: 2, + locale: "sv-SE", + }, + { + currency: "sgd", + symbol: "S$", + maxDecimals: 2, + locale: "en-SG", + }, + { + currency: "thb", + symbol: "฿", + maxDecimals: 2, + locale: "th-TH", + }, + { + currency: "try", + symbol: "₺", + maxDecimals: 2, + locale: "tr-TR", + }, + { + currency: "twd", + symbol: "NT$", + maxDecimals: 2, + locale: "zh-TW", + }, + { + currency: "uah", + symbol: "₴", + maxDecimals: 2, + locale: "uk-UA", + }, + { + currency: "vef", + symbol: "Bs", + maxDecimals: 2, + locale: "es-VE", + }, + { + currency: "vnd", + symbol: "₫", + maxDecimals: 0, + locale: "vi-VN", + }, + { + currency: "zar", + symbol: "R", + maxDecimals: 2, + locale: "en-ZA", + }, +]; + +export const SUPPORTED_LOCALE_FIAT_CURRENCIES = [ + "usd", + "eur", + "gbp", + "cad", + "aud", + "hkd", + "jpy", + "cny", + "inr", + "krw", + "nzd", + "pkr", + "myr", + "sgd", + "thb", + "mxn", ]; export const LanguageToFiatCurrency: TypeLanguageToFiatCurrency = { diff --git a/packages/fetch-extension/src/graphQL/activity-queries.ts b/packages/fetch-extension/src/graphQL/activity-queries.ts index 158902402a..f04a0fadba 100644 --- a/packages/fetch-extension/src/graphQL/activity-queries.ts +++ b/packages/fetch-extension/src/graphQL/activity-queries.ts @@ -60,7 +60,7 @@ export const blocks = `query LatestBlock { export const govProposals = `query govFilters($after: Cursor, $address: String!, $filter: [GovProposalVoteOption!]){ govProposalVotes(after: $after - first: 30 + first: 9999 filter: { option: { in: $filter } voterAddress: { equalTo: $address } @@ -75,6 +75,9 @@ export const govProposals = `query govFilters($after: Cursor, $address: String!, } nodes { + block { + timestamp + } transaction { status log diff --git a/packages/fetch-extension/src/pages-new/activity/gov-proposals/activity-row.tsx b/packages/fetch-extension/src/pages-new/activity/gov-proposals/activity-row.tsx index a2ade8709a..868b957218 100644 --- a/packages/fetch-extension/src/pages-new/activity/gov-proposals/activity-row.tsx +++ b/packages/fetch-extension/src/pages-new/activity/gov-proposals/activity-row.tsx @@ -1,4 +1,3 @@ -// import { formatActivityHash } from "@utils/format"; import React from "react"; import style from "./style.module.scss"; import { observer } from "mobx-react-lite"; @@ -43,51 +42,15 @@ const cardStatusTitle = (details: string) => { } }; -// const getHash = (proposal: any) => { -// if (proposal && proposal.id) { -// return formatActivityHash(proposal.id); -// } -// return null; -// }; - -const getProposalIdFromLogs = (logs: string) => { - let proposalId = ""; - const parsedLogs = JSON.parse(logs); - let log = []; - - if (Array.isArray(parsedLogs) && parsedLogs.length) { - log = parsedLogs?.[0]?.events || []; - } - - const attributes = - log - .map((item: any) => { - if (item.type && item.type === "proposal_vote") { - return item?.attributes; - } - }) - .find((item: any) => item) || []; - - if (Array.isArray(attributes) && attributes.length) { - proposalId = attributes.find( - (item: any) => item.key === "proposal_id" - ).value; - } - - return proposalId; -}; - export const ActivityRow = observer(({ node }: { node: any }) => { const details = node.option; - // const hash = getHash(node); - const { status, id, log } = node.transaction; - const proposalId = getProposalIdFromLogs(log); + const { proposalId, transaction, id } = node; + const { status } = transaction; const { queriesStore, chainStore } = useStore(); const current = chainStore.current; const queries = queriesStore.get(current.chainId); const proposal = queries.cosmos.queryGovernance.getProposal(proposalId || ""); - return ( <React.Fragment> <a @@ -103,7 +66,13 @@ export const ActivityRow = observer(({ node }: { node: any }) => { <div className={style["rowSubtitle"]}> <div>PROPOSAL #{proposalId}</div> <div style={{ fontSize: "14px" }}>●</div> - <div>{status === "Success" ? "Confirmed" : "Failed"}</div> + <div> + {status === "Success" + ? "Confirmed" + : status + ? status + : "Failed"} + </div> </div> </div> <div diff --git a/packages/fetch-extension/src/pages-new/activity/gov-proposals/index.tsx b/packages/fetch-extension/src/pages-new/activity/gov-proposals/index.tsx index 0ab5df9dbb..c9d677d347 100644 --- a/packages/fetch-extension/src/pages-new/activity/gov-proposals/index.tsx +++ b/packages/fetch-extension/src/pages-new/activity/gov-proposals/index.tsx @@ -1,175 +1,130 @@ -import { fetchGovProposalTransactions } from "@graphQL/activity-api"; -import React, { useEffect, useState } from "react"; -import { Button } from "reactstrap"; +import React, { FunctionComponent, useEffect, useState } from "react"; import { useStore } from "../../../stores"; import { FilterDropdown, FilterActivities } from "../filter"; import { ActivityRow } from "./activity-row"; -import style from "../style.module.scss"; import { NoActivity } from "../no-activity"; import { govOptions } from "../utils"; import { CHAIN_ID_DORADO, CHAIN_ID_FETCHHUB } from "../../../config.ui.var"; import { UnsupportedNetwork } from "../unsupported-network"; -import { ErrorActivity } from "../error-activity"; +import { observer } from "mobx-react-lite"; -export const GovProposalsTab = ({ latestBlock }: { latestBlock: any }) => { - const { chainStore, accountStore, analyticsStore } = useStore(); - const current = chainStore.current; - const accountInfo = accountStore.getAccount(current.chainId); - const [isLoading, setIsLoading] = useState(false); - const [isOpen, setIsOpen] = useState(false); +export const GovProposalsTab: FunctionComponent<{ latestBlock: any }> = + observer(({}) => { + const { chainStore, analyticsStore, accountStore, activityStore } = + useStore(); + const current = chainStore.current; + const accountInfo = accountStore.getAccount(current.chainId); + const [isOpen, setIsOpen] = useState(false); + const [filter, setFilter] = useState<string[]>( + govOptions.map((option) => option.value) + ); + const [isSelectAll, setIsSelectAll] = useState(true); + const [isSaveChangesButtonDisabled, setIsSaveChangesButtonDisabled] = + useState(true); + const proposalNodes = activityStore.sortedNodesProposals; - const [loadingRequest, setLoadingRequest] = useState(false); - const [nodes, setNodes] = useState<any>({}); - const [pageInfo, setPageInfo] = useState<any>(); - const [filter, setFilter] = useState<string[]>( - govOptions.map((option) => option.value) - ); - const [isSelectAll, setIsSelectAll] = useState(true); - const [isSaveChangesButtonDisabled, setIsSaveChangesButtonDisabled] = - useState(true); - const [isError, setIsError] = useState(false); + const accountOrChainChanged = + activityStore.getAddress !== accountInfo.bech32Address || + activityStore.getChainId !== current.chainId; - const fetchNodes = async (cursor: any) => { - setIsLoading(true); - try { - const fetchedData = await fetchGovProposalTransactions( - current.chainId, - cursor, - accountInfo.bech32Address, - filter - ); - if (fetchedData) { - const nodeMap: any = {}; - fetchedData.nodes.map((node: any) => { - nodeMap[node.id] = node; - }); - - setPageInfo(fetchedData.pageInfo); - setNodes({ ...nodes, ...nodeMap }); + useEffect(() => { + if (accountOrChainChanged) { + activityStore.setAddress(accountInfo.bech32Address); + activityStore.setChainId(current.chainId); } - } catch (error) { - setIsError(true); - } - - setIsLoading(false); - }; - - useEffect(() => { - fetchNodes(""); - }, []); - useEffect(() => { - fetchNodes(""); - }, [filter, latestBlock]); + /* accountInit is required because in case of a reload, proposalNodes + becomes empty and should be updated with KVstore's saved nodes */ + if (accountInfo.bech32Address !== "") { + activityStore.accountInit(); + } + }, [ + accountInfo.bech32Address, + current.chainId, + accountOrChainChanged, + activityStore, + ]); - const handleClick = async () => { - analyticsStore.logEvent("activity_transactions_click", { - pageName: "Transaction Tab", - }); - setLoadingRequest(true); - await fetchNodes(pageInfo.endCursor); - setLoadingRequest(false); - }; + const handleFilterChange = (selectedFilter: string[]) => { + setFilter(selectedFilter); + analyticsStore.logEvent("activity_filter_click", { + pageName: "Transaction Tab", + }); + }; - const handleFilterChange = (selectedFilter: string[]) => { - setPageInfo(undefined); - setNodes({}); - setFilter(selectedFilter); - analyticsStore.logEvent("activity_filter_click", { - pageName: "Transaction Tab", - }); - }; + const handleCheckboxChange = (value: string) => { + const newFilters = filter.slice(); + if (newFilters.includes(value)) { + setIsSelectAll(false); + setFilter(newFilters.filter((item) => item !== value)); + } else { + setFilter([...newFilters, value]); + setIsSelectAll(filter.length === govOptions.length); + } + setIsSaveChangesButtonDisabled(false); + }; - const handleCheckboxChange = (value: string) => { - const newFilters = filter.slice(); - if (newFilters.includes(value)) { + const handleDeselectClicks = () => { setIsSelectAll(false); - setFilter(newFilters.filter((item) => item !== value)); - } else { - setFilter([...newFilters, value]); - setIsSelectAll(filter.length === govOptions.length); - } - setIsSaveChangesButtonDisabled(false); - }; - - const handleDeselectClicks = () => { - setIsSelectAll(false); - setFilter([]); - setIsSaveChangesButtonDisabled(false); - }; + setFilter([]); + setIsSaveChangesButtonDisabled(false); + }; - const handleSelectClicks = () => { - setIsSelectAll(true); - setFilter(govOptions.map((option) => option.value)); - setIsSaveChangesButtonDisabled(false); - }; + const handleSelectClicks = () => { + setIsSelectAll(true); + setFilter(govOptions.map((option) => option.value)); + setIsSaveChangesButtonDisabled(false); + }; - const handleSaveChanges = () => { - setIsSaveChangesButtonDisabled(true); - handleFilterChange(filter); - fetchNodes(pageInfo.endCursor); - setIsOpen(false); - }; + const handleSaveChanges = () => { + setIsSaveChangesButtonDisabled(true); + handleFilterChange(filter); + setIsOpen(false); + }; - return ( - <React.Fragment> - <FilterDropdown - isOpen={isOpen} - setIsOpen={setIsOpen} - options={govOptions} - selectedFilter={filter} - handleCheckboxChange={handleCheckboxChange} - handleSaveChanges={handleSaveChanges} - isSelectAll={isSelectAll} - handleSelectClicks={handleSelectClicks} - handleDeselectClicks={handleDeselectClicks} - isSaveChangesButtonDisabled={isSaveChangesButtonDisabled} - /> - <FilterActivities - onFilterChange={handleFilterChange} - options={govOptions} - selectedFilter={filter} - setIsOpen={setIsOpen} - isOpen={isOpen} - /> - {current.chainId === CHAIN_ID_FETCHHUB || - current.chainId === CHAIN_ID_DORADO ? ( - isError ? ( - <ErrorActivity /> - ) : Object.keys(nodes).length > 0 ? ( - <React.Fragment> - {Object.values(nodes) - .filter((node: any) => filter.includes(node.option)) - .map((node, index) => ( - <ActivityRow node={node} key={index} /> - ))} - {pageInfo?.hasNextPage && ( - <Button - outline - color="primary" - size="sm" - block - disabled={!pageInfo?.hasNextPage || loadingRequest} - onClick={handleClick} - className="mt-2" - > - Load more{" "} - {loadingRequest && ( - <i className="fas fa-spinner fa-spin ml-2" /> - )} - </Button> - )} - </React.Fragment> - ) : isLoading ? ( - <div className={style["activityMessage"]}>Loading Activities...</div> + return ( + <React.Fragment> + <FilterDropdown + isOpen={isOpen} + setIsOpen={setIsOpen} + options={govOptions} + selectedFilter={filter} + handleCheckboxChange={handleCheckboxChange} + handleSaveChanges={handleSaveChanges} + isSelectAll={isSelectAll} + handleSelectClicks={handleSelectClicks} + handleDeselectClicks={handleDeselectClicks} + isSaveChangesButtonDisabled={isSaveChangesButtonDisabled} + /> + <FilterActivities + onFilterChange={handleFilterChange} + options={govOptions} + selectedFilter={filter} + setIsOpen={setIsOpen} + isOpen={isOpen} + /> + {current.chainId === CHAIN_ID_FETCHHUB || + current.chainId === CHAIN_ID_DORADO || + current.chainId === "test" || + current.chainId === "test-local" ? ( + proposalNodes.length > 0 && + proposalNodes.filter((node: any) => filter.includes(node.option)) + .length > 0 ? ( + <React.Fragment> + {proposalNodes + .filter((node: any) => filter.includes(node.option)) + .map((node: any, index: any) => ( + <ActivityRow node={node} key={index} /> + ))} + </React.Fragment> + ) : ( + <NoActivity /> + ) ) : ( - <NoActivity /> - ) - ) : ( - <UnsupportedNetwork chainID={current.chainName} /> - )} + <UnsupportedNetwork chainID={current.chainName} /> + )} - {filter.length === 0 && !isLoading && <NoActivity />} - </React.Fragment> - ); -}; + {filter.length === 0 && <NoActivity />} + </React.Fragment> + ); + }); diff --git a/packages/fetch-extension/src/pages-new/activity/index.tsx b/packages/fetch-extension/src/pages-new/activity/index.tsx index a49845cd06..34511b9d53 100644 --- a/packages/fetch-extension/src/pages-new/activity/index.tsx +++ b/packages/fetch-extension/src/pages-new/activity/index.tsx @@ -1,9 +1,10 @@ import { TabsPanel } from "@components-v2/tabs/tabsPanel-2"; import { HeaderLayout } from "@layouts-v2/header-layout"; import { observer } from "mobx-react-lite"; -import React, { FunctionComponent, useState } from "react"; +import React, { FunctionComponent, useState, useEffect } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; +import { useSearchParams } from "react-router-dom"; import { useStore } from "../../stores"; import { GovProposalsTab } from "./gov-proposals"; import { NativeTab } from "./native"; @@ -11,10 +12,10 @@ import style from "./style.module.scss"; export const ActivityPage: FunctionComponent = observer(() => { const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const intl = useIntl(); const [latestBlock, _setLatestBlock] = useState<string>(); - const { analyticsStore } = useStore(); - const tab = [ + const tabs = [ { id: "Transactions", component: <NativeTab />, @@ -24,6 +25,22 @@ export const ActivityPage: FunctionComponent = observer(() => { component: <GovProposalsTab latestBlock={latestBlock} />, }, ]; + const [activeTabId, setActiveTabId] = useState(tabs[0].id); + const { analyticsStore } = useStore(); + + useEffect(() => { + // url /activity?tab=Proposals will open gov .proposals tab + // url /activity?tab=Transactions will open transactions tab + const tab = searchParams.get("tab"); + const tabIds = { + Proposals: "Gov Proposals", + Transactions: "Transactions", + }; + + if (tab === "Proposals") { + setActiveTabId(tabIds[tab]); + } + }, [searchParams]); return ( <HeaderLayout @@ -44,7 +61,8 @@ export const ActivityPage: FunctionComponent = observer(() => { { <div style={{ width: "326px" }} className={style["tabContainer"]}> <TabsPanel - tabs={tab} + activeTabId={activeTabId} + tabs={tabs} tabStyle={{ margin: "24px 0px 32px 0px", }} diff --git a/packages/fetch-extension/src/pages-new/activity/utils.ts b/packages/fetch-extension/src/pages-new/activity/utils.ts index f4b3fa0b77..5911e399f1 100644 --- a/packages/fetch-extension/src/pages-new/activity/utils.ts +++ b/packages/fetch-extension/src/pages-new/activity/utils.ts @@ -3,6 +3,7 @@ import { shortenNumber } from "@utils/format"; import sendIcon from "@assets/svg/wireframe/asi-send.svg"; import recieveIcon from "@assets/svg/wireframe/activity-recieve.svg"; import stakeIcon from "@assets/svg/wireframe/asi-staked.svg"; +import { fetchGovProposalTransactions } from "@graphQL/activity-api"; const getAmount = (denom: string, amount: string, chainStore: any) => { const amountCurrency = chainStore.current.currencies.find( @@ -178,3 +179,67 @@ export const calculatePercentages = ( noWithVetoPercentage: noWithVetoPercentage.toFixed(2), }; }; + +export const getProposalIdFromLogs = (logs: string) => { + let proposalId = ""; + const parsedLogs = JSON.parse(logs); + let log = []; + + if (Array.isArray(parsedLogs) && parsedLogs.length) { + log = parsedLogs?.[0]?.events || []; + } + + const attributes = + log + .map((item: any) => { + if (item.type && item.type === "proposal_vote") { + return item?.attributes; + } + }) + .find((item: any) => item) || []; + + if (Array.isArray(attributes) && attributes.length) { + proposalId = attributes.find( + (item: any) => item.key === "proposal_id" + ).value; + } + + return proposalId; +}; + +export const fetchProposalNodes = async ( + cursor: any, + chainId: string, + bech32Address: string +) => { + try { + let parsedNodes: any = []; + // avoid fetching for test networks (remote and local) + if ( + chainId && + chainId !== "test" && + chainId !== "test-local" && + bech32Address + ) { + const fetchedData = await fetchGovProposalTransactions( + chainId, + cursor, + bech32Address, + govOptions.map((option) => option.value) + ); + if (fetchedData) { + parsedNodes = fetchedData.nodes.map((node: any) => ({ + ...node, + proposalId: getProposalIdFromLogs(node.transaction.log), + })); + return parsedNodes; + } else { + return parsedNodes; + } + } else { + return parsedNodes; + } + } catch (error) { + return []; + } +}; diff --git a/packages/fetch-extension/src/pages-new/asset-view/index.tsx b/packages/fetch-extension/src/pages-new/asset-view/index.tsx index bf25a12b1f..6d81d7617a 100644 --- a/packages/fetch-extension/src/pages-new/asset-view/index.tsx +++ b/packages/fetch-extension/src/pages-new/asset-view/index.tsx @@ -10,6 +10,7 @@ import { observer } from "mobx-react-lite"; import { separateNumericAndDenom } from "@utils/format"; import { useStore } from "../../stores"; import { TXNTYPE } from "../../config"; +import { useLanguage } from "../../languages"; export const AssetView = observer(() => { const { activityStore } = useStore(); @@ -20,6 +21,8 @@ export const AssetView = observer(() => { const [balances, setBalances] = useState<any>(); const [assetValues, setAssetValues] = useState<any>(); const navigate = useNavigate(); + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; useEffect(() => { const searchParams = new URLSearchParams(location.search); @@ -81,7 +84,9 @@ export const AssetView = observer(() => { )} <div className={style["name"]}>{tokenInfo?.coinDenom}</div> <div className={style["price-in-usd"]}> - {balances?.balanceInUsd ? `${balances?.balanceInUsd} USD` : "0 USD"} + {balances?.balanceInUsd + ? `${balances?.balanceInUsd} ${fiatCurrency.toUpperCase()}` + : `0 ${fiatCurrency.toUpperCase()}`} </div> {assetValues?.diff && ( @@ -122,8 +127,8 @@ export const AssetView = observer(() => { </div> <div className={style["inUsd"]}> {balances?.balanceInUsd - ? `${balances?.balanceInUsd} USD` - : "0 USD"}{" "} + ? `${balances?.balanceInUsd} ${fiatCurrency.toUpperCase()}` + : `0 ${fiatCurrency.toUpperCase()}`}{" "} </div> </div> </div> diff --git a/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/coin-input.tsx b/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/coin-input.tsx index 59330ac091..dd91a5db3d 100644 --- a/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/coin-input.tsx +++ b/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/coin-input.tsx @@ -11,6 +11,7 @@ interface CoinInputProps { inputInUsd: any; amountError: any; tokenBal: any; + currencySymbol: string; } export const CoinInput: React.FC<CoinInputProps> = ({ amount, @@ -21,6 +22,7 @@ export const CoinInput: React.FC<CoinInputProps> = ({ inputInUsd, amountError, tokenBal, + currencySymbol, }) => { return ( <div> @@ -37,7 +39,7 @@ export const CoinInput: React.FC<CoinInputProps> = ({ disabled={!transferToken || depositAddress} /> <div className={style["amountInUsd"]}> - {inputInUsd && `(${inputInUsd} USD)`} + {inputInUsd && `(${inputInUsd} ${currencySymbol})`} </div> </div> diff --git a/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/index.tsx b/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/index.tsx index 85ae247ab5..d85b625afd 100644 --- a/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/index.tsx +++ b/packages/fetch-extension/src/pages-new/axelar-bridge/axelar-bridge-cosmos/index.tsx @@ -189,6 +189,7 @@ export const AxelarBridgeCosmos = observer(() => { setAmount={setAmount} tokenBal={tokenBal} transferToken={transferToken} + currencySymbol={fiatCurrency.toUpperCase()} /> <Card style={{ background: "rgba(255,255,255,0.1)", marginBottom: "16px" }} diff --git a/packages/fetch-extension/src/pages-new/main/balances/index.tsx b/packages/fetch-extension/src/pages-new/main/balances/index.tsx index 5a0a70cfae..4a8404de48 100644 --- a/packages/fetch-extension/src/pages-new/main/balances/index.tsx +++ b/packages/fetch-extension/src/pages-new/main/balances/index.tsx @@ -140,7 +140,7 @@ export const Balances: React.FC<Props> = observer(({ tokenState }) => { .trim(true) .hideDenom(true) .maxDecimals(6) - .toString()} USD` + .toString()} ${fiatCurrency.toUpperCase()}` )} </div> {tokenState?.diff && ( diff --git a/packages/fetch-extension/src/pages-new/main/tokens/native-tokens.tsx b/packages/fetch-extension/src/pages-new/main/tokens/native-tokens.tsx index 14b627790f..43d7ffca93 100644 --- a/packages/fetch-extension/src/pages-new/main/tokens/native-tokens.tsx +++ b/packages/fetch-extension/src/pages-new/main/tokens/native-tokens.tsx @@ -131,7 +131,7 @@ export const NativeTokens = observer(() => { color: "rgba(255,255,255,0.4)", }} > - USD + {fiatCurrency.toUpperCase()} </span> </div> ) diff --git a/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx b/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx index c29e1face2..e5fc394c1c 100644 --- a/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx +++ b/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx @@ -15,6 +15,7 @@ import { WalletConfig } from "@keplr-wallet/stores/build/chat/user-details"; import { observer } from "mobx-react-lite"; import { txType } from "./constants"; import { Skeleton } from "@components-v2/skeleton-loader"; +import { fetchProposalNodes } from "../../activity/utils"; export const WalletDetailsView = observer( ({ @@ -125,6 +126,34 @@ export const WalletDetailsView = observer( activityStore.getAddress !== accountInfo.bech32Address || activityStore.getChainId !== current.chainId; + useEffect(() => { + /* this is required because accountInit sets the nodes on reload, + so we wait for accountInit to set the proposal nodes and then we + store the proposal votes from api in activity store */ + const timeout = setTimeout(async () => { + const nodes = activityStore.sortedNodesProposals; + if (nodes.length === 0) { + const nodes = await fetchProposalNodes( + "", + current.chainId, + accountInfo.bech32Address + ); + if (nodes.length) { + nodes.forEach((node: any) => activityStore.addProposalNode(node)); + } + } + }, 100); + + return () => { + clearTimeout(timeout); + }; + }, [ + accountInfo.bech32Address, + current.chainId, + accountOrChainChanged, + activityStore, + ]); + useEffect(() => { if (accountOrChainChanged) { activityStore.setAddress(accountInfo.bech32Address); diff --git a/packages/fetch-extension/src/pages-new/more/proposals/index.tsx b/packages/fetch-extension/src/pages-new/more/proposals/index.tsx index cba4ddd9f4..6b2f97827a 100644 --- a/packages/fetch-extension/src/pages-new/more/proposals/index.tsx +++ b/packages/fetch-extension/src/pages-new/more/proposals/index.tsx @@ -154,7 +154,8 @@ export const Proposals = observer(() => { > {current.chainId === CHAIN_ID_FETCHHUB || current.chainId === CHAIN_ID_DORADO || - current.chainId === "test" ? ( + current.chainId === "test" || + current.chainId === "test-local" ? ( isError ? ( <ErrorActivity /> ) : proposals && Object.keys(proposals).length > 0 ? ( diff --git a/packages/fetch-extension/src/pages-new/more/proposals/proposal-details/vote-dropdown.tsx b/packages/fetch-extension/src/pages-new/more/proposals/proposal-details/vote-dropdown.tsx index 49f833bf83..f4fc4eaba8 100644 --- a/packages/fetch-extension/src/pages-new/more/proposals/proposal-details/vote-dropdown.tsx +++ b/packages/fetch-extension/src/pages-new/more/proposals/proposal-details/vote-dropdown.tsx @@ -67,7 +67,7 @@ export const VoteDropdown = ({ proposal }: VoteDropdownProps) => { } ); - navigate(`/gov-proposal/details/${proposal.id}`, { replace: true }); + navigate(`/activity?tab=Proposals`, { replace: true }); } catch (e: any) { analyticsStore.logEvent("vote_txn_broadcasted_fail", { chainId: chainStore.current.chainId, @@ -100,7 +100,7 @@ export const VoteDropdown = ({ proposal }: VoteDropdownProps) => { }, }); navigate(-2); - navigate(`/activity`, { replace: true }); + navigate(`/activity?tab=Proposals`, { replace: true }); } finally { setIsSendingTx(false); } diff --git a/packages/fetch-extension/src/pages-new/portfolio/stats/index.tsx b/packages/fetch-extension/src/pages-new/portfolio/stats/index.tsx index 541e67ecd2..9666cf4c8d 100644 --- a/packages/fetch-extension/src/pages-new/portfolio/stats/index.tsx +++ b/packages/fetch-extension/src/pages-new/portfolio/stats/index.tsx @@ -14,6 +14,7 @@ import { Dropdown } from "@components-v2/dropdown"; import { observer } from "mobx-react-lite"; import { WalletStatus } from "@keplr-wallet/stores"; import { Skeleton } from "@components-v2/skeleton-loader"; +import { useLanguage } from "../../../languages"; export const Stats = observer( ({ @@ -25,6 +26,8 @@ export const Stats = observer( }) => { const navigate = useNavigate(); const notification = useNotification(); + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; const [_isWithdrawingRewards, setIsWithdrawingRewards] = useState(false); @@ -93,9 +96,15 @@ export const Stats = observer( const stakedPercentage = total ? (stakedBalInUI / total) * 100 : 0; const rewardsPercentage = total ? (rewardsBalInUI / total) * 100 : 0; - const stakableInUSD = priceStore.calculatePrice(stakable)?.toString(); - const stakedInUSD = priceStore.calculatePrice(stakedSum)?.toString(); - const rewardsInUSD = priceStore.calculatePrice(stakableReward)?.toString(); + const stakableInFiatCurrency = priceStore + .calculatePrice(stakable, fiatCurrency) + ?.toString(); + const stakedInFiatCurrency = priceStore + .calculatePrice(stakedSum, fiatCurrency) + ?.toString(); + const rewardsInFiatCurrency = priceStore + .calculatePrice(stakableReward, fiatCurrency) + ?.toString(); const doughnutData = { labels: ["Balance", "Staked", "Rewards"], @@ -226,7 +235,8 @@ export const Stats = observer( <div className={style["legend"]}> <img src={ - stakableInUSD !== undefined && stakableInUSD > "$0" + stakableInFiatCurrency !== undefined && + stakableInFiatCurrency > "$0" ? require("@assets/svg/wireframe/legend-light-purple-long.svg") : require("@assets/svg/wireframe/legend-light-purple.svg") } @@ -251,7 +261,8 @@ export const Stats = observer( <Skeleton height="17.5px" /> )} {isLoaded ? ( - stakableInUSD !== undefined && stakableInUSD > "$0" ? ( + stakableInFiatCurrency !== undefined && + stakableInFiatCurrency > "$0" ? ( <div style={{ fontSize: "14px", @@ -259,7 +270,7 @@ export const Stats = observer( color: "rgba(255,255,255,0.6)", }} > - {stakableInUSD} + {stakableInFiatCurrency} </div> ) : null ) : ( @@ -270,7 +281,8 @@ export const Stats = observer( <div className={style["legend"]}> <img src={ - stakedInUSD !== undefined && stakedInUSD > "$0" + stakedInFiatCurrency !== undefined && + stakedInFiatCurrency > "$0" ? require("@assets/svg/wireframe/legend-purple-long.svg") : require("@assets/svg/wireframe/legend-purple.svg") } @@ -296,7 +308,8 @@ export const Stats = observer( <Skeleton height="17.5px" /> )} {isLoaded ? ( - stakedInUSD !== undefined && stakedInUSD > "$0" ? ( + stakedInFiatCurrency !== undefined && + stakedInFiatCurrency > "$0" ? ( <div style={{ fontSize: "14px", @@ -304,7 +317,7 @@ export const Stats = observer( color: "rgba(255,255,255,0.6)", }} > - {stakedInUSD} + {stakedInFiatCurrency} </div> ) : null ) : ( @@ -315,7 +328,8 @@ export const Stats = observer( <div className={style["legend"]}> <img src={ - rewardsInUSD !== undefined && rewardsInUSD > "$0" + rewardsInFiatCurrency !== undefined && + rewardsInFiatCurrency > "$0" ? require("@assets/svg/wireframe/legend-orange-long.svg") : require("@assets/svg/wireframe/legend-orange.svg") } @@ -340,7 +354,8 @@ export const Stats = observer( <Skeleton height="17.5px" /> )} {isLoaded ? ( - rewardsInUSD !== undefined && rewardsInUSD > "$0" ? ( + rewardsInFiatCurrency !== undefined && + rewardsInFiatCurrency > "$0" ? ( <div style={{ fontSize: "14px", @@ -348,7 +363,7 @@ export const Stats = observer( color: "rgba(255,255,255,0.6)", }} > - {rewardsInUSD} + {rewardsInFiatCurrency} </div> ) : null ) : ( diff --git a/packages/fetch-extension/src/pages-new/send/send-phase-2.tsx b/packages/fetch-extension/src/pages-new/send/send-phase-2.tsx index a4f2663a48..805b9222cf 100644 --- a/packages/fetch-extension/src/pages-new/send/send-phase-2.tsx +++ b/packages/fetch-extension/src/pages-new/send/send-phase-2.tsx @@ -182,7 +182,7 @@ export const SendPhase2: React.FC<SendPhase2Props> = observer( new Int(0) ) )}{" "} - USD + {fiatCurrency.toUpperCase()} </div> <div className={style["amount"]}> {parseFloat(sendConfigs.amountConfig.amount) diff --git a/packages/fetch-extension/src/pages-new/stake/dashboard/index.tsx b/packages/fetch-extension/src/pages-new/stake/dashboard/index.tsx index 10628a507e..40888d9590 100644 --- a/packages/fetch-extension/src/pages-new/stake/dashboard/index.tsx +++ b/packages/fetch-extension/src/pages-new/stake/dashboard/index.tsx @@ -12,6 +12,7 @@ import { MyStakes } from "./my-stake/my-stakes"; import { observer } from "mobx-react-lite"; import { WalletStatus } from "@keplr-wallet/stores"; import { Skeleton } from "@components-v2/skeleton-loader"; +import { useLanguage } from "../../../languages"; export const Dashboard = observer(() => { const { chainStore, accountStore, queriesStore, priceStore } = useStore(); @@ -23,6 +24,8 @@ export const Dashboard = observer(() => { accountInfo.bech32Address ); const balanceStakableQuery = balanceQuery.stakable; + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; const queryDelegations = queries.cosmos.queryDelegations.getQueryBech32Address( @@ -79,9 +82,15 @@ export const Dashboard = observer(() => { const stakedPercentage = total ? (stakedBalInUI / total) * 100 : 0; const rewardsPercentage = total ? (rewardsBalInUI / total) * 100 : 0; - const stakableInUSD = priceStore.calculatePrice(stakable)?.toString(); - const stakedInUSD = priceStore.calculatePrice(stakedSum)?.toString(); - const rewardsInUSD = priceStore.calculatePrice(stakableReward)?.toString(); + const stakableInFiatCurrency = priceStore + .calculatePrice(stakable, fiatCurrency) + ?.toString(); + const stakedInFiatCurrency = priceStore + .calculatePrice(stakedSum, fiatCurrency) + ?.toString(); + const rewardsInFiatCurrency = priceStore + .calculatePrice(stakableReward, fiatCurrency) + ?.toString(); const isLoaded = accountInfo.walletStatus === WalletStatus.Loaded && @@ -118,7 +127,8 @@ export const Dashboard = observer(() => { <div className={style["legend"]}> <img src={ - stakableInUSD !== undefined && stakableInUSD > "$0" + stakableInFiatCurrency !== undefined && + stakableInFiatCurrency > "$0" ? require("@assets/svg/wireframe/legend-light-purple-long.svg") : require("@assets/svg/wireframe/legend-light-purple.svg") } @@ -143,7 +153,8 @@ export const Dashboard = observer(() => { <Skeleton height="17.5px" /> )} {isLoaded ? ( - stakableInUSD !== undefined && stakableInUSD > "$0" ? ( + stakableInFiatCurrency !== undefined && + stakableInFiatCurrency > "$0" ? ( <div style={{ fontSize: "14px", @@ -151,7 +162,7 @@ export const Dashboard = observer(() => { color: "rgba(255,255,255,0.6)", }} > - {stakableInUSD} + {stakableInFiatCurrency} </div> ) : null ) : ( @@ -162,7 +173,8 @@ export const Dashboard = observer(() => { <div className={style["legend"]}> <img src={ - stakedInUSD !== undefined && stakedInUSD > "$0" + stakedInFiatCurrency !== undefined && + stakedInFiatCurrency > "$0" ? require("@assets/svg/wireframe/legend-purple-long.svg") : require("@assets/svg/wireframe/legend-purple.svg") } @@ -188,7 +200,8 @@ export const Dashboard = observer(() => { <Skeleton height="17.5px" /> )} {isLoaded ? ( - stakedInUSD !== undefined && stakedInUSD > "$0" ? ( + stakedInFiatCurrency !== undefined && + stakedInFiatCurrency > "$0" ? ( <div style={{ fontSize: "14px", @@ -196,7 +209,7 @@ export const Dashboard = observer(() => { color: "rgba(255,255,255,0.6)", }} > - {stakedInUSD} + {stakedInFiatCurrency} </div> ) : null ) : ( @@ -207,7 +220,8 @@ export const Dashboard = observer(() => { <div className={style["legend"]}> <img src={ - rewardsInUSD !== undefined && rewardsInUSD > "$0" + rewardsInFiatCurrency !== undefined && + rewardsInFiatCurrency > "$0" ? require("@assets/svg/wireframe/legend-orange-long.svg") : require("@assets/svg/wireframe/legend-orange.svg") } @@ -232,7 +246,8 @@ export const Dashboard = observer(() => { <Skeleton height="17.5px" /> )} {isLoaded ? ( - rewardsInUSD !== undefined && rewardsInUSD > "$0" ? ( + rewardsInFiatCurrency !== undefined && + rewardsInFiatCurrency > "$0" ? ( <div style={{ fontSize: "14px", @@ -240,7 +255,7 @@ export const Dashboard = observer(() => { color: "rgba(255,255,255,0.6)", }} > - {rewardsInUSD} + {rewardsInFiatCurrency} </div> ) : null ) : ( diff --git a/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-stakes.tsx b/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-stakes.tsx index dbf69ce73e..aee4e1232d 100644 --- a/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-stakes.tsx +++ b/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-stakes.tsx @@ -22,6 +22,7 @@ import { separateNumericAndDenom } from "@utils/format"; import { Dec } from "@keplr-wallet/unit"; import { TXNTYPE } from "../../../../config"; import { useDropdown } from "@components-v2/dropdown/dropdown-context"; +import { useLanguage } from "../../../../languages"; export const MyStakes = observer( ({ @@ -37,6 +38,9 @@ export const MyStakes = observer( }) => { const navigate = useNavigate(); const notification = useNotification(); + const language = useLanguage(); + + const fiatCurrency = language.fiatCurrency; const [_isWithdrawingRewards, setIsWithdrawingRewards] = useState(false); const { @@ -73,7 +77,8 @@ export const MyStakes = observer( ).stakableReward; const pendingStakableRewardUSD = priceStore.calculatePrice( - pendingStakableReward.shrink(true).maxDecimals(6).trim(true) + pendingStakableReward.shrink(true).maxDecimals(6).trim(true), + fiatCurrency ); const { numericPart: totalNumber } = separateNumericAndDenom( @@ -207,7 +212,9 @@ export const MyStakes = observer( .trim(true) .toString() : totalNumber}{" "} - <span style={{ color: "#556578" }}>USD</span> + <span style={{ color: "#556578" }}> + {fiatCurrency.toUpperCase()} + </span> </span> </div> diff --git a/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-validator.tsx b/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-validator.tsx index 297d710ab1..9cf02df6a1 100644 --- a/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-validator.tsx +++ b/packages/fetch-extension/src/pages-new/stake/dashboard/my-stake/my-validator.tsx @@ -6,10 +6,14 @@ import { Staking } from "@keplr-wallet/stores"; import { useStore } from "../../../../stores"; import { useNavigate } from "react-router"; import { Dec } from "@keplr-wallet/unit"; +import { useLanguage } from "../../../../languages"; export const MyValidator = observer(() => { const { chainStore, accountStore, queriesStore, priceStore, analyticsStore } = useStore(); + const language = useLanguage(); + + const fiatCurrency = language.fiatCurrency; const navigate = useNavigate(); @@ -66,8 +70,9 @@ export const MyValidator = observer(() => { unbondedValidators.getValidatorThumbnail(val.operator_address); const amount = queryDelegations.getDelegationTo(val.operator_address); - const amountUSD = priceStore.calculatePrice( - amount.maxDecimals(5).trim(true).shrink(true) + const amountFiatCurrency = priceStore.calculatePrice( + amount.maxDecimals(5).trim(true).shrink(true), + fiatCurrency ); const reward = queries.cosmos.queryRewards @@ -132,10 +137,10 @@ export const MyValidator = observer(() => { </div> </div> - {amountUSD && ( + {amountFiatCurrency && ( <div className={styles["right-col"]}> <span> - {amountUSD + {amountFiatCurrency .shrink(true) .maxDecimals(6) .trim(true) @@ -143,7 +148,7 @@ export const MyValidator = observer(() => { </span> <span className={styles["validator-currency"]}> {" "} - {priceStore.defaultVsCurrency.toUpperCase()} + {fiatCurrency.toUpperCase()} </span> </div> )} diff --git a/packages/fetch-extension/src/pages-new/validator/delegate/index.tsx b/packages/fetch-extension/src/pages-new/validator/delegate/index.tsx index 0a7bd2c328..75cef59125 100644 --- a/packages/fetch-extension/src/pages-new/validator/delegate/index.tsx +++ b/packages/fetch-extension/src/pages-new/validator/delegate/index.tsx @@ -15,6 +15,7 @@ import { useLocation, useNavigate } from "react-router"; import { Alert, FormGroup } from "reactstrap"; import { TXNTYPE } from "../../../config"; import { useStore } from "../../../stores"; +import { useLanguage } from "../../../languages"; import style from "./style.module.scss"; export const Delegate: FunctionComponent = observer(() => { @@ -22,6 +23,8 @@ export const Delegate: FunctionComponent = observer(() => { const validatorAddress = location.pathname.split("/")[2]; const navigate = useNavigate(); + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; const { chainStore, @@ -34,7 +37,9 @@ export const Delegate: FunctionComponent = observer(() => { const [isToggleClicked, setIsToggleClicked] = useState<boolean>(false); - const [inputInUsd, setInputInUsd] = useState<string | undefined>(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); const account = accountStore.getAccount(chainStore.current.chainId); const queries = queriesStore.get(chainStore.current.chainId); @@ -73,23 +78,23 @@ export const Delegate: FunctionComponent = observer(() => { : new CoinPretty(sendConfigs.amountConfig.sendCurrency, new Int(0)); const convertToUsd = (currency: any) => { - const value = priceStore.calculatePrice(currency); + const value = priceStore.calculatePrice(currency, fiatCurrency); return value && value.shrink(true).maxDecimals(6).toString(); }; useEffect(() => { const inputValueInUsd = convertToUsd(balance); - setInputInUsd(inputValueInUsd); + setInputInFiatCurrency(inputValueInUsd); }, [sendConfigs.amountConfig.amount]); - const Usd = inputInUsd - ? ` (${inputInUsd} ${priceStore.defaultVsCurrency.toUpperCase()})` + const FiatCurrency = inputInFiatCurrency + ? ` (${inputInFiatCurrency} ${fiatCurrency.toUpperCase()})` : ""; const availableBalance = `${balance .trim(true) .shrink(true) .maxDecimals(6) - .toString()}${Usd}`; + .toString()}${FiatCurrency}`; const bondedValidators = queries.cosmos.queryValidators.getQueryStatus( Staking.BondStatus.Bonded diff --git a/packages/fetch-extension/src/pages-new/validator/redelegate/index.tsx b/packages/fetch-extension/src/pages-new/validator/redelegate/index.tsx index 591af97973..8c6f8981a5 100644 --- a/packages/fetch-extension/src/pages-new/validator/redelegate/index.tsx +++ b/packages/fetch-extension/src/pages-new/validator/redelegate/index.tsx @@ -21,6 +21,7 @@ import style from "./style.module.scss"; import { RedelegateValidatorDetail } from "./validator-detail"; import { TXNTYPE } from "../../../config"; import { useIntl } from "react-intl"; +import { useLanguage } from "../../../languages"; type Sort = "APR" | "Voting Power" | "Name"; @@ -28,6 +29,9 @@ export const Redelegate = observer(() => { const location = useLocation(); const validatorAddress = location.pathname.split("/")[2]; + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; + const navigate = useNavigate(); const { chainStore, @@ -103,7 +107,9 @@ export const Redelegate = observer(() => { const { amountConfig, memoConfig, feeConfig } = sendConfigs; const [isToggleClicked, setIsToggleClicked] = useState<boolean>(false); - const [inputInUsd, setInputInUsd] = useState<string | undefined>(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); const stakedAmount = queriesStore .get(amountConfig.chainId) @@ -138,24 +144,24 @@ export const Redelegate = observer(() => { : new CoinPretty(sendConfigs.amountConfig.sendCurrency, new Int(0)); const convertToUsd = (currency: any) => { - const value = priceStore.calculatePrice(currency); + const value = priceStore.calculatePrice(currency, fiatCurrency); return value && value.shrink(true).maxDecimals(6).toString(); }; useEffect(() => { const inputValueInUsd = convertToUsd(balance); - setInputInUsd(inputValueInUsd); + setInputInFiatCurrency(inputValueInUsd); }, [sendConfigs.amountConfig.amount]); - const Usd = inputInUsd - ? ` (${inputInUsd} ${priceStore.defaultVsCurrency.toUpperCase()})` + const FiatCurrency = inputInFiatCurrency + ? ` (${inputInFiatCurrency} ${fiatCurrency.toUpperCase()})` : ""; const availableBalance = `${balance .trim(true) .shrink(true) .maxDecimals(6) - .toString()}${Usd}`; + .toString()}${FiatCurrency}`; const notification = useNotification(); diff --git a/packages/fetch-extension/src/pages-new/validator/unstake/index.tsx b/packages/fetch-extension/src/pages-new/validator/unstake/index.tsx index eb0b261770..72014ec759 100644 --- a/packages/fetch-extension/src/pages-new/validator/unstake/index.tsx +++ b/packages/fetch-extension/src/pages-new/validator/unstake/index.tsx @@ -21,10 +21,13 @@ import { Alert, FormGroup } from "reactstrap"; import { TXNTYPE } from "../../../config"; import { useStore } from "../../../stores"; import style from "./style.module.scss"; +import { useLanguage } from "../../../languages"; export const Unstake = observer(() => { const location = useLocation(); const validatorAddress = location.pathname.split("/")[2]; + const language = useLanguage(); + const fiatCurrency = language.fiatCurrency; const navigate = useNavigate(); const { chainStore, @@ -47,7 +50,9 @@ export const Unstake = observer(() => { const { amountConfig, memoConfig, feeConfig } = sendConfigs; const [isToggleClicked, setIsToggleClicked] = useState<boolean>(false); - const [inputInUsd, setInputInUsd] = useState<string | undefined>(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); const intl = useIntl(); const error = amountConfig.error; @@ -58,7 +63,7 @@ export const Unstake = observer(() => { .getDelegationTo(validatorAddress); const convertToUsd = (currency: any) => { - const value = priceStore.calculatePrice(currency); + const value = priceStore.calculatePrice(currency, fiatCurrency); return value && value.shrink(true).maxDecimals(6).toString(); }; @@ -77,18 +82,18 @@ export const Unstake = observer(() => { useEffect(() => { const inputValueInUsd = convertToUsd(balance); - setInputInUsd(inputValueInUsd); + setInputInFiatCurrency(inputValueInUsd); }, [sendConfigs.amountConfig.amount]); - const Usd = inputInUsd - ? ` (${inputInUsd} ${priceStore.defaultVsCurrency.toUpperCase()})` + const FiatCurrency = inputInFiatCurrency + ? ` (${inputInFiatCurrency} ${fiatCurrency.toUpperCase()})` : ""; const availableBalance = `${balance .trim(true) .shrink(true) .maxDecimals(6) - .toString()}${Usd}`; + .toString()}${FiatCurrency}`; const errorText: string | undefined = useMemo(() => { if (error) { diff --git a/packages/fetch-extension/src/utils/fetch-proposals.ts b/packages/fetch-extension/src/utils/fetch-proposals.ts index 75e1799e96..aa0928933a 100644 --- a/packages/fetch-extension/src/utils/fetch-proposals.ts +++ b/packages/fetch-extension/src/utils/fetch-proposals.ts @@ -13,6 +13,12 @@ export const fetchProposals = async (chainId: string) => { .then((response) => response.data) .catch((e) => console.log(e)); + if (chainId === "test-local") + return await axios + .get("http://localhost:1317/cosmos/gov/v1beta1/proposals") + .then((response) => response.data) + .catch((e) => console.log(e)); + if (chainId === "test") return await axios .get("http://34.34.58.246:1317/cosmos/gov/v1beta1/proposals") diff --git a/packages/fetch-extension/src/utils/format.ts b/packages/fetch-extension/src/utils/format.ts index 3a4627d638..8b477eadfd 100644 --- a/packages/fetch-extension/src/utils/format.ts +++ b/packages/fetch-extension/src/utils/format.ts @@ -131,9 +131,18 @@ export const separateNumericAndDenom = (value: any) => { }; export const parseDollarAmount = (dollarString: any) => { - const match = dollarString.match(/[0-9.]+/); + const match = dollarString.match( + /(?<=\D|\b)(\d{1,2}(?:,\d{2})*(?:,\d{3})*)(?:\.\d+)?(?=\b|\D)/g + ); + let cleanedMatches = []; + if (match) { - return parseFloat(match[0]); + // removes commas from matched result + cleanedMatches = match.map((match: any) => match.replace(/,/g, "")); + } + + if (cleanedMatches && cleanedMatches.length > 0) { + return parseFloat(cleanedMatches[0]); } return NaN; }; diff --git a/packages/stores/src/account/cosmos.ts b/packages/stores/src/account/cosmos.ts index 9323ba52e0..05fd1bf1a3 100644 --- a/packages/stores/src/account/cosmos.ts +++ b/packages/stores/src/account/cosmos.ts @@ -48,8 +48,10 @@ import { MakeTxResponse, ProtoMsgsOrWithAminoMsgs } from "./types"; import { getEip712TypedDataBasedOnChainId, getNodes, + getProposalNode, txEventsWithPreOnFulfill, updateNodeOnTxnCompleted, + updateProposalNodeOnTxnCompleted, } from "./utils"; import { ExtensionOptionsWeb3Tx } from "@keplr-wallet/proto-types/ethermint/types/v1/web3"; import { MsgRevoke } from "@keplr-wallet/proto-types/cosmos/authz/v1beta1/tx"; @@ -88,6 +90,33 @@ export interface Node { }; } +export interface ProposalNode { + type: string; + block: { + timestamp: string; + __typename: string; + }; + transaction: { + status: string; + id: string; + log: []; + __typename: string; + fees: string; + gasUsed: string; + chainId: string; + memo: string; + }; + messages: { + json: string; + typeUrl: string; + __typename: string; + }; + proposalId: string; + option: string | undefined; + id: string; + __typename: string; +} + export const CosmosAccount = { use(options: { msgOptsCreator?: ( @@ -400,6 +429,7 @@ export class CosmosAccountImpl { let txHash: Uint8Array; let signDoc: StdSignDoc; let txId: string; + let proposalNode: ProposalNode; try { if (typeof msgs === "function") { @@ -443,6 +473,17 @@ export class CosmosAccountImpl { }, }; + if (type === "govVote") { + proposalNode = getProposalNode({ + txId, + signDoc, + type, + fee, + memo, + nodes, + }); + this.activityStore.addProposalNode(proposalNode); + } this.activityStore.addNode(newNode); this.activityStore.addPendingTxn({ id: txId, type }); } catch (e: any) { @@ -502,6 +543,13 @@ export class CosmosAccountImpl { .traceTx(txHash) .then((tx) => { txTracer.close(); + if (type === "govVote") { + updateProposalNodeOnTxnCompleted( + tx, + proposalNode, + this.activityStore + ); + } //update node's gas, amount and status on completed updateNodeOnTxnCompleted(type, tx, txId, this.activityStore); this.activityStore.removePendingTxn(txId); @@ -537,6 +585,12 @@ export class CosmosAccountImpl { .catch(() => { this.activityStore.removePendingTxn(txId); this.activityStore.setTxnStatus(txId, "Unconfirmed"); + if (type === "govVote") { + this.activityStore.setProposalTxnStatus( + proposalNode.id, + "Unconfirmed" + ); + } this.base.setTxTypeInProgress(""); this.activityStore.setIsNodeUpdated(true); }); diff --git a/packages/stores/src/account/utils.ts b/packages/stores/src/account/utils.ts index e2113b9f53..12dadcd446 100644 --- a/packages/stores/src/account/utils.ts +++ b/packages/stores/src/account/utils.ts @@ -1,5 +1,6 @@ import { EthermintChainIdHelper } from "@keplr-wallet/cosmos"; import { ProtoMsgsOrWithAminoMsgs } from "./types"; +import { ProposalNode } from "./cosmos"; export function txEventsWithPreOnFulfill( onTxEvents: @@ -294,3 +295,83 @@ export const updateNodeOnTxnCompleted = ( activityStore.setIsNodeUpdated(true); }; + +export const updateProposalNodeOnTxnCompleted = ( + tx: any, + proposalNode: ProposalNode, + activityStore: any +) => { + const txId = proposalNode.id; + + const updatedProposalNode = { + ...proposalNode, + transaction: { + ...proposalNode.transaction, + log: tx.log, + }, + }; + + activityStore.setProposalNode(txId, updatedProposalNode); + + // if txn fails, it will have tx.code. + if (tx.code) { + activityStore.setProposalTxnStatus(txId, "Failed"); + } else { + activityStore.setProposalTxnStatus(txId, "Success"); + } +}; + +export const getProposalNode = ({ + txId, + signDoc, + type, + fee, + memo, + nodes, +}: { + txId: string; + signDoc: any; + type: string; + fee: any; + memo: string; + nodes: Array<any>; +}): ProposalNode => { + const option = Number(signDoc.msgs[0].value.option); + const optionText = (() => { + switch (option) { + case 1: + return "YES"; + case 2: + return "ABSTAIN"; + case 3: + return "NO"; + case 4: + return "NO_WITH_VETO"; + } + })(); + + const proposalNode: ProposalNode = { + type, + block: { + timestamp: new Date().toJSON(), + __typename: "Block", + }, + transaction: { + status: "Pending", + id: txId, + log: [], + fees: JSON.stringify(signDoc.fee.amount), + chainId: signDoc.chain_id, + gasUsed: fee.gas, + memo: memo, + __typename: "Transaction", + }, + messages: nodes[0], + proposalId: signDoc.msgs[0].value.proposal_id, + option: optionText, + id: `${txId}-0`, + __typename: "GovProposalVote", + }; + + return proposalNode; +}; diff --git a/packages/stores/src/activity/index.ts b/packages/stores/src/activity/index.ts index 4a2756bcd3..b354612adc 100644 --- a/packages/stores/src/activity/index.ts +++ b/packages/stores/src/activity/index.ts @@ -1,7 +1,10 @@ import { KVStore, toGenerator } from "@keplr-wallet/common"; import { TendermintTxTracer } from "@keplr-wallet/cosmos"; import { action, flow, makeObservable, observable } from "mobx"; -import { updateNodeOnTxnCompleted } from "../account"; +import { + updateNodeOnTxnCompleted, + updateProposalNodeOnTxnCompleted, +} from "../account"; import { ChainGetter } from "src/common"; enum TXNTYPE { @@ -20,6 +23,9 @@ export class ActivityStore { @observable protected nodes: any = {}; + @observable + protected proposalNodes: any = {}; + @observable protected address: string = ""; @@ -54,6 +60,14 @@ export class ActivityStore { this.saveNodes(); } + // updates or adds new nodes to the proposal list + updateProposalNodes(nodes: any) { + const updatedNodes = { ...this.proposalNodes, ...nodes }; + this.setProposalNodes(updatedNodes); + + this.saveProposalNodes(); + } + getSortedNodesByTimeStamps() { const sortedNodes = Object.values(this.nodes).sort((a: any, b: any) => { if (a.block.timestamp < b.block.timestamp) { @@ -68,6 +82,22 @@ export class ActivityStore { return sortedNodes; } + getSortedProposalNodesByTimeStamps() { + const sortedNodes = Object.values(this.proposalNodes).sort( + (a: any, b: any) => { + if (a.block.timestamp < b.block.timestamp) { + return 1; + } else if (a.block.timestamp > b.block.timestamp) { + return -1; + } else { + return 0; + } + } + ); + + return sortedNodes; + } + @flow *saveNodes() { const currNodes = Object.keys(this.nodes).length > 0 ? this.nodes : {}; @@ -89,6 +119,27 @@ export class ActivityStore { } } + @flow + *saveProposalNodes() { + const currNodes = + Object.keys(this.proposalNodes).length > 0 ? this.proposalNodes : {}; + let oldNodes = yield* toGenerator( + this.kvStore.get<any>( + `extension_gov_proposal_nodes-${this.address}-${this.chainId}` + ) + ); + if (oldNodes === undefined || oldNodes === null) { + oldNodes = {}; + } + + if (Object.values(currNodes).length >= Object.values(oldNodes).length) { + yield this.kvStore.set<any>( + `extension_gov_proposal_nodes-${this.address}-${this.chainId}`, + JSON.parse(JSON.stringify(currNodes)) + ); + } + } + @flow *saveAddress() { yield this.kvStore.set<any>("extension_activity_address", this.address); @@ -152,6 +203,14 @@ export class ActivityStore { ); this.nodes = savedNodes !== undefined ? savedNodes : {}; + const savedProposalNodes = yield* toGenerator( + this.kvStore.get<any>( + `extension_gov_proposal_nodes-${this.address}-${this.chainId}` + ) + ); + this.proposalNodes = + savedProposalNodes !== undefined ? savedProposalNodes : {}; + const savedPendingTxn = yield* toGenerator( this.kvStore.get<any>( `extension_pending_txn-${this.address}-${this.chainId}` @@ -185,6 +244,15 @@ export class ActivityStore { }); }); + Object.values(this.proposalNodes).map((node: any) => { + const txId = node.transaction.id; + const txHash = Uint8Array.from(Buffer.from(txId, "hex")); + txTracer.traceTx(txHash).then(async (tx) => { + updateProposalNodeOnTxnCompleted(tx, this.proposalNodes[node.id], this); + this.removePendingTxn(txId); + }); + }); + if (Object.keys(this.pendingTxn).length === 0) { this.resetPendingTxnTypes(); } @@ -195,6 +263,11 @@ export class ActivityStore { this.updateNodes({ [node.id]: node }); } + @action + addProposalNode(node: any) { + this.updateProposalNodes({ [node.id]: node }); + } + @action addPendingTxn(node: any) { const newNode = { @@ -256,6 +329,15 @@ export class ActivityStore { this.saveNodes(); } + @action + setProposalTxnStatus( + nodeId: any, + status: "Pending" | "Success" | "Failed" | "Unconfirmed" + ) { + this.proposalNodes[nodeId].transaction.status = status; + this.saveProposalNodes(); + } + @action updateTxnBalance(nodeId: any, amount: number) { this.nodes[nodeId].balanceOffset = amount; @@ -297,6 +379,11 @@ export class ActivityStore { this.nodes = nodes; } + @action + setProposalNodes(nodes: any) { + this.proposalNodes = nodes; + } + @action setPendingTxn(nodes: any) { this.pendingTxn = nodes; @@ -314,6 +401,11 @@ export class ActivityStore { this.nodes[id] = node; } + @action + setProposalNode(id: any, node: any) { + this.proposalNodes[id] = node; + } + @action setAddress(address: string) { this.address = address; @@ -335,6 +427,10 @@ export class ActivityStore { return this.nodes; } + get getProposalNodes() { + return this.proposalNodes; + } + get checkIsNodeUpdated() { return this.isNodeUpdated; } @@ -358,4 +454,8 @@ export class ActivityStore { get sortedNodes() { return this.getSortedNodesByTimeStamps(); } + + get sortedNodesProposals() { + return this.getSortedProposalNodesByTimeStamps(); + } }