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 = ({ />
{`Change to ${ !isToggleClicked - ? priceStore.defaultVsCurrency.toUpperCase() + ? fiatCurrency.toUpperCase() : amountConfig.sendCurrency.coinDenom }`}
@@ -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={} 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 = observer( ({ amountConfig, disableAllBalance }) => { const intl = useIntl(); - const [inputInUsd, setInputInUsd] = useState(""); + const [inputInFiatCurrency, setInputInFiatCurrency] = useState< + string | undefined + >(""); const [isToggleClicked, setIsToggleClicked] = useState(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 = 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 = observer( const isClicked = () => { setIsToggleClicked(!isToggleClicked); }; - console.log(inputInUsd, isToggleClicked); return ( @@ -140,9 +142,10 @@ export const CoinInput: FunctionComponent = 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 = observer( return; } isToggleClicked === true - ? parseDollarAmount(inputInUsd) + ? parseDollarAmount(inputInFiatCurrency) : amountConfig.setAmount(e.target.value); } }} @@ -176,14 +179,14 @@ export const CoinInput: FunctionComponent = observer( {isToggleClicked === true - ? "USD" + ? fiatCurrency.toUpperCase() : amountConfig.sendCurrency.coinDenom.split(" ")[0]}
{isToggleClicked === true ? `${amountConfig.amount} ${amountConfig.sendCurrency.coinDenom}` - : inputInUsd} + : inputInFiatCurrency}
{errorText != null ? (
{errorText}
@@ -194,10 +197,16 @@ export const CoinInput: FunctionComponent = observer( style={{ margin: "0px" }} className={styleCoinInput["widgetButton"]} onClick={isClicked} - disabled + disabled={ + !SUPPORTED_LOCALE_FIAT_CURRENCIES.includes(fiatCurrency) + } > - Change to USD + {`Change to ${ + !isToggleClicked + ? fiatCurrency.toUpperCase() + : amountConfig.sendCurrency.coinDenom + }`} {!disableAllBalance ? ( - )} -
- ) : isLoading ? ( -
Loading Activities...
+ return ( + + + + {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 ? ( + + {proposalNodes + .filter((node: any) => filter.includes(node.option)) + .map((node: any, index: any) => ( + + ))} + + ) : ( + + ) ) : ( - - ) - ) : ( - - )} + + )} - {filter.length === 0 && !isLoading && } - - ); -}; + {filter.length === 0 && } + + ); + }); 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(); - const { analyticsStore } = useStore(); - const tab = [ + const tabs = [ { id: "Transactions", component: , @@ -24,6 +25,22 @@ export const ActivityPage: FunctionComponent = observer(() => { component: , }, ]; + 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 ( { {
{ 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(); const [assetValues, setAssetValues] = useState(); 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(() => { )}
{tokenInfo?.coinDenom}
- {balances?.balanceInUsd ? `${balances?.balanceInUsd} USD` : "0 USD"} + {balances?.balanceInUsd + ? `${balances?.balanceInUsd} ${fiatCurrency.toUpperCase()}` + : `0 ${fiatCurrency.toUpperCase()}`}
{assetValues?.diff && ( @@ -122,8 +127,8 @@ export const AssetView = observer(() => {
{balances?.balanceInUsd - ? `${balances?.balanceInUsd} USD` - : "0 USD"}{" "} + ? `${balances?.balanceInUsd} ${fiatCurrency.toUpperCase()}` + : `0 ${fiatCurrency.toUpperCase()}`}{" "}
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 = ({ amount, @@ -21,6 +22,7 @@ export const CoinInput: React.FC = ({ inputInUsd, amountError, tokenBal, + currencySymbol, }) => { return (
@@ -37,7 +39,7 @@ export const CoinInput: React.FC = ({ disabled={!transferToken || depositAddress} />
- {inputInUsd && `(${inputInUsd} USD)`} + {inputInUsd && `(${inputInUsd} ${currencySymbol})`}
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()} /> = observer(({ tokenState }) => { .trim(true) .hideDenom(true) .maxDecimals(6) - .toString()} USD` + .toString()} ${fiatCurrency.toUpperCase()}` )} {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()} ) 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 ? ( ) : 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(
"$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( )} {isLoaded ? ( - stakableInUSD !== undefined && stakableInUSD > "$0" ? ( + stakableInFiatCurrency !== undefined && + stakableInFiatCurrency > "$0" ? (
- {stakableInUSD} + {stakableInFiatCurrency}
) : null ) : ( @@ -270,7 +281,8 @@ export const Stats = observer(
"$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( )} {isLoaded ? ( - stakedInUSD !== undefined && stakedInUSD > "$0" ? ( + stakedInFiatCurrency !== undefined && + stakedInFiatCurrency > "$0" ? (
- {stakedInUSD} + {stakedInFiatCurrency}
) : null ) : ( @@ -315,7 +328,8 @@ export const Stats = observer(
"$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( )} {isLoaded ? ( - rewardsInUSD !== undefined && rewardsInUSD > "$0" ? ( + rewardsInFiatCurrency !== undefined && + rewardsInFiatCurrency > "$0" ? (
- {rewardsInUSD} + {rewardsInFiatCurrency}
) : 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 = observer( new Int(0) ) )}{" "} - USD + {fiatCurrency.toUpperCase()}
{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(() => {
"$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(() => { )} {isLoaded ? ( - stakableInUSD !== undefined && stakableInUSD > "$0" ? ( + stakableInFiatCurrency !== undefined && + stakableInFiatCurrency > "$0" ? (
{ color: "rgba(255,255,255,0.6)", }} > - {stakableInUSD} + {stakableInFiatCurrency}
) : null ) : ( @@ -162,7 +173,8 @@ export const Dashboard = observer(() => {
"$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(() => { )} {isLoaded ? ( - stakedInUSD !== undefined && stakedInUSD > "$0" ? ( + stakedInFiatCurrency !== undefined && + stakedInFiatCurrency > "$0" ? (
{ color: "rgba(255,255,255,0.6)", }} > - {stakedInUSD} + {stakedInFiatCurrency}
) : null ) : ( @@ -207,7 +220,8 @@ export const Dashboard = observer(() => {
"$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(() => { )} {isLoaded ? ( - rewardsInUSD !== undefined && rewardsInUSD > "$0" ? ( + rewardsInFiatCurrency !== undefined && + rewardsInFiatCurrency > "$0" ? (
{ color: "rgba(255,255,255,0.6)", }} > - {rewardsInUSD} + {rewardsInFiatCurrency}
) : 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}{" "} - USD + + {fiatCurrency.toUpperCase()} +
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(() => {
- {amountUSD && ( + {amountFiatCurrency && (
- {amountUSD + {amountFiatCurrency .shrink(true) .maxDecimals(6) .trim(true) @@ -143,7 +148,7 @@ export const MyValidator = observer(() => { {" "} - {priceStore.defaultVsCurrency.toUpperCase()} + {fiatCurrency.toUpperCase()}
)} 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(false); - const [inputInUsd, setInputInUsd] = useState(""); + 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(false); - const [inputInUsd, setInputInUsd] = useState(""); + 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(false); - const [inputInUsd, setInputInUsd] = useState(""); + 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; +}): 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( + `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( + `extension_gov_proposal_nodes-${this.address}-${this.chainId}`, + JSON.parse(JSON.stringify(currNodes)) + ); + } + } + @flow *saveAddress() { yield this.kvStore.set("extension_activity_address", this.address); @@ -152,6 +203,14 @@ export class ActivityStore { ); this.nodes = savedNodes !== undefined ? savedNodes : {}; + const savedProposalNodes = yield* toGenerator( + this.kvStore.get( + `extension_gov_proposal_nodes-${this.address}-${this.chainId}` + ) + ); + this.proposalNodes = + savedProposalNodes !== undefined ? savedProposalNodes : {}; + const savedPendingTxn = yield* toGenerator( this.kvStore.get( `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(); + } }