= ({ epochProgress }) => {
+ const strings = useStrings();
+
+ // Days remaining
+ const days = moment(epochProgress.endEpochDateTime).diff(moment(), 'days');
+
+ return (
+
+
+
+ {strings.epochProgress}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default EpochProgressWrapper;
+
+const Card = styled(Box)({
+ borderRadius: '8px',
+ flex: '1 1 100%',
+ display: 'flex',
+ flexDirection: 'column',
+});
diff --git a/packages/yoroi-extension/app/UI/features/staking/useCases/LegacyDialogs/LegacyDialogs.tsx b/packages/yoroi-extension/app/UI/features/staking/useCases/LegacyDialogs/LegacyDialogs.tsx
new file mode 100644
index 00000000000..790b834290f
--- /dev/null
+++ b/packages/yoroi-extension/app/UI/features/staking/useCases/LegacyDialogs/LegacyDialogs.tsx
@@ -0,0 +1,77 @@
+import { maybe, useStaking } from '../../module/StakingContextProvider';
+import OverviewModal from '../../../../../components/wallet/staking/dashboard-revamp/OverviewDialog';
+import { generateGraphData } from '../../common/helpers/graph';
+import { GovernanceParticipateDialog } from '../../../../../containers/wallet/dialogs/GovernanceParticipateDialog';
+import UnmangleTxDialogContainer from '../../../../../containers/transfer/UnmangleTxDialogContainer';
+import RewardHistoryDialog from '../../../../../components/wallet/staking/dashboard-revamp/RewardHistoryDialog';
+import { observer } from 'mobx-react';
+
+export const LegacyDialogs = observer(() => {
+ const { legacyUIDialogs, stores, totalRewards, toUnitOfAccount, delegationRequests, selectedWallet } = useStaking();
+ const errorIfPresent = maybe(delegationRequests.error, error => ({ error }));
+
+ const showRewardAmount = errorIfPresent == null && stores.delegation.isExecutedDelegatedBalance(selectedWallet.publicDeriverId);
+ const isParticipatingToGovernance = stores.delegation.governanceStatus?.drepDelegation !== null;
+ const isStakeRegistered = stores.delegation.isStakeRegistered(selectedWallet.publicDeriverId);
+
+ const onClose = () => {
+ legacyUIDialogs.closeActiveDialog();
+ };
+
+ return (
+
+ {legacyUIDialogs.isOpen(OverviewModal) ? (
+ {
+ legacyUIDialogs.open({
+ dialog: GovernanceParticipateDialog,
+ });
+ }
+ : isStakeRegistered
+ ? () => {
+ legacyUIDialogs.open({
+ dialog: GovernanceParticipateDialog,
+ });
+ }
+ : undefined
+ }
+ />
+ ) : null}
+ {legacyUIDialogs.isOpen(GovernanceParticipateDialog) ? (
+
+ ) : null}
+ {legacyUIDialogs.isOpen(UnmangleTxDialogContainer) ? : null}
+ {legacyUIDialogs.isOpen(RewardHistoryDialog) ? (
+
+ ) : null}
+
+ );
+});
+
+export const genLookupOrFail = map => lookup => {
+ const tokenRow = map.get(lookup.networkId.toString())?.get(lookup.identifier);
+
+ if (tokenRow == null) {
+ throw new Error(`genLookupOrFail: no token info for ${JSON.stringify(lookup)}`);
+ }
+
+ return tokenRow;
+};
diff --git a/packages/yoroi-extension/app/UI/features/staking/useCases/PoolList/PoolList.tsx b/packages/yoroi-extension/app/UI/features/staking/useCases/PoolList/PoolList.tsx
index 73d5e63caf3..b509690ad6d 100644
--- a/packages/yoroi-extension/app/UI/features/staking/useCases/PoolList/PoolList.tsx
+++ b/packages/yoroi-extension/app/UI/features/staking/useCases/PoolList/PoolList.tsx
@@ -1,9 +1,20 @@
-import { Typography } from '@mui/material';
+// $FlowIgnore: suppressing this error
+import CardanoStakingPage from '../../../../../containers/wallet/staking/CardanoStakingPage';
+import { useStaking } from '../../module/StakingContextProvider';
+import type { ConfigType } from '../../../../../../config/config-types';
+
+// populated by ConfigWebpackPlugin
+declare var CONFIG: ConfigType;
export const PoolList = () => {
+ const { stores, delegationStore, selectedWallet } = useStaking();
return (
- PoolList
+
);
};
diff --git a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardGraphClean.tsx b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardGraphClean.tsx
index 40642439ae0..87136022e29 100644
--- a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardGraphClean.tsx
+++ b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardGraphClean.tsx
@@ -39,7 +39,7 @@ const RewardGraphClean: React.FC = ({
}) => {
const theme: any = useTheme();
- const formatYAxis = (value: number): string | number => (!hideYAxis ? value : '∗∗∗ ');
+ const formatYAxis = (value: number): string | number => (!hideYAxis ? value : '∗∗∗');
const GraphTooltip: React.FC = ({ active, payload, label }) => {
if (active && payload != null && payload.length > 0 && label != null) {
diff --git a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardHistoryGraph.tsx b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardHistoryGraph.tsx
index d38dc2e622d..d932da2da59 100644
--- a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardHistoryGraph.tsx
+++ b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardHistoryGraph.tsx
@@ -2,15 +2,16 @@ import React from 'react';
import { Box, styled } from '@mui/system';
import { Button, CircularProgress, Stack, Typography } from '@mui/material';
import RewardGraphClean from './RewardGraphClean';
-// import VerticallyCenteredLayout from '../../../layout/VerticallyCenteredLayout'; // --- IGNORE ---
import MuiAccordion, { AccordionProps as MuiAccordionProps } from '@mui/material/Accordion';
import MuiAccordionSummary, { AccordionSummaryProps as MuiAccordionSummaryProps } from '@mui/material/AccordionSummary';
import MuiAccordionDetails from '@mui/material/AccordionDetails';
import { getAvatarFromPoolId } from '../../common/helpers';
import { useStrings } from '../../common/hooks/useStrings';
import { GraphData } from '../../common/types';
+import { useStaking } from '../../module/StakingContextProvider';
-/* ---------- Types ---------- */
+import RewardHistoryDialog from '../../../../../components/wallet/staking/dashboard-revamp/RewardHistoryDialog';
+import { observer } from 'mobx-react';
type RewardHistoryEntry = {
type: string;
@@ -27,12 +28,12 @@ type RewardHistoryItemProps = {
type RewardHistoryGraphProps = {
graphData: GraphData;
- onOpenRewardList: () => void;
};
/* ---------- RewardHistoryItem ---------- */
export const RewardHistoryItem: React.FC = ({ poolId, poolName, poolAvatar, historyList }) => {
+ const strings = useStrings();
const avatarGenerated = getAvatarFromPoolId(poolId);
return (
@@ -41,7 +42,7 @@ export const RewardHistoryItem: React.FC = ({ poolId, po
- Stake Pool
+ {strings.stakePoolLabel}
@@ -138,11 +139,15 @@ const AccordionSummary = styled((props: MuiAccordionSummaryProps) => (
},
}));
-const RewardHistoryGraph: React.FC = ({ graphData, onOpenRewardList }) => {
+const RewardHistoryGraph: React.FC = observer(({ graphData }) => {
const strings = useStrings();
+ const { stores } = useStaking();
const { rewardsGraphData } = graphData;
const rewardList = rewardsGraphData.items?.perEpochRewards;
const title = strings.rewardHistoryLabel;
+ const isRewardListArray = Array.isArray(rewardList);
+ const hasError = rewardsGraphData.error && !rewardsGraphData.items;
+ const isLoading = !isRewardListArray && !hasError;
return (
= ({ graphData, onOp
- {rewardsGraphData.error && !rewardsGraphData.items && (
+ {hasError && (
{strings.errorLabel}
@@ -183,9 +192,9 @@ const RewardHistoryGraph: React.FC = ({ graphData, onOp
)}
- {!Array.isArray(rewardList) ? (
-
- ) : (
+ {isLoading && }
+
+ {isRewardListArray && (
= ({ graphData, onOp
)}
);
-};
+});
export default RewardHistoryGraph;
diff --git a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardsSummaryCard.tsx b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardsSummaryCard.tsx
index 9233c37457d..6128ba2e33a 100644
--- a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardsSummaryCard.tsx
+++ b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/RewardsSummaryCard.tsx
@@ -1,8 +1,6 @@
-import React, { ReactNode } from 'react';
+import { ReactNode } from 'react';
import { Box, styled } from '@mui/system';
import { Divider, Typography } from '@mui/material';
-// import loadingSpinnerStyles from '../dashboard/LoadingSpinner.scss'
-// import LoadingSpinner from '../../../widgets/LoadingSpinner'
import RewardHistoryGraph from './RewardHistoryGraph';
import LoadingSpinner from '../../../../../components/widgets/LoadingSpinner';
import { WithdrawButton } from './WithdrawButton';
@@ -10,6 +8,7 @@ import { useStrings } from '../../common/hooks/useStrings';
import { maybe, useStaking } from '../../module/StakingContextProvider';
import { HIDDEN_AMOUNT } from '../../../../common/constants';
import { truncateToken } from '../../../../../utils/formatters';
+import { getTokenName } from '../../../../../stores/stateless/tokenHelpers';
import { Icon } from '../../../../components';
const StakingIconWrapper = styled(Box)(({ theme }) => ({
@@ -57,9 +56,7 @@ const InfoDetails = styled(Box)({});
export const RewardsSummaryCard: React.FC = () => {
const strings = useStrings();
- const { getTokenInfo, onOpenRewardList, totalRewards, totalDelegated, shouldHideBalance, historyGraphData, toUnitOfAccount } =
- useStaking();
- const govStatusFetched = true; // TODO: get from governance provider
+ const { getTokenInfo, totalRewards, totalDelegated, shouldHideBalance, historyGraphData, toUnitOfAccount } = useStaking();
const formatTokenEntry = (tokenEntry): ReactNode => {
const tokenInfo = getTokenInfo(tokenEntry);
@@ -82,7 +79,7 @@ export const RewardsSummaryCard: React.FC = () => {
return (
<>
{amountNode}
- {truncateToken(tokenInfo?.assetName || '', 12)}
+ {truncateToken(getTokenName(tokenInfo))}
>
);
};
@@ -98,8 +95,7 @@ export const RewardsSummaryCard: React.FC = () => {
);
};
- // TODO: enable later
- // const hasNoRewards = (token?: any | null): boolean => (totalRewards ? token?.getDefaultEntry()?.amount?.isZero() : true);
+ const hasNoRewards = token => maybe(token, t => t.getDefaultEntry()?.amount?.isZero?.()) ?? false;
return (
{
{strings.rewardsSummary}
-
+
@@ -135,16 +131,19 @@ export const RewardsSummaryCard: React.FC = () => {
-
+ {/*@ts-ignore */}
+
{strings.totalRewardsLabel}
-
- {totalRewards ? renderAmount(totalRewards) : }
+
+ {renderAmount(totalRewards)}
+
+
+ {renderAmountWithUnitOfAccount(totalRewards)}
-
@@ -154,7 +153,8 @@ export const RewardsSummaryCard: React.FC = () => {
-
+ {/*@ts-ignore */}
+
{strings.totalDelegated}
@@ -178,7 +178,7 @@ export const RewardsSummaryCard: React.FC = () => {
- {historyGraphData && }
+ {historyGraphData && }
);
};
diff --git a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/WithdrawButton.tsx b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/WithdrawButton.tsx
index 6d191302d6e..f701b27c7fa 100644
--- a/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/WithdrawButton.tsx
+++ b/packages/yoroi-extension/app/UI/features/staking/useCases/RewardsSummary/WithdrawButton.tsx
@@ -4,8 +4,11 @@ import { useTxReviewModal } from '../../../transaction-review/module/ReviewTxPro
import { TransactionResult } from '../../../transaction-review/common/types';
import { useStrings } from '../../common/hooks/useStrings';
import { useStaking } from '../../module/StakingContextProvider';
+import React from 'react';
+
+export const WithdrawButton = ({ isDisabled }) => {
+ const [govStatusFetched, setStatusFetched] = React.useState(false);
-export const WithdrawButton = ({ govStatusFetched, isDisabled }) => {
const { openTxReviewModal, stopLoadingTxReview, startLoadingTxReview, showTxResultModal } = useTxReviewModal();
const strings = useStrings();
const { stores } = useStaking();
@@ -14,6 +17,18 @@ export const WithdrawButton = ({ govStatusFetched, isDisabled }) => {
const wallet = stores.wallets.selected;
const isStakeRegistered = stores.delegation.isStakeRegistered(wallet.publicDeriverId);
+ React.useEffect(() => {
+ stores.delegation
+ .checkGovernanceStatus(wallet)
+ .then(() => {
+ setStatusFetched(true);
+ return null;
+ })
+ .catch(e => {
+ console.error('Failed to fetch governance status', e);
+ });
+ }, []);
+
const handleRewardsWithdrawal = async () => {
if (!isParticipatingToGovernance) {
stores.uiDialogs.open({
diff --git a/packages/yoroi-extension/app/components/layout/TopBarLayout.js b/packages/yoroi-extension/app/components/layout/TopBarLayout.js
index 1882f4130bf..89462df35ba 100644
--- a/packages/yoroi-extension/app/components/layout/TopBarLayout.js
+++ b/packages/yoroi-extension/app/components/layout/TopBarLayout.js
@@ -60,7 +60,7 @@ function TopBarLayout({
flex: '0 1 auto',
height: '100%',
}),
- overflow: 'auto',
+ overflow: 'scroll',
}}
>
{
diff --git a/packages/yoroi-extension/app/components/topbar/banners/NotProductionBanner.scss b/packages/yoroi-extension/app/components/topbar/banners/NotProductionBanner.scss
index 3a50594861d..76be1845136 100644
--- a/packages/yoroi-extension/app/components/topbar/banners/NotProductionBanner.scss
+++ b/packages/yoroi-extension/app/components/topbar/banners/NotProductionBanner.scss
@@ -1,5 +1,5 @@
.notProdWarning {
- height: 46px;
+ height: 27px;
display: flex;
justify-content: center;
align-items: center;
diff --git a/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss b/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss
index 549a70f6950..fd3e24a9bff 100644
--- a/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss
+++ b/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss
@@ -1,31 +1,7 @@
@import '../../../themes/mixins/underline';
-.testnetWarning {
- font-weight: 700;
- text-align: center;
- color: var(--yoroi-palette-common-white);
- background-color: var(--yoroi-palette-background-banner-warning);
- padding: 4px;
- text-transform: uppercase;
- font-size: 14px;
- line-height: 21px;
-
- .warningIcon {
- display: inline-flex;
- margin-right: 5px;
- vertical-align: top;
- }
-
- a {
- color: var(--yoroi-palette-common-white);
- font-weight: 700;
- text-transform: uppercase;
- @include underline(var(--yoroi-palette-common-white));
- }
-}
-
.shelleyTestnetWarning {
- height: 46px;
+ height: 27px;
display: flex;
justify-content: center;
align-items: center;
diff --git a/packages/yoroi-extension/app/containers/wallet/WalletDelegationBanner.js b/packages/yoroi-extension/app/containers/wallet/WalletDelegationBanner.js
index b0066db5302..047202700c9 100644
--- a/packages/yoroi-extension/app/containers/wallet/WalletDelegationBanner.js
+++ b/packages/yoroi-extension/app/containers/wallet/WalletDelegationBanner.js
@@ -1,5 +1,6 @@
// @flow
import type { Node, ComponentType } from 'react';
+import { useState, useEffect } from 'react';
import { Box, styled } from '@mui/system';
import { Button, Typography, Link } from '@mui/material';
@@ -74,6 +75,14 @@ const messages = defineMessages({
});
function WalletDelegationBanner({ isOpen, isWalletWithNoFunds, isTestnet, intl, ticker, poolInfo, stores }: Props & Intl): Node {
+ const avatar = poolInfo?.avatar;
+ const [avatarLoadError, setAvatarLoadError] = useState(false);
+
+ // Reset error state when avatar changes
+ useEffect(() => {
+ setAvatarLoadError(false);
+ }, [avatar]);
+
if (poolInfo == null) {
return (
@@ -81,11 +90,13 @@ function WalletDelegationBanner({ isOpen, isWalletWithNoFunds, isTestnet, intl,
);
}
- const { id, name, avatar, websiteUrl, roa: estimatedRoa30d, socialLinks } = poolInfo || {};
+ const { id, name, websiteUrl, roa: estimatedRoa30d, socialLinks } = poolInfo || {};
const avatarSource = toSvg(id, 36, { padding: 0 });
const avatarGenerated = `data:image/svg+xml;utf8,${encodeURIComponent(avatarSource)}`;
+ const shouldUseAvatar = avatar && !avatarLoadError;
+
return isOpen ? (
- {avatar ? : }
+ {shouldUseAvatar ? (
+ setAvatarLoadError(true)} />
+ ) : (
+
+ )}
{name}
diff --git a/packages/yoroi-extension/app/i18n/locales/en-US.json b/packages/yoroi-extension/app/i18n/locales/en-US.json
index 7a8d286cdd2..89b530b33f7 100644
--- a/packages/yoroi-extension/app/i18n/locales/en-US.json
+++ b/packages/yoroi-extension/app/i18n/locales/en-US.json
@@ -853,6 +853,7 @@
"wallet.delegation.transaction.explanationLine2": "You can switch to delegate to a different stake pool at any time",
"wallet.delegation.transaction.explanationLine3": "You can cancel your delegation at any time",
"wallet.delegation.transaction.generation": "Generating transaction",
+ "wallet.delegation.transaction.stakePoolLabel": "Stake pool",
"wallet.delegation.transaction.stakePoolHash": "Stake pool id",
"wallet.delegation.transaction.stakePoolName": "Stake pool name",
"wallet.delegation.transaction.success.button.label": "Dashboard page",
diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json
index 4452d0ff289..e6e3d523f2d 100644
--- a/packages/yoroi-extension/package-lock.json
+++ b/packages/yoroi-extension/package-lock.json
@@ -34,6 +34,7 @@
"@posthog/react": "1.4.0",
"@svgr/webpack": "5.5.0",
"@tanstack/react-query": "^5.90.11",
+ "@yoroi/api": "6.0.0",
"@yoroi/exchange": "2.0.1",
"@yoroi/explorers": "^1.0.2",
"@yoroi/portfolio": "1.0.3",
@@ -10496,51 +10497,23 @@
"license": "Apache-2.0"
},
"node_modules/@yoroi/api": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@yoroi/api/-/api-1.5.1.tgz",
- "integrity": "sha512-upwmeE1a9pdykkDMBRl1Ubwvb4YDWbK5M3OKHAA9b0iKivpblo+vhjOqyqfaaUqRR5GdCljkgvJuaJLYqd0PVA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@emurgo/cip14-js": "^3.0.1",
- "@yoroi/common": "1.5.1",
- "axios": "^1.5.0",
- "zod": "^3.22.1"
- },
- "engines": {
- "node": ">= 16.19.0"
- },
- "peerDependencies": {
- "react": ">= 16.8.0 <= 19.0.0",
- "react-query": "^3.39.3"
- }
- },
- "node_modules/@yoroi/api/node_modules/@yoroi/common": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@yoroi/common/-/common-1.5.1.tgz",
- "integrity": "sha512-ifOdu0wOoXUqMAWfsK05UAQIBytUHK6W60P5gdcBDjg7s28IykxRP7h0MnbGocgV89p5Sdyd/10yb0eRh2o+Nw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@yoroi/api/-/api-6.0.0.tgz",
+ "integrity": "sha512-BEsF48KtX9wkEtsbTBCSSCSBKQFQDQ0q5hvwg+qhG3px3FZt4euy8EWpcHuzA+4sMgXw8ZPxIaByC5BXBk8GXg==",
"license": "Apache-2.0",
"dependencies": {
- "axios": "^1.5.0",
- "zod": "^3.22.1"
+ "@emurgo/cip14-js": "^3.0.1"
},
"engines": {
- "node": ">= 16.19.0"
+ "node": ">= 22.12.0"
},
"peerDependencies": {
- "@react-native-async-storage/async-storage": ">= 1.19.3 <= 1.20.0",
+ "@tanstack/react-query": "^5.76.2",
+ "@yoroi/common": "6.0.0",
+ "axios": "^1.10.0",
+ "immer": "10.1.1",
"react": ">= 16.8.0 <= 19.0.0",
- "react-query": "^3.39.3"
- }
- },
- "node_modules/@yoroi/api/node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
+ "zod": "3.25.17"
}
},
"node_modules/@yoroi/common": {
@@ -23735,6 +23708,25 @@
"react-query": "^3.39.3"
}
},
+ "node_modules/legacySwap/node_modules/@yoroi/api": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@yoroi/api/-/api-1.5.1.tgz",
+ "integrity": "sha512-upwmeE1a9pdykkDMBRl1Ubwvb4YDWbK5M3OKHAA9b0iKivpblo+vhjOqyqfaaUqRR5GdCljkgvJuaJLYqd0PVA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@emurgo/cip14-js": "^3.0.1",
+ "@yoroi/common": "1.5.1",
+ "axios": "^1.5.0",
+ "zod": "^3.22.1"
+ },
+ "engines": {
+ "node": ">= 16.19.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 <= 19.0.0",
+ "react-query": "^3.39.3"
+ }
+ },
"node_modules/legacySwap/node_modules/@yoroi/common": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@yoroi/common/-/common-1.5.1.tgz",