Skip to content

Commit 26fb657

Browse files
authored
Merge pull request #4467 from Emurgo/chore/YW-172/staking-revamp-layout
Staking Page Refactor Part I
2 parents 2795056 + 7e4e9af commit 26fb657

File tree

25 files changed

+1550
-13
lines changed

25 files changed

+1550
-13
lines changed

packages/yoroi-extension/app/Routes.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ import SwapOrdersRevampPage from './UI/pages/Swap-New/SwapOrdersPage';
7070
import SwapReviewRevampPage from './UI/pages/Swap-New/SwapReviewPage';
7171
// $FlowIgnore: suppressing this error
7272
import AirdropPage from './UI/pages/AirdropPage';
73+
// $FlowIgnore: suppressing this error
74+
import StakingPageRevamp from './UI/pages/Staking/StakingPage';
75+
// $FlowIgnore: suppressing this error
76+
import { StakingContextProvider } from './UI/features/staking/module/StakingContextProvider';
7377

7478
// $FlowIgnore: suppressing this error
75-
// import DappCenterPage from './UI/pages/dapp-center/DappCenterPage';
7679
import BuySellDialog from './components/buySell/BuySellDialog';
7780
// $FlowIgnore: suppressing this error
7881
import TransactionReviewFailedPage from './UI/pages/TransactionReview/TransactionReviewFailedPage';
@@ -201,6 +204,9 @@ export const YoroiRoutes = (stores: StoresMap): Node => {
201204
<Route element={<DappCenterSubpages stores={stores} />}>
202205
<Route path={ROUTES.DAPP_CONNECTOR.DAPP_CENTER} element={<DappCenterPage stores={stores} />} />
203206
</Route>
207+
<Route element={<StakingSubpages stores={stores} />}>
208+
<Route path={ROUTES.STAKING_REVAMP.ROOT} element={<StakingPageRevamp stores={stores} />} />
209+
</Route>
204210
<Route element={<WalletsSubpages stores={stores} />}>
205211
<Route path={ROUTES.WALLETS.TRANSACTIONS} element={<WalletSummaryPage stores={stores} />} />
206212
<Route path={ROUTES.WALLETS.SEND} element={<WalletSendPage stores={stores} />} />
@@ -255,7 +261,7 @@ export const YoroiRoutes = (stores: StoresMap): Node => {
255261
<Route path={ROUTES.PORTFOLIO.DAPPS} element={<PortfolioDappsPage stores={stores} />} />
256262
<Route path={ROUTES.PORTFOLIO.DETAILS} element={<PortfolioDetailPage stores={stores} />} />
257263
</Route>
258-
<Route path={ROUTES.TX_REVIEW.FAIL} element={<TransactionReviewFailedPage stores={stores} />} />
264+
259265
<Route path={ROUTES.TX_REVIEW.FAIL} element={<TransactionReviewFailedPage stores={stores} />} />
260266
<Route path={ROUTES.AIRDROP} element={<AirdropPage stores={stores} />} />
261267
</Routes>
@@ -301,15 +307,13 @@ const SwapSubpages = ({ stores }) => {
301307
</FullscreenLayout>
302308
);
303309
return (
304-
// <QueryClientProvider client={queryClient}>
305310
<SwapProvider publicDeriver={stores.wallets.selected} key={stores.wallets.selected?.publicDeriverId}>
306311
<SwapPageContainer stores={stores}>
307312
<Suspense fallback={loader}>
308313
<Outlet />
309314
</Suspense>
310315
</SwapPageContainer>
311316
</SwapProvider>
312-
// </QueryClientProvider>
313317
);
314318
};
315319

@@ -362,6 +366,14 @@ const DappCenterSubpages = ({ stores }) => (
362366
</DappCenterContextProvider>
363367
);
364368

369+
const StakingSubpages = ({ stores }) => (
370+
<StakingContextProvider stores={stores}>
371+
<Suspense fallback={null}>
372+
<Outlet />
373+
</Suspense>
374+
</StakingContextProvider>
375+
);
376+
365377
const CatalystRegistrationSubpages = ({ stores }) => (
366378
<CatalystRegistrationContextProvider stores={stores}>
367379
<Suspense fallback={null}>

packages/yoroi-extension/app/UI/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export enum BannerType {
1616

1717
export const SUPPORT_CRISP_CHATBOX_URL = 'https://emurgo.github.io/yoroi-crisp-support/';
1818
export const MIDNIGHT_PHASE2_URL = 'https://www.midnight.gd/';
19+
export const HIDDEN_AMOUNT = '******';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Chain } from '@yoroi/types';
2+
3+
import { QueryKey } from '@tanstack/react-query';
4+
5+
/**
6+
* React Query Query Factories
7+
* Standardized query keys and factory functions for consistent cache management
8+
*/
9+
10+
// ============================================================================
11+
// Pool Queries
12+
// ============================================================================
13+
14+
export const poolQueryKeys = {
15+
/**
16+
* Query key for pool list
17+
* @param walletId - Wallet ID
18+
* @param network - Network
19+
* @param searchQuery - Optional search query
20+
*/
21+
list: (walletId: string, network: Chain.SupportedNetworks, searchQuery?: string): QueryKey => [
22+
'poolList',
23+
walletId,
24+
network,
25+
searchQuery?.trim() ?? '',
26+
],
27+
28+
/**
29+
* Query key for a single pool info
30+
* @param poolId - Pool ID
31+
*/
32+
info: (poolId: string): QueryKey => ['usePoolInfo', poolId],
33+
34+
/**
35+
* Query key for pool list with pagination
36+
* @param walletId - Wallet ID
37+
* @param network - Network
38+
* @param searchQuery - Optional search query
39+
* @param pageParam - Page parameter for infinite queries
40+
*/
41+
listInfinite: (walletId: string, network: Chain.SupportedNetworks, searchQuery?: string, pageParam?: number): QueryKey => [
42+
'poolList',
43+
walletId,
44+
network,
45+
searchQuery?.trim() ?? '',
46+
pageParam ?? 0,
47+
],
48+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const StakingActive = () => {
2+
return (
3+
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<rect width="40" height="40" rx="20" fill="#E4E8F7" />
5+
<path
6+
fill-rule="evenodd"
7+
clip-rule="evenodd"
8+
d="M27.0001 17C27.0001 13.134 23.8661 10 20.0001 10C16.1341 10 13.0001 13.134 13.0001 17C13.0001 20.866 16.1341 24 20.0001 24C23.8661 24 27.0001 20.866 27.0001 17ZM15.0001 17C15.0001 14.2386 17.2387 12 20.0001 12C22.7615 12 25.0001 14.2386 25.0001 17C25.0001 19.7614 22.7615 22 20.0001 22C17.2387 22 15.0001 19.7614 15.0001 17Z"
9+
fill="#3154CB"
10+
/>
11+
<path
12+
d="M13.8322 23.5547C14.1385 23.0952 14.0143 22.4743 13.5548 22.168C13.0953 21.8616 12.4744 21.9858 12.1681 22.4453L10.1755 25.4342C10.1208 25.5137 10.0773 25.6016 10.0473 25.6955C10.0067 25.8223 9.99264 25.9543 10.0036 26.0836C10.0148 26.2189 10.0529 26.3466 10.1127 26.4613C10.179 26.5893 10.2739 26.7044 10.3953 26.7964C10.4824 26.8627 10.5806 26.9149 10.6868 26.9499C10.7939 26.9853 10.9048 27.0017 11.0148 27H13.882L15.0985 29.4329C15.1357 29.5106 15.1832 29.584 15.2406 29.6509C15.3239 29.7483 15.4239 29.827 15.5341 29.8849C15.6642 29.9536 15.804 29.9909 15.944 29.9986C16.2122 30.0134 16.481 29.9198 16.6827 29.7311C16.7436 29.6743 16.7965 29.6106 16.8406 29.542L18.8322 26.5547C19.1385 26.0952 19.0143 25.4743 18.5548 25.168C18.0953 24.8616 17.4744 24.9858 17.1681 25.4453L16.1239 27.0116L15.405 25.5738C15.3244 25.4029 15.1967 25.2584 15.0387 25.1573C14.9149 25.078 14.7725 25.0253 14.6196 25.0071C14.573 25.0014 14.526 24.999 14.4787 25H12.8686L13.8322 23.5547Z"
13+
fill="#3154CB"
14+
/>
15+
<path
16+
d="M26.4454 22.168C25.9859 22.4743 25.8617 23.0952 26.1681 23.5547L27.1316 25H25.5224C25.4703 24.9989 25.4184 25.0018 25.3672 25.0087C25.229 25.0271 25.0997 25.0737 24.9853 25.1425C24.8172 25.2436 24.6813 25.3928 24.5965 25.571L23.8763 27.0115L22.8322 25.4453C22.5258 24.9858 21.9049 24.8616 21.4454 25.1679C20.9859 25.4743 20.8617 26.0952 21.1681 26.5547L23.1589 29.5409C23.2065 29.6151 23.2642 29.6834 23.3312 29.7437C23.4255 29.8286 23.5338 29.8933 23.6493 29.9365C23.7848 29.9875 23.9258 30.007 24.0635 29.9981C24.1998 29.9896 24.3357 29.9531 24.4625 29.8867C24.5738 29.8288 24.6748 29.7497 24.7589 29.6517C24.8167 29.5845 24.8645 29.5107 24.9019 29.4325L26.1181 27H28.9854C29.0954 27.0017 29.2063 26.9853 29.3134 26.9499C29.4196 26.9149 29.5179 26.8626 29.6049 26.7964C29.7263 26.7044 29.8212 26.5893 29.8876 26.4613C29.9469 26.3474 29.9849 26.2206 29.9964 26.0863C30.0078 25.9561 29.9938 25.8232 29.9529 25.6955C29.9229 25.6016 29.8794 25.5138 29.8248 25.4342L27.8322 22.4453C27.5258 21.9858 26.9049 21.8616 26.4454 22.168Z"
17+
fill="#3154CB"
18+
/>
19+
</svg>
20+
);
21+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const TotalDelegated = () => {
2+
return (
3+
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<rect width="40" height="40" rx="20" fill="#E4F7F3" />
5+
<path
6+
fill-rule="evenodd"
7+
clip-rule="evenodd"
8+
d="M17 11C13.134 11 10 14.134 10 18C10 21.0791 11.988 23.6938 14.7504 24.6307C15.5503 25.5367 16.5809 26.2341 17.7504 26.6307C19.0331 28.0836 20.9095 29 23 29C26.866 29 30 25.866 30 22C30 18.9209 28.012 16.3062 25.2496 15.3693C24.4497 14.4633 23.4191 13.7659 22.2496 13.3693C20.9669 11.9164 19.0905 11 17 11ZM21.3715 15.1904C20.9357 15.0664 20.4756 15 20 15C17.2386 15 15 17.2386 15 20C15 21.1776 15.4071 22.2601 16.0882 23.1145C16.0302 22.7515 16 22.3793 16 22C16 18.6947 18.2909 15.9244 21.3715 15.1904ZM13 20C13 20.3793 13.0302 20.7515 13.0882 21.1145C12.4071 20.2601 12 19.1776 12 18C12 15.2386 14.2386 13 17 13C17.4756 13 17.9357 13.0664 18.3715 13.1904C15.2909 13.9244 13 16.6947 13 20ZM28 22C28 24.7614 25.7614 27 23 27C20.2386 27 18 24.7614 18 22C18 19.2386 20.2386 17 23 17C25.7614 17 28 19.2386 28 22Z"
9+
fill="#0CDDB3"
10+
/>
11+
</svg>
12+
);
13+
};

packages/yoroi-extension/app/UI/components/icons/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ import { YoroiLogo } from './YoroiLogo';
108108
import { VotingDrep } from './VotingDrep';
109109
import { VotingAbstain } from './VotingAbstain';
110110
import { VotingNoConfidence } from './VotingNoConfidence';
111+
import { StakingActive } from './StakingActive';
112+
import { TotalDelegated } from './TotalDelegated';
111113

112114
export const Icon = {
113115
Assets,
@@ -218,6 +220,8 @@ export const Icon = {
218220
VotingDrep,
219221
VotingAbstain,
220222
VotingNoConfidence,
223+
StakingActive,
224+
TotalDelegated,
221225
};
222226

223227
export enum Icons {
@@ -324,6 +328,8 @@ export enum Icons {
324328
VotingDrep = 'VotingDrep',
325329
VotingAbstain = 'VotingAbstain',
326330
VotingNoConfidence = 'VotingNoConfidence',
331+
StakingActive = 'StakingActive',
332+
TotalDelegated = 'TotalDelegated',
327333
}
328334

329335
interface IconWrapperProps {

packages/yoroi-extension/app/UI/features/governace/module/GovernanceContextProvider.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { GovernanceApi } from '@emurgo/yoroi-lib/dist/governance/emurgo-api';
22
import * as React from 'react';
3-
43
import { RustModule } from '../../../../api/ada/lib/cardanoCrypto/rustLoader';
54
import { dRepNormalize } from '../../../../api/ada/lib/cardanoCrypto/utils';
65
import { unwrapStakingKey } from '../../../../api/ada/lib/storage/bridge/utils';
76
import { getPrivateStakingKey } from '../../../../api/thunk';
87
import { DREP_ALWAYS_ABSTAIN, DREP_ALWAYS_NO_CONFIDENCE } from '../common/constants';
9-
import { getFormattedPairingValue } from '../common/helpers';
108
import { useGovernanceManagerMaker } from '../common/hooks/useGovernanceManagerMaker';
119
import { GovernanceActionType, GovernanceReducer, defaultGovernanceActions, defaultGovernanceState } from './state';
1210

@@ -22,7 +20,6 @@ const initialGovernanceProvider = {
2220
txDelegationResult: null,
2321
txDelegationError: null,
2422
tokenInfo: null,
25-
getFormattedPairingAmount: (_amount: string) => Response,
2623
isHardwareWallet: false,
2724
createDrepDelegationTransaction: async (_drepCredential: string) => Response,
2825
signDelegationTransaction: async (_params: any) => Response,
@@ -59,7 +56,6 @@ export const GovernanceContextProvider = ({
5956
signDelegationTransaction,
6057
tokenInfo,
6158
triggerBuySellAdaDialog,
62-
getCurrentPrice,
6359
}: GovernanceProviderProps) => {
6460
if (!currentWallet?.selectedWallet) throw new Error(`requires a wallet to be selected`);
6561
const [state, dispatch] = React.useReducer(GovernanceReducer, {
@@ -73,8 +69,6 @@ export const GovernanceContextProvider = ({
7369
currentPool,
7470
selectedWallet,
7571
backendService,
76-
defaultTokenInfo,
77-
unitOfAccount,
7872
isHardwareWallet,
7973
walletAdaBalance,
8074
backendServiceZero,
@@ -162,8 +156,6 @@ export const GovernanceContextProvider = ({
162156
tokenInfo,
163157
isHardwareWallet,
164158
walletAdaBalance,
165-
getFormattedPairingAmount: (amount: string) =>
166-
getFormattedPairingValue(getCurrentPrice, defaultTokenInfo, unitOfAccount, amount),
167159
triggerBuySellAdaDialog,
168160
recentTransactions,
169161
submitedTransactions,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Stack } from '@mui/material';
2+
import { PoolList } from './useCases/PoolList/PoolList';
3+
import { RewardsSummaryCard } from './useCases/RewardsSummary/RewardsSummaryCard';
4+
5+
export const StakingRoot = () => {
6+
return (
7+
<Stack>
8+
<RewardsSummaryCard />
9+
<PoolList />
10+
</Stack>
11+
);
12+
};
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import {
2+
getCardanoHaskellBaseConfig,
3+
isCardanoHaskell,
4+
getNetworkById,
5+
} from '../../../../../api/ada/lib/storage/database/prepackaged/networks';
6+
import { MultiToken } from '../../../../../api/common/lib/MultiToken';
7+
import type { PoolMeta, DelegationRequests } from '../../../../../stores/toplevel/DelegationStore';
8+
import type { TokenInfoMap } from '../../../../../stores/toplevel/TokenInfoStore';
9+
import type { TokenEntry } from '../../../../../api/common/lib/MultiToken';
10+
import { GraphData, GraphItems } from '../types';
11+
12+
/**
13+
* Reward history entry:
14+
* [ epochNumber, rewardMultiToken, poolHash ]
15+
*/
16+
type RewardHistoryEntry = [number, MultiToken, string];
17+
18+
type GenerateRewardGraphDataArgs = {
19+
delegationRequests: DelegationRequests;
20+
currentEpoch: number;
21+
networkId: number;
22+
defaultTokenId: string;
23+
getLocalPoolInfo: (networkId: number, poolHash: string) => PoolMeta | void;
24+
tokenInfo: TokenInfoMap;
25+
};
26+
27+
type RewardGraphItemsResult = {
28+
totalRewards: GraphItems[];
29+
perEpochRewards: GraphItems[];
30+
};
31+
32+
const generateRewardGraphData = (request: GenerateRewardGraphDataArgs): RewardGraphItemsResult | null => {
33+
const defaultToken = {
34+
defaultNetworkId: request.networkId,
35+
defaultIdentifier: request.defaultTokenId,
36+
};
37+
38+
const network = getNetworkById(request.networkId);
39+
40+
const history = request.delegationRequests.rewardHistory.result as RewardHistoryEntry[] | null | undefined;
41+
if (history == null) {
42+
return null;
43+
}
44+
45+
let historyIterator = 0;
46+
47+
// the reward history endpoint doesn't contain entries when the reward was 0
48+
// so we need to insert these manually
49+
const totalRewards: GraphItems[] = [];
50+
const perEpochRewards: GraphItems[] = [];
51+
let amountSum = new MultiToken([], defaultToken);
52+
53+
const startEpoch = (() => {
54+
if (isCardanoHaskell(network)) {
55+
const shelleyConfig = getCardanoHaskellBaseConfig(network)[1];
56+
return shelleyConfig.StartAt;
57+
}
58+
return 0;
59+
})();
60+
61+
const endEpoch = (() => {
62+
if (isCardanoHaskell(network)) {
63+
// TODO: -1 since cardano-db-sync doesn't expose this information for some reason
64+
return request.currentEpoch - 1;
65+
}
66+
throw new Error(`${String(generateRewardGraphData)} can't compute endEpoch for rewards`);
67+
})();
68+
69+
const getMiniPoolInfo = (poolHash: string): string => {
70+
const meta = request.getLocalPoolInfo(request.networkId, poolHash);
71+
if (meta == null || meta.info == null || meta.info.ticker == null || meta.info.name == null) {
72+
return poolHash;
73+
}
74+
return `[${meta.info.ticker}] ${meta.info.name}`;
75+
};
76+
77+
const getNormalized = (tokenEntry: TokenEntry) => {
78+
const tokenRow = request.tokenInfo.get(tokenEntry.networkId.toString())?.get(tokenEntry.identifier);
79+
80+
if (tokenRow == null) {
81+
throw new Error(`${String(generateRewardGraphData)} no token info for ${JSON.stringify(tokenEntry)}`);
82+
}
83+
84+
return tokenEntry.amount.shiftedBy(-tokenRow.Metadata.numberOfDecimals);
85+
};
86+
87+
for (let i = startEpoch; i < endEpoch; i++) {
88+
const currentHistoryEntry = history[historyIterator];
89+
if (historyIterator < history.length && currentHistoryEntry != null && i === currentHistoryEntry[0]) {
90+
// exists a reward for this epoch
91+
const poolHash = currentHistoryEntry[2];
92+
const nextReward = currentHistoryEntry[1];
93+
amountSum = amountSum.joinAddMutable(nextReward);
94+
95+
totalRewards.push({
96+
name: i,
97+
primary: getNormalized(amountSum.getDefaultEntry()).toNumber(),
98+
poolName: getMiniPoolInfo(poolHash),
99+
});
100+
101+
perEpochRewards.push({
102+
name: i,
103+
primary: getNormalized(nextReward.getDefaultEntry()).toNumber(),
104+
poolName: getMiniPoolInfo(poolHash),
105+
});
106+
107+
historyIterator++;
108+
} else {
109+
// no reward for this epoch
110+
totalRewards.push({
111+
name: i,
112+
primary: getNormalized(amountSum.getDefaultEntry()).toNumber(),
113+
poolName: '',
114+
});
115+
116+
perEpochRewards.push({
117+
name: i,
118+
primary: 0,
119+
poolName: '',
120+
});
121+
}
122+
}
123+
124+
return {
125+
totalRewards,
126+
perEpochRewards,
127+
};
128+
};
129+
130+
type GenerateGraphDataArgs = {
131+
delegationRequests: DelegationRequests;
132+
networkId: number;
133+
defaultTokenId: string;
134+
currentEpoch: number;
135+
shouldHideBalance: boolean;
136+
getLocalPoolInfo: (networkId: number, poolHash: string) => PoolMeta | void;
137+
tokenInfo: TokenInfoMap;
138+
};
139+
140+
export const generateGraphData = (request: GenerateGraphDataArgs): GraphData => {
141+
return {
142+
rewardsGraphData: {
143+
error: request.delegationRequests.rewardHistory.error,
144+
items:
145+
generateRewardGraphData({
146+
delegationRequests: request.delegationRequests,
147+
currentEpoch: request.currentEpoch,
148+
networkId: request.networkId,
149+
defaultTokenId: request.defaultTokenId,
150+
getLocalPoolInfo: request.getLocalPoolInfo,
151+
tokenInfo: request.tokenInfo,
152+
}) ?? undefined,
153+
hideYAxis: request.shouldHideBalance,
154+
},
155+
};
156+
};

0 commit comments

Comments
 (0)