From fb284b07d8bc515f6eb77e808398db0178170e9e Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Wed, 18 Dec 2024 15:25:42 +0200
Subject: [PATCH 1/8] feat(blockchain-link): add Solana epoch info call and
upgrade SDK
---
packages/blockchain-link-types/package.json | 2 +-
packages/blockchain-link-types/src/common.ts | 3 ++-
packages/blockchain-link-utils/package.json | 2 +-
packages/blockchain-link/package.json | 2 +-
.../blockchain-link/src/workers/solana/index.ts | 13 ++++++-------
packages/blockchain-link/src/workers/utils.ts | 13 +++++++++++--
6 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/packages/blockchain-link-types/package.json b/packages/blockchain-link-types/package.json
index ac7d8520646..72997d648bb 100644
--- a/packages/blockchain-link-types/package.json
+++ b/packages/blockchain-link-types/package.json
@@ -19,7 +19,7 @@
"prepublish": "yarn tsx ../../scripts/prepublish.js"
},
"dependencies": {
- "@everstake/wallet-sdk": "^1.0.5",
+ "@everstake/wallet-sdk": "^1.0.7",
"@solana/web3.js": "^2.0.0",
"@trezor/type-utils": "workspace:*",
"@trezor/utxo-lib": "workspace:*"
diff --git a/packages/blockchain-link-types/src/common.ts b/packages/blockchain-link-types/src/common.ts
index 0748dad8a70..1b6b33f995f 100644
--- a/packages/blockchain-link-types/src/common.ts
+++ b/packages/blockchain-link-types/src/common.ts
@@ -226,7 +226,6 @@ export interface AccountInfo {
nonce?: string;
contractInfo?: ContractInfo;
stakingPools?: StakingPool[];
- solStakingAccounts?: SolanaStakingAccount[]; // solana staking accounts
addressAliases?: { [key: string]: AddressAlias };
// XRP
sequence?: number;
@@ -243,6 +242,8 @@ export interface AccountInfo {
// SOL
owner?: string; // The Solana program owning the account
rent?: number; // The rent required for the account to opened
+ solStakingAccounts?: SolanaStakingAccount[]; // Solana staking accounts
+ solEpoch?: number; // Solana current epoch
};
page?: {
// blockbook and blockfrost
diff --git a/packages/blockchain-link-utils/package.json b/packages/blockchain-link-utils/package.json
index 159f6aa349d..87f99418a0e 100644
--- a/packages/blockchain-link-utils/package.json
+++ b/packages/blockchain-link-utils/package.json
@@ -20,7 +20,7 @@
"prepublish": "yarn tsx ../../scripts/prepublish.js"
},
"dependencies": {
- "@everstake/wallet-sdk": "^1.0.5",
+ "@everstake/wallet-sdk": "^1.0.7",
"@mobily/ts-belt": "^3.13.1",
"@trezor/env-utils": "workspace:*",
"@trezor/utils": "workspace:*"
diff --git a/packages/blockchain-link/package.json b/packages/blockchain-link/package.json
index 9f933a02e57..5f52dea0386 100644
--- a/packages/blockchain-link/package.json
+++ b/packages/blockchain-link/package.json
@@ -76,7 +76,7 @@
"worker-loader": "^3.0.8"
},
"dependencies": {
- "@everstake/wallet-sdk": "^1.0.5",
+ "@everstake/wallet-sdk": "^1.0.7",
"@solana-program/token": "^0.4.1",
"@solana-program/token-2022": "^0.3.1",
"@solana/web3.js": "^2.0.0",
diff --git a/packages/blockchain-link/src/workers/solana/index.ts b/packages/blockchain-link/src/workers/solana/index.ts
index 174a2ace3b9..4df6ad2073f 100644
--- a/packages/blockchain-link/src/workers/solana/index.ts
+++ b/packages/blockchain-link/src/workers/solana/index.ts
@@ -61,7 +61,7 @@ import { IntervalId } from '@trezor/type-utils';
import { getBaseFee, getPriorityFee } from './fee';
import { BaseWorker, ContextType, CONTEXT } from '../baseWorker';
-// import { getSolanaStakingAccounts } from '../utils';
+import { getSolanaStakingData } from '../utils';
export type SolanaAPI = Readonly<{
clusterUrl: ClusterUrl;
@@ -208,8 +208,7 @@ const pushTransaction = async (request: Request) =
const getAccountInfo = async (
request: Request,
- // TODO: uncomment when solana staking accounts are supported
- // isTestnet: boolean,
+ isTestnet: boolean,
) => {
const { payload } = request;
const { details = 'basic' } = payload;
@@ -351,12 +350,12 @@ const getAccountInfo = async (
const accountDataBytes = getBase64Encoder().encode(accountDataEncoded);
const accountDataLength = BigInt(accountDataBytes.byteLength);
const rent = await api.rpc.getMinimumBalanceForRentExemption(accountDataLength).send();
- // TODO: uncomment when solana staking accounts are supported
- // const stakingAccounts = await getSolanaStakingAccounts(payload.descriptor, isTestnet);
+ const stakingData = await getSolanaStakingData(payload.descriptor, isTestnet);
misc = {
owner: accountInfo?.owner,
rent: Number(rent),
- solStakingAccounts: [],
+ solStakingAccounts: stakingData?.stakingAccounts,
+ solEpoch: stakingData?.epoch,
};
}
}
@@ -708,7 +707,7 @@ const unsubscribe = (request: Request) => {
const onRequest = (request: Request, isTestnet: boolean) => {
switch (request.type) {
case MESSAGES.GET_ACCOUNT_INFO:
- return getAccountInfo(request);
+ return getAccountInfo(request, isTestnet);
case MESSAGES.GET_INFO:
return getInfo(request, isTestnet);
case MESSAGES.PUSH_TRANSACTION:
diff --git a/packages/blockchain-link/src/workers/utils.ts b/packages/blockchain-link/src/workers/utils.ts
index ace5622825e..50a2b539439 100644
--- a/packages/blockchain-link/src/workers/utils.ts
+++ b/packages/blockchain-link/src/workers/utils.ts
@@ -25,7 +25,7 @@ export const prioritizeEndpoints = (urls: string[]) =>
.sort(([, a], [, b]) => b - a)
.map(([url]) => url);
-export const getSolanaStakingAccounts = async (descriptor: string, isTestnet: boolean) => {
+export const getSolanaStakingData = async (descriptor: string, isTestnet: boolean) => {
const blockchainEnvironment = isTestnet ? 'devnet' : 'mainnet';
// Find the blockchain configuration for the specified chain and environment
@@ -38,7 +38,16 @@ export const getSolanaStakingAccounts = async (descriptor: string, isTestnet: bo
const solanaClient = new Solana(network, serverUrl);
const delegations = await solanaClient.getDelegations(descriptor);
+ if (!delegations || !delegations.result) {
+ throw new Error('Failed to fetch delegations');
+ }
const { result: stakingAccounts } = delegations;
- return stakingAccounts;
+ const epochInfo = await solanaClient.getEpochInfo();
+ if (!epochInfo || !epochInfo.result) {
+ throw new Error('Failed to fetch epoch info');
+ }
+ const { epoch } = epochInfo.result;
+
+ return { stakingAccounts, epoch };
};
From 822600946d81ef8e3d24318a60789b78a98229ea Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Wed, 18 Dec 2024 15:51:29 +0200
Subject: [PATCH 2/8] feat(suite): add getAssets call to fetch Solana APY data
---
.../wallet-core/src/stake/stakeConstants.ts | 12 +-
.../wallet-core/src/stake/stakeReducer.ts | 68 ++++++++--
.../wallet-core/src/stake/stakeSelectors.ts | 25 ++--
.../wallet-core/src/stake/stakeThunks.ts | 119 +++++++++++++-----
.../wallet-core/src/stake/stakeTypes.ts | 8 ++
.../wallet-types/src/ethereumStaking.ts | 2 +-
.../wallet-utils/src/ethereumStakingUtils.ts | 6 +-
7 files changed, 187 insertions(+), 53 deletions(-)
diff --git a/suite-common/wallet-core/src/stake/stakeConstants.ts b/suite-common/wallet-core/src/stake/stakeConstants.ts
index 8b48dfe1b05..d8f0774fc35 100644
--- a/suite-common/wallet-core/src/stake/stakeConstants.ts
+++ b/suite-common/wallet-core/src/stake/stakeConstants.ts
@@ -1,6 +1,14 @@
-import { SupportedNetworkSymbol } from '@suite-common/wallet-types';
+import {
+ SupportedEthereumNetworkSymbol,
+ SupportedSolanaNetworkSymbols,
+} from '@suite-common/wallet-types';
-export const EVERSTAKE_ENDPOINT_PREFIX: Record = {
+export const EVERSTAKE_ENDPOINT_PREFIX: Record<
+ SupportedEthereumNetworkSymbol | SupportedSolanaNetworkSymbols,
+ string
+> = {
eth: 'https://eth-api-b2c.everstake.one/api/v1',
thol: 'https://eth-api-b2c-stage.everstake.one/api/v1',
+ sol: 'https://dashboard-api.everstake.one',
+ dsol: 'https://dashboard-api.everstake.one',
};
diff --git a/suite-common/wallet-core/src/stake/stakeReducer.ts b/suite-common/wallet-core/src/stake/stakeReducer.ts
index 81cfac20d01..6079d411dd2 100644
--- a/suite-common/wallet-core/src/stake/stakeReducer.ts
+++ b/suite-common/wallet-core/src/stake/stakeReducer.ts
@@ -5,7 +5,7 @@ import { NetworkSymbol } from '@suite-common/wallet-config';
import { stakeActions } from './stakeActions';
import { ValidatorsQueue } from './stakeTypes';
-import { fetchEverstakeData } from './stakeThunks';
+import { fetchEverstakeAssetData, fetchEverstakeData } from './stakeThunks';
import { SerializedTx } from '../send/sendFormTypes';
export interface StakeState {
@@ -14,7 +14,7 @@ export interface StakeState {
serializedTx?: SerializedTx; // payload for TrezorConnect.pushTransaction
data: {
[key in NetworkSymbol]?: {
- poolStats: {
+ poolStats?: {
error: boolean | string;
isLoading: boolean;
lastSuccessfulFetchTimestamp: Timestamp;
@@ -24,12 +24,18 @@ export interface StakeState {
isPoolStatsLoading?: boolean;
};
};
- validatorsQueue: {
+ validatorsQueue?: {
error: boolean | string;
isLoading: boolean;
lastSuccessfulFetchTimestamp: Timestamp;
data: ValidatorsQueue;
};
+ getAssets?: {
+ error: boolean | string;
+ isLoading: boolean;
+ lastSuccessfulFetchTimestamp: Timestamp;
+ data: { apy?: number };
+ };
};
};
}
@@ -70,10 +76,10 @@ export const prepareStakeReducer = createReducerWithExtraDeps(stakeInitialState,
delete state.serializedTx;
})
.addCase(fetchEverstakeData.pending, (state, action) => {
- const { networkSymbol } = action.meta.arg;
+ const { symbol } = action.meta.arg;
- if (!state.data[networkSymbol]) {
- state.data[networkSymbol] = {
+ if (!state.data[symbol]) {
+ state.data[symbol] = {
poolStats: {
error: false,
isLoading: true,
@@ -90,9 +96,9 @@ export const prepareStakeReducer = createReducerWithExtraDeps(stakeInitialState,
}
})
.addCase(fetchEverstakeData.fulfilled, (state, action) => {
- const { networkSymbol, endpointType } = action.meta.arg;
+ const { symbol, endpointType } = action.meta.arg;
- const data = state.data[networkSymbol];
+ const data = state.data[symbol];
if (data?.[endpointType]) {
data[endpointType] = {
@@ -104,9 +110,51 @@ export const prepareStakeReducer = createReducerWithExtraDeps(stakeInitialState,
}
})
.addCase(fetchEverstakeData.rejected, (state, action) => {
- const { networkSymbol, endpointType } = action.meta.arg;
+ const { symbol, endpointType } = action.meta.arg;
+
+ const data = state.data[symbol];
+
+ if (data?.[endpointType]) {
+ data[endpointType] = {
+ error: true,
+ isLoading: false,
+ lastSuccessfulFetchTimestamp: 0 as Timestamp,
+ data: {},
+ };
+ }
+ })
+ .addCase(fetchEverstakeAssetData.pending, (state, action) => {
+ const { symbol } = action.meta.arg;
+
+ if (!state.data[symbol]) {
+ state.data[symbol] = {
+ getAssets: {
+ error: false,
+ isLoading: true,
+ lastSuccessfulFetchTimestamp: 0 as Timestamp,
+ data: {},
+ },
+ };
+ }
+ })
+ .addCase(fetchEverstakeAssetData.fulfilled, (state, action) => {
+ const { symbol, endpointType } = action.meta.arg;
+
+ const data = state.data[symbol];
+
+ if (data?.[endpointType]) {
+ data[endpointType] = {
+ error: false,
+ isLoading: false,
+ lastSuccessfulFetchTimestamp: Date.now() as Timestamp,
+ data: action.payload,
+ };
+ }
+ })
+ .addCase(fetchEverstakeAssetData.rejected, (state, action) => {
+ const { symbol, endpointType } = action.meta.arg;
- const data = state.data[networkSymbol];
+ const data = state.data[symbol];
if (data?.[endpointType]) {
data[endpointType] = {
diff --git a/suite-common/wallet-core/src/stake/stakeSelectors.ts b/suite-common/wallet-core/src/stake/stakeSelectors.ts
index 8a309673ca4..6f27cd910ce 100644
--- a/suite-common/wallet-core/src/stake/stakeSelectors.ts
+++ b/suite-common/wallet-core/src/stake/stakeSelectors.ts
@@ -1,20 +1,27 @@
import { type NetworkSymbol } from '@suite-common/wallet-config';
-import { BACKUP_ETH_APY } from '@suite-common/wallet-constants';
+import { BACKUP_APY, BACKUP_ETH_APY, BACKUP_SOL_APY } from '@suite-common/wallet-constants';
+import { isSupportedSolStakingNetworkSymbol } from '@suite-common/wallet-utils';
import { StakeRootState } from './stakeReducer';
export const selectEverstakeData = (
state: StakeRootState,
- symbol: NetworkSymbol,
- endpointType: 'poolStats' | 'validatorsQueue',
-) => state.wallet.stake?.data?.[symbol]?.[endpointType];
+ networkSymbol: NetworkSymbol,
+ endpointType: 'poolStats' | 'validatorsQueue' | 'getAssets',
+) => state.wallet.stake?.data?.[networkSymbol]?.[endpointType];
export const selectPoolStatsApyData = (state: StakeRootState, symbol?: NetworkSymbol) => {
- if (!symbol) {
- return BACKUP_ETH_APY;
+ const { data } = state.wallet.stake ?? {};
+
+ if (!symbol || !data) {
+ return BACKUP_APY;
+ }
+
+ if (isSupportedSolStakingNetworkSymbol(symbol)) {
+ return data?.[symbol]?.getAssets?.data?.apy || BACKUP_SOL_APY;
}
- return state.wallet.stake?.data?.[symbol]?.poolStats.data.ethApy || BACKUP_ETH_APY;
+ return data?.[symbol]?.poolStats?.data.ethApy || BACKUP_ETH_APY;
};
export const selectPoolStatsNextRewardPayout = (state: StakeRootState, symbol?: NetworkSymbol) => {
@@ -22,7 +29,7 @@ export const selectPoolStatsNextRewardPayout = (state: StakeRootState, symbol?:
return undefined;
}
- return state.wallet.stake?.data?.[symbol]?.poolStats.data?.nextRewardPayout;
+ return state.wallet.stake?.data?.[symbol]?.poolStats?.data?.nextRewardPayout;
};
export const selectValidatorsQueueData = (state: StakeRootState, symbol?: NetworkSymbol) => {
@@ -30,7 +37,7 @@ export const selectValidatorsQueueData = (state: StakeRootState, symbol?: Networ
return {};
}
- return state.wallet.stake?.data?.[symbol]?.validatorsQueue.data || {};
+ return state.wallet.stake?.data?.[symbol]?.validatorsQueue?.data || {};
};
export const selectValidatorsQueue = (state: StakeRootState, symbol?: NetworkSymbol) => {
diff --git a/suite-common/wallet-core/src/stake/stakeThunks.ts b/suite-common/wallet-core/src/stake/stakeThunks.ts
index 6eaf5e5baec..deb0cafdc48 100644
--- a/suite-common/wallet-core/src/stake/stakeThunks.ts
+++ b/suite-common/wallet-core/src/stake/stakeThunks.ts
@@ -1,11 +1,24 @@
import { BigNumber } from '@trezor/utils/src/bigNumber';
import { createThunk } from '@suite-common/redux-utils';
import { TimerId } from '@trezor/type-utils';
-import { getStakingSymbols } from '@suite-common/wallet-utils';
-import { SupportedNetworkSymbol } from '@suite-common/wallet-types';
+import {
+ getSolanaStakingSymbols,
+ getStakingSymbols,
+ isSupportedSolStakingNetworkSymbol,
+} from '@suite-common/wallet-utils';
+import {
+ SupportedEthereumNetworkSymbol,
+ SupportedSolanaNetworkSymbols,
+} from '@suite-common/wallet-types';
import { selectEverstakeData } from './stakeSelectors';
-import { EVERSTAKE_ENDPOINT_TYPES, EverstakeEndpointType, ValidatorsQueue } from './stakeTypes';
+import {
+ EVERSTAKE_ASSET_ENDPOINT_TYPES,
+ EVERSTAKE_ENDPOINT_TYPES,
+ EverstakeAssetEndpointType,
+ EverstakeEndpointType,
+ ValidatorsQueue,
+} from './stakeTypes';
import { EVERSTAKE_ENDPOINT_PREFIX } from './stakeConstants';
import { selectAllNetworkSymbolsOfVisibleAccounts } from '../accounts/accountsReducer';
@@ -14,15 +27,15 @@ const STAKE_MODULE = '@common/wallet-core/stake';
export const fetchEverstakeData = createThunk<
ValidatorsQueue | { ethApy: number; nextRewardPayout: number },
{
- networkSymbol: SupportedNetworkSymbol;
+ symbol: SupportedEthereumNetworkSymbol;
endpointType: EverstakeEndpointType;
},
{ rejectValue: string }
>(`${STAKE_MODULE}/fetchEverstakeData`, async (params, { fulfillWithValue, rejectWithValue }) => {
- const { networkSymbol, endpointType } = params;
+ const { symbol, endpointType } = params;
const endpointSuffix = EVERSTAKE_ENDPOINT_TYPES[endpointType];
- const endpointPrefix = EVERSTAKE_ENDPOINT_PREFIX[networkSymbol];
+ const endpointPrefix = EVERSTAKE_ENDPOINT_PREFIX[symbol];
try {
const response = await fetch(`${endpointPrefix}/${endpointSuffix}`);
@@ -58,6 +71,40 @@ export const fetchEverstakeData = createThunk<
}
});
+export const fetchEverstakeAssetData = createThunk<
+ { apy: number },
+ {
+ symbol: SupportedSolanaNetworkSymbols;
+ endpointType: EverstakeAssetEndpointType;
+ },
+ { rejectValue: string }
+>(
+ `${STAKE_MODULE}/fetchEverstakeAssetData`,
+ async (params, { fulfillWithValue, rejectWithValue }) => {
+ const { symbol, endpointType } = params;
+
+ const endpointSuffix = EVERSTAKE_ASSET_ENDPOINT_TYPES[endpointType];
+ const endpointPrefix = EVERSTAKE_ENDPOINT_PREFIX[symbol];
+ const endpointParams = isSupportedSolStakingNetworkSymbol(symbol) ? `name=solana` : '';
+
+ try {
+ const response = await fetch(`${endpointPrefix}/${endpointSuffix}?${endpointParams}`);
+
+ if (!response.ok) {
+ throw Error(response.statusText);
+ }
+
+ const data = await response.json();
+
+ return fulfillWithValue({
+ apy: data.blockchain.apr,
+ });
+ } catch (error) {
+ return rejectWithValue(error.toString());
+ }
+ },
+);
+
export const initStakeDataThunk = createThunk(
`${STAKE_MODULE}/initStakeDataThunk`,
(_, { getState, dispatch, extra }) => {
@@ -65,30 +112,46 @@ export const initStakeDataThunk = createThunk(
const accountsNetworks = selectAllNetworkSymbolsOfVisibleAccounts(getState());
//also join with enabled networks in case account was not yet discovered, but network is already enabled
const enabledNetworks = extra.selectors.selectEnabledNetworks(getState());
- const networks = [...new Set([...accountsNetworks, ...enabledNetworks])];
-
- const networksWithStaking = getStakingSymbols(networks);
-
- const promises = networksWithStaking.flatMap(symbol =>
- Object.values(EverstakeEndpointType).map(endpointType => {
- const data = selectEverstakeData(getState(), symbol, endpointType);
-
- const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
-
- const shouldRefetch =
- data?.error ||
- !data?.lastSuccessfulFetchTimestamp ||
- data?.lastSuccessfulFetchTimestamp <= fiveMinutesAgo;
-
- if (shouldRefetch) {
- return dispatch(fetchEverstakeData({ networkSymbol: symbol, endpointType }));
- }
-
- return null;
- }),
+ const mergedNetworks = [...new Set([...accountsNetworks, ...enabledNetworks])];
+
+ const ethereumBasedNetworksWithStaking = getStakingSymbols(mergedNetworks);
+ const solanaBasedNetworksWithStaking = getSolanaStakingSymbols(mergedNetworks);
+
+ const createPromises = (
+ networks: (SupportedSolanaNetworkSymbols | SupportedEthereumNetworkSymbol)[],
+ endpointTypes: typeof EverstakeEndpointType | typeof EverstakeAssetEndpointType,
+ ) =>
+ networks
+ .flatMap(symbol =>
+ Object.values(endpointTypes).map(endpointType => {
+ const data = selectEverstakeData(getState(), symbol, endpointType);
+ const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
+
+ const shouldRefetch =
+ data?.error ||
+ !data?.lastSuccessfulFetchTimestamp ||
+ data?.lastSuccessfulFetchTimestamp <= fiveMinutesAgo;
+
+ if (shouldRefetch) {
+ if (isSupportedSolStakingNetworkSymbol(symbol)) {
+ return dispatch(fetchEverstakeAssetData({ symbol, endpointType }));
+ }
+
+ return dispatch(fetchEverstakeData({ symbol, endpointType }));
+ }
+
+ return null;
+ }),
+ )
+ .filter(Boolean);
+
+ const ethPromises = createPromises(ethereumBasedNetworksWithStaking, EverstakeEndpointType);
+ const solPromises = createPromises(
+ solanaBasedNetworksWithStaking,
+ EverstakeAssetEndpointType,
);
- return Promise.all(promises);
+ return Promise.all([...ethPromises, ...solPromises]);
},
);
diff --git a/suite-common/wallet-core/src/stake/stakeTypes.ts b/suite-common/wallet-core/src/stake/stakeTypes.ts
index c29fe4139d1..7e03b6cb642 100644
--- a/suite-common/wallet-core/src/stake/stakeTypes.ts
+++ b/suite-common/wallet-core/src/stake/stakeTypes.ts
@@ -21,6 +21,14 @@ export const EVERSTAKE_ENDPOINT_TYPES = {
[EverstakeEndpointType.ValidatorsQueue]: 'validators/queue',
};
+export enum EverstakeAssetEndpointType {
+ GetAssets = 'getAssets',
+}
+
+export const EVERSTAKE_ASSET_ENDPOINT_TYPES = {
+ [EverstakeAssetEndpointType.GetAssets]: 'chain',
+};
+
export interface ValidatorsQueue {
validatorsEnteringNum?: number;
validatorsExitingNum?: number;
diff --git a/suite-common/wallet-types/src/ethereumStaking.ts b/suite-common/wallet-types/src/ethereumStaking.ts
index e3c3dd95309..004b31ff15c 100644
--- a/suite-common/wallet-types/src/ethereumStaking.ts
+++ b/suite-common/wallet-types/src/ethereumStaking.ts
@@ -2,4 +2,4 @@ export type StakeType = 'stake' | 'unstake' | 'claim';
export const supportedNetworkSymbols = ['eth', 'thol'] as const;
-export type SupportedNetworkSymbol = (typeof supportedNetworkSymbols)[number];
+export type SupportedEthereumNetworkSymbol = (typeof supportedNetworkSymbols)[number];
diff --git a/suite-common/wallet-utils/src/ethereumStakingUtils.ts b/suite-common/wallet-utils/src/ethereumStakingUtils.ts
index d7efe7bcb89..425bac1b589 100644
--- a/suite-common/wallet-utils/src/ethereumStakingUtils.ts
+++ b/suite-common/wallet-utils/src/ethereumStakingUtils.ts
@@ -5,7 +5,7 @@ import {
StakingPoolExtended,
StakeType,
supportedNetworkSymbols,
- SupportedNetworkSymbol,
+ SupportedEthereumNetworkSymbol,
} from '@suite-common/wallet-types';
import { BigNumber } from '@trezor/utils/src/bigNumber';
import { NetworkSymbol, getNetworkFeatures } from '@suite-common/wallet-config';
@@ -69,7 +69,7 @@ export const getEthereumCryptoBalanceWithStaking = (account: Account) => {
export function isSupportedEthStakingNetworkSymbol(
symbol: NetworkSymbol,
-): symbol is SupportedNetworkSymbol {
+): symbol is SupportedEthereumNetworkSymbol {
return isArrayMember(symbol, supportedNetworkSymbols);
}
@@ -83,7 +83,7 @@ export const getStakingSymbols = (symbols: NetworkSymbol[]) =>
}
return acc;
- }, [] as SupportedNetworkSymbol[]);
+ }, [] as SupportedEthereumNetworkSymbol[]);
// Define signature constants
const STAKE_SIGNATURE = '0x3a29dbae';
From a40fcb9f2b1204b14e10b5637828bf0bb23fb4bb Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Wed, 18 Dec 2024 15:59:32 +0200
Subject: [PATCH 3/8] chore(suite): add Solana epoch info to account, upgrade
SDK to latest
---
packages/suite/package.json | 2 +-
.../AccountsMenu/AccountSection.tsx | 6 +-
.../StakingDashboard/StakingDashboard.tsx | 15 +-
.../src/accounts/accountsReducer.ts | 2 +-
suite-common/wallet-types/src/account.ts | 1 +
suite-common/wallet-utils/package.json | 2 +-
suite-common/wallet-utils/src/accountUtils.ts | 6 +-
yarn.lock | 203 +++++++++---------
8 files changed, 120 insertions(+), 117 deletions(-)
diff --git a/packages/suite/package.json b/packages/suite/package.json
index a8839304dc7..f5b7beba1fe 100644
--- a/packages/suite/package.json
+++ b/packages/suite/package.json
@@ -18,7 +18,7 @@
"test-unit:watch": "yarn g:jest -o --watch"
},
"dependencies": {
- "@everstake/wallet-sdk": "^1.0.5",
+ "@everstake/wallet-sdk": "^1.0.7",
"@floating-ui/react": "^0.26.9",
"@formatjs/intl": "2.10.0",
"@hookform/resolvers": "3.9.1",
diff --git a/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx b/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx
index 28b1b92baee..746c157c4b0 100644
--- a/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx
+++ b/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx
@@ -1,6 +1,6 @@
import { Account } from '@suite-common/wallet-types';
import { selectCoinDefinitions } from '@suite-common/token-definitions';
-import { selectAccountHasStaked, selectStakingAccounts } from '@suite-common/wallet-core';
+import { selectAccountHasStaked, selectSolStakingAccounts } from '@suite-common/wallet-core';
import { isSupportedStakingNetworkSymbol } from '@suite-common/wallet-utils';
import { useSelector } from 'src/hooks/suite';
@@ -37,9 +37,9 @@ export const AccountSection = ({
const coinDefinitions = useSelector(state => selectCoinDefinitions(state, symbol));
const hasStaked = useSelector(state => selectAccountHasStaked(state, account.key));
- const stakingAccounts = useSelector(state => selectStakingAccounts(state, account.key));
+ const solStakingAccounts = useSelector(state => selectSolStakingAccounts(state, account.key));
// TODO: remove isDebugModeActive when staking will be ready for launch
- const hasStakingAccount = !!stakingAccounts?.length && isDebugModeActive; // for solana
+ const hasStakingAccount = !!solStakingAccounts?.length && isDebugModeActive; // for solana
const isStakeShown =
isSupportedStakingNetworkSymbol(symbol) && (hasStaked || hasStakingAccount);
diff --git a/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
index 916260de595..85871f348a4 100644
--- a/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
@@ -1,5 +1,6 @@
-import { selectAccountHasStaked } from '@suite-common/wallet-core';
+import { selectAccountHasStaked, selectSolStakingAccounts } from '@suite-common/wallet-core';
import { SelectedAccountStatus } from '@suite-common/wallet-types';
+import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
import { WalletLayout } from 'src/components/wallet';
import { useSelector } from 'src/hooks/suite';
@@ -17,16 +18,20 @@ export const StakingDashboard = ({ selectedAccount, dashboard }: StakingDashboar
selectAccountHasStaked(state, selectedAccount?.account?.key ?? ''),
);
- if (!selectedAccount) return null;
+ const { account } = selectedAccount;
+
+ const solStakingAccounts = useSelector(state => selectSolStakingAccounts(state, account?.key));
+
+ const hasSolStakingAccount = !!solStakingAccounts?.length;
+ const shouldShowDashboard = hasStaked || hasSolStakingAccount;
return (
- {hasStaked ? dashboard : }
-
+ {shouldShowDashboard ? dashboard : }
);
diff --git a/suite-common/wallet-core/src/accounts/accountsReducer.ts b/suite-common/wallet-core/src/accounts/accountsReducer.ts
index 3a164cef050..a3fbec3d381 100644
--- a/suite-common/wallet-core/src/accounts/accountsReducer.ts
+++ b/suite-common/wallet-core/src/accounts/accountsReducer.ts
@@ -473,7 +473,7 @@ export const selectIsDeviceNotEmpty = createMemoizedSelector(
},
);
-export const selectStakingAccounts = createMemoizedSelector([selectAccountByKey], account => {
+export const selectSolStakingAccounts = createMemoizedSelector([selectAccountByKey], account => {
if (!account || account.networkType !== 'solana') return null;
return account.misc.solStakingAccounts ?? [];
diff --git a/suite-common/wallet-types/src/account.ts b/suite-common/wallet-types/src/account.ts
index 03f7989f742..df8ff9b7a31 100644
--- a/suite-common/wallet-types/src/account.ts
+++ b/suite-common/wallet-types/src/account.ts
@@ -63,6 +63,7 @@ type AccountNetworkSpecific =
misc: {
rent?: number;
solStakingAccounts?: SolanaStakingAccount[];
+ solEpoch?: number;
};
marker: undefined;
page: AccountInfo['page'];
diff --git a/suite-common/wallet-utils/package.json b/suite-common/wallet-utils/package.json
index cb8b021d338..7e92b632c93 100644
--- a/suite-common/wallet-utils/package.json
+++ b/suite-common/wallet-utils/package.json
@@ -12,7 +12,7 @@
"test-unit:watch": "yarn g:jest -c ../../jest.config.base.js -o --watch"
},
"dependencies": {
- "@everstake/wallet-sdk": "^1.0.5",
+ "@everstake/wallet-sdk": "^1.0.7",
"@mobily/ts-belt": "^3.13.1",
"@solana-program/compute-budget": "^0.6.1",
"@solana-program/system": "^0.6.2",
diff --git a/suite-common/wallet-utils/src/accountUtils.ts b/suite-common/wallet-utils/src/accountUtils.ts
index 661560288fd..2274fbfbfca 100644
--- a/suite-common/wallet-utils/src/accountUtils.ts
+++ b/suite-common/wallet-utils/src/accountUtils.ts
@@ -869,7 +869,11 @@ export const getAccountSpecific = (accountInfo: Partial, networkTyp
if (networkType === 'solana') {
return {
networkType,
- misc: { rent: misc?.rent, solStakingAccounts: misc?.solStakingAccounts },
+ misc: {
+ rent: misc?.rent,
+ solStakingAccounts: misc?.solStakingAccounts,
+ solEpoch: misc?.solEpoch,
+ },
marker: undefined,
page: accountInfo.page,
};
diff --git a/yarn.lock b/yarn.lock
index 40bbdee6305..0eb2670cae9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3198,15 +3198,15 @@ __metadata:
languageName: node
linkType: hard
-"@everstake/wallet-sdk@npm:^1.0.5":
- version: 1.0.5
- resolution: "@everstake/wallet-sdk@npm:1.0.5"
+"@everstake/wallet-sdk@npm:^1.0.7":
+ version: 1.0.7
+ resolution: "@everstake/wallet-sdk@npm:1.0.7"
dependencies:
- "@solana/web3.js": "npm:^1.75.0"
- bignumber.js: "npm:^9.1.2"
- superstruct: "npm:^0.16.7"
- web3: "npm:^4.14.0"
- checksum: 10/00715f194f44f2c9b2631b0d5e4b3c31be1483b2e750f8a1c9554ca3cc601f2756798ae5e78c7eb4ccc88c2e5c585e6442b654e6081570232489d667d241f4f9
+ "@solana/web3.js": "npm:1.95.8"
+ bignumber.js: "npm:9.1.2"
+ superstruct: "npm:2.0.2"
+ web3: "npm:4.16.0"
+ checksum: 10/ed96a3ad15f3da3cf5d8884bca6bdbdc6e20bdec2b5b24ab8046cd5f7cfe25b760ef00e75723c9802f6f45e0df03c2d512b9e17cc8e0df18d647f10aa7e3a1d3
languageName: node
linkType: hard
@@ -9584,7 +9584,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@suite-common/wallet-utils@workspace:suite-common/wallet-utils"
dependencies:
- "@everstake/wallet-sdk": "npm:^1.0.5"
+ "@everstake/wallet-sdk": "npm:^1.0.7"
"@mobily/ts-belt": "npm:^3.13.1"
"@solana-program/compute-budget": "npm:^0.6.1"
"@solana-program/system": "npm:^0.6.2"
@@ -11343,7 +11343,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@trezor/blockchain-link-types@workspace:packages/blockchain-link-types"
dependencies:
- "@everstake/wallet-sdk": "npm:^1.0.5"
+ "@everstake/wallet-sdk": "npm:^1.0.7"
"@solana/web3.js": "npm:^2.0.0"
"@trezor/type-utils": "workspace:*"
"@trezor/utxo-lib": "workspace:*"
@@ -11357,7 +11357,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@trezor/blockchain-link-utils@workspace:packages/blockchain-link-utils"
dependencies:
- "@everstake/wallet-sdk": "npm:^1.0.5"
+ "@everstake/wallet-sdk": "npm:^1.0.7"
"@mobily/ts-belt": "npm:^3.13.1"
"@trezor/blockchain-link-types": "workspace:*"
"@trezor/env-utils": "workspace:*"
@@ -11374,7 +11374,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@trezor/blockchain-link@workspace:packages/blockchain-link"
dependencies:
- "@everstake/wallet-sdk": "npm:^1.0.5"
+ "@everstake/wallet-sdk": "npm:^1.0.7"
"@solana-program/token": "npm:^0.4.1"
"@solana-program/token-2022": "npm:^0.3.1"
"@solana/web3.js": "npm:^2.0.0"
@@ -12334,7 +12334,7 @@ __metadata:
resolution: "@trezor/suite@workspace:packages/suite"
dependencies:
"@crowdin/cli": "npm:^4.0.0"
- "@everstake/wallet-sdk": "npm:^1.0.5"
+ "@everstake/wallet-sdk": "npm:^1.0.7"
"@floating-ui/react": "npm:^0.26.9"
"@formatjs/cli": "npm:^6.2.7"
"@formatjs/intl": "npm:2.10.0"
@@ -16363,7 +16363,7 @@ __metadata:
languageName: node
linkType: hard
-"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.1.2":
+"bignumber.js@npm:9.1.2, bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.1.2":
version: 9.1.2
resolution: "bignumber.js@npm:9.1.2"
checksum: 10/d89b8800a987225d2c00dcbf8a69dc08e92aa0880157c851c287b307d31ceb2fc2acb0c62c3e3a3d42b6c5fcae9b004035f13eb4386e56d529d7edac18d5c9d8
@@ -39230,6 +39230,13 @@ __metadata:
languageName: node
linkType: hard
+"superstruct@npm:2.0.2, superstruct@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "superstruct@npm:2.0.2"
+ checksum: 10/10e1944a9da4baee187fbaa6c5d97d7af266b55786dfe50bce67f0f1e7d93f1a5a42dd51e245a2e16404f8336d07c21c67f1c1fbc4ad0a252d3d2601d6c926da
+ languageName: node
+ linkType: hard
+
"superstruct@npm:^0.12.1":
version: 0.12.2
resolution: "superstruct@npm:0.12.2"
@@ -39237,20 +39244,6 @@ __metadata:
languageName: node
linkType: hard
-"superstruct@npm:^0.16.7":
- version: 0.16.7
- resolution: "superstruct@npm:0.16.7"
- checksum: 10/2f3fb30f8fa3e012fb74fc6f8c850b13b6f6c4b8a412186996b55da8292586205dda8495bcd2e850436a6476dd25f7d0d226905f1ed97162903dcf4cce59d2b8
- languageName: node
- linkType: hard
-
-"superstruct@npm:^2.0.2":
- version: 2.0.2
- resolution: "superstruct@npm:2.0.2"
- checksum: 10/10e1944a9da4baee187fbaa6c5d97d7af266b55786dfe50bce67f0f1e7d93f1a5a42dd51e245a2e16404f8336d07c21c67f1c1fbc4ad0a252d3d2601d6c926da
- languageName: node
- linkType: hard
-
"supports-color@npm:^2.0.0":
version: 2.0.0
resolution: "supports-color@npm:2.0.0"
@@ -42101,76 +42094,76 @@ __metadata:
languageName: node
linkType: hard
-"web3-core@npm:^4.4.0, web3-core@npm:^4.5.0, web3-core@npm:^4.5.1, web3-core@npm:^4.6.0, web3-core@npm:^4.7.0":
- version: 4.7.0
- resolution: "web3-core@npm:4.7.0"
+"web3-core@npm:^4.4.0, web3-core@npm:^4.5.0, web3-core@npm:^4.6.0, web3-core@npm:^4.7.1":
+ version: 4.7.1
+ resolution: "web3-core@npm:4.7.1"
dependencies:
- web3-errors: "npm:^1.3.0"
- web3-eth-accounts: "npm:^4.2.1"
+ web3-errors: "npm:^1.3.1"
+ web3-eth-accounts: "npm:^4.3.1"
web3-eth-iban: "npm:^4.0.7"
web3-providers-http: "npm:^4.2.0"
web3-providers-ipc: "npm:^4.0.7"
web3-providers-ws: "npm:^4.0.8"
- web3-types: "npm:^1.8.1"
- web3-utils: "npm:^4.3.2"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
dependenciesMeta:
web3-providers-ipc:
optional: true
- checksum: 10/76f740cff838f32e21992a2d263b770256562fe9cad5c7b6b220db9990b09b940ff7eb7261400dab971fc2a00bfb75ab4f89f9b5b67c4e70cac099072c177944
+ checksum: 10/c6b9447e62f5c57ccc3c96492adf5630cb3256968c15ce5675c660dec1f6da0bf60397efa88588029640f749ff45a1adaa0167a402ba0b4a46e600d8eda76334
languageName: node
linkType: hard
-"web3-errors@npm:^1.1.3, web3-errors@npm:^1.2.0, web3-errors@npm:^1.3.0":
- version: 1.3.0
- resolution: "web3-errors@npm:1.3.0"
+"web3-errors@npm:^1.1.3, web3-errors@npm:^1.2.0, web3-errors@npm:^1.3.0, web3-errors@npm:^1.3.1":
+ version: 1.3.1
+ resolution: "web3-errors@npm:1.3.1"
dependencies:
- web3-types: "npm:^1.7.0"
- checksum: 10/613b01013b697dcaaad0ff9cfac8c4c69557fb0ffbc9efe86ac3eaf5179c6f18687fdcd2e53faf0a2c3ee68409491171968eb2017444cb14ffa74663ff77556f
+ web3-types: "npm:^1.10.0"
+ checksum: 10/0d1cb0e02701a4bd619f856b0a6702fdd4cdc0a434029c3c3dcde3f3cc4acaca418117ad10238002aa697745840e7fd312bd43ad5341482b3ff8f9e6eb438a31
languageName: node
linkType: hard
-"web3-eth-abi@npm:^4.2.3, web3-eth-abi@npm:^4.3.0":
- version: 4.3.0
- resolution: "web3-eth-abi@npm:4.3.0"
+"web3-eth-abi@npm:^4.4.1":
+ version: 4.4.1
+ resolution: "web3-eth-abi@npm:4.4.1"
dependencies:
abitype: "npm:0.7.1"
- web3-errors: "npm:^1.3.0"
- web3-types: "npm:^1.8.1"
- web3-utils: "npm:^4.3.2"
+ web3-errors: "npm:^1.3.1"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
- checksum: 10/e490ad7142ae14c2def2b596551b54e83727af76a96ee4854e236cd19477028960eb944af258c42efc4646fc0b685ae4f3b3f2e0c4c75e15bfc346fc05c1ff6e
+ checksum: 10/0c7f4f9f05f04e0ac98f6029edfb3bf7e514efc325f6be83e999203f49c7a0cdc9759f4b1011ce12d80c2044c74a867f7fc0ee83538408c2ebb4c9f407027b7f
languageName: node
linkType: hard
-"web3-eth-accounts@npm:^4.2.1":
- version: 4.2.1
- resolution: "web3-eth-accounts@npm:4.2.1"
+"web3-eth-accounts@npm:^4.3.1":
+ version: 4.3.1
+ resolution: "web3-eth-accounts@npm:4.3.1"
dependencies:
"@ethereumjs/rlp": "npm:^4.0.1"
crc-32: "npm:^1.2.2"
ethereum-cryptography: "npm:^2.0.0"
- web3-errors: "npm:^1.3.0"
- web3-types: "npm:^1.7.0"
- web3-utils: "npm:^4.3.1"
+ web3-errors: "npm:^1.3.1"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
- checksum: 10/a19a2bad97ed9cc7a82af6ce6acba906f3ed8829116bda1dcd72dd4ee7e31d332239f7464cfa89f053dffcef857fc284777474571e68878dbc2377057f9ea329
+ checksum: 10/f8b689146c908d88b983bd467c3e794ed96e284490aa3f74e665580202db4f0826d4108f0aa95dc6ef1e14f9a8a41939ff2c4485e9713744dc6474d7082d9239
languageName: node
linkType: hard
-"web3-eth-contract@npm:^4.5.0, web3-eth-contract@npm:^4.7.0":
- version: 4.7.0
- resolution: "web3-eth-contract@npm:4.7.0"
+"web3-eth-contract@npm:^4.5.0, web3-eth-contract@npm:^4.7.2":
+ version: 4.7.2
+ resolution: "web3-eth-contract@npm:4.7.2"
dependencies:
"@ethereumjs/rlp": "npm:^5.0.2"
- web3-core: "npm:^4.5.1"
- web3-errors: "npm:^1.3.0"
- web3-eth: "npm:^4.8.2"
- web3-eth-abi: "npm:^4.2.3"
- web3-types: "npm:^1.7.0"
- web3-utils: "npm:^4.3.1"
+ web3-core: "npm:^4.7.1"
+ web3-errors: "npm:^1.3.1"
+ web3-eth: "npm:^4.11.1"
+ web3-eth-abi: "npm:^4.4.1"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
- checksum: 10/1ae62530fdf1389aa896af527c028e3c39ca16e2318e010d07e6b7d421abb8b1c6a6dbe834d3317098a29a6c8ad47f057d17e1c9602dc0060b689c331c3ce440
+ checksum: 10/f5dd22199a69c6f10b0c38daee790341f80247a0155bad03e7c1a9ffad2d6c47722010b4fd0e3fe7832a43eb72a2fceadfd2892712ef199898c1e43067a92c0d
languageName: node
linkType: hard
@@ -42217,22 +42210,22 @@ __metadata:
languageName: node
linkType: hard
-"web3-eth@npm:^4.10.0, web3-eth@npm:^4.8.0, web3-eth@npm:^4.8.2, web3-eth@npm:^4.9.0":
- version: 4.10.0
- resolution: "web3-eth@npm:4.10.0"
+"web3-eth@npm:^4.11.1, web3-eth@npm:^4.8.0, web3-eth@npm:^4.9.0":
+ version: 4.11.1
+ resolution: "web3-eth@npm:4.11.1"
dependencies:
setimmediate: "npm:^1.0.5"
- web3-core: "npm:^4.7.0"
- web3-errors: "npm:^1.3.0"
- web3-eth-abi: "npm:^4.3.0"
- web3-eth-accounts: "npm:^4.2.1"
+ web3-core: "npm:^4.7.1"
+ web3-errors: "npm:^1.3.1"
+ web3-eth-abi: "npm:^4.4.1"
+ web3-eth-accounts: "npm:^4.3.1"
web3-net: "npm:^4.1.0"
web3-providers-ws: "npm:^4.0.8"
web3-rpc-methods: "npm:^1.3.0"
- web3-types: "npm:^1.8.1"
- web3-utils: "npm:^4.3.2"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
- checksum: 10/9dcbce05e160cde8148f2ad9919c56e0ad242beb05c2a013219a7d7ab697f5ca7e3c55089d27bdff50c28eebc36a9e1ae4b8b40c57432bdf5a200ace46d8cdc8
+ checksum: 10/b39f5f1559a012ece0017f3976207ffb5358c4ebb2e8518721efcc4975005ed8948814613795d1ceee67eb28f33608cbc89f6b231534241052de231c6477ed17
languageName: node
linkType: hard
@@ -42296,37 +42289,37 @@ __metadata:
languageName: node
linkType: hard
-"web3-rpc-providers@npm:^1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "web3-rpc-providers@npm:1.0.0-rc.2"
+"web3-rpc-providers@npm:^1.0.0-rc.4":
+ version: 1.0.0-rc.4
+ resolution: "web3-rpc-providers@npm:1.0.0-rc.4"
dependencies:
- web3-errors: "npm:^1.3.0"
+ web3-errors: "npm:^1.3.1"
web3-providers-http: "npm:^4.2.0"
web3-providers-ws: "npm:^4.0.8"
- web3-types: "npm:^1.7.0"
- web3-utils: "npm:^4.3.1"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
- checksum: 10/8f1fb9a798cec61f245e6e13b796f1b43231d7bd5eb0db8b5b636b4e2facc75ee1453541237f057292ec7ecdb56ba284c29734cd17ea010c27c5580d6edea787
+ checksum: 10/a6dff5ce76e6905eb3e8e7175984305b859a35f17ffad9511371e0840097cdccc4d8dd4a4bc893aeb78f93c22034b4c73cac79551a4d7cba204e55590018909b
languageName: node
linkType: hard
-"web3-types@npm:^1.3.0, web3-types@npm:^1.6.0, web3-types@npm:^1.7.0, web3-types@npm:^1.8.0, web3-types@npm:^1.8.1":
- version: 1.8.1
- resolution: "web3-types@npm:1.8.1"
- checksum: 10/e64f7e59806e1875e34a1daaa34e98043fcaebfd2fc96ab9948789129e8da73e812de62f63cf32c347c202d6ba40121f87516b183bb7074f27331fc7f90a46d9
+"web3-types@npm:^1.10.0, web3-types@npm:^1.3.0, web3-types@npm:^1.6.0, web3-types@npm:^1.7.0, web3-types@npm:^1.8.0":
+ version: 1.10.0
+ resolution: "web3-types@npm:1.10.0"
+ checksum: 10/849f05a001896b27082c5b5c46c62b65a28f463366eeec7223802418a61db6d3487ebfb73d1fe6dcad3f0849a76e20706098819cb4e266df4f75ca24617e62a1
languageName: node
linkType: hard
-"web3-utils@npm:^4.0.7, web3-utils@npm:^4.3.0, web3-utils@npm:^4.3.1, web3-utils@npm:^4.3.2":
- version: 4.3.2
- resolution: "web3-utils@npm:4.3.2"
+"web3-utils@npm:^4.0.7, web3-utils@npm:^4.3.0, web3-utils@npm:^4.3.1, web3-utils@npm:^4.3.2, web3-utils@npm:^4.3.3":
+ version: 4.3.3
+ resolution: "web3-utils@npm:4.3.3"
dependencies:
ethereum-cryptography: "npm:^2.0.0"
eventemitter3: "npm:^5.0.1"
- web3-errors: "npm:^1.3.0"
- web3-types: "npm:^1.8.1"
+ web3-errors: "npm:^1.3.1"
+ web3-types: "npm:^1.10.0"
web3-validator: "npm:^2.0.6"
- checksum: 10/3fff4418782b0fb05587c9150daedbad90a9ebadd1e3b115a71a72fea885ce10341054758087e47ab81f3cc67da812eaf9077f05e07a872e63d27299f285a81a
+ checksum: 10/c91ebbe67e469fe184ab258564b1f002f6f0e563a91637429c8e5bd3f0653b970d0c45dac544402021a5512297f0bea39aa2dd0b4c9bc6f54d4b58897f2dd002
languageName: node
linkType: hard
@@ -42343,16 +42336,16 @@ __metadata:
languageName: node
linkType: hard
-"web3@npm:^4.14.0":
- version: 4.14.0
- resolution: "web3@npm:4.14.0"
+"web3@npm:4.16.0":
+ version: 4.16.0
+ resolution: "web3@npm:4.16.0"
dependencies:
- web3-core: "npm:^4.7.0"
- web3-errors: "npm:^1.3.0"
- web3-eth: "npm:^4.10.0"
- web3-eth-abi: "npm:^4.3.0"
- web3-eth-accounts: "npm:^4.2.1"
- web3-eth-contract: "npm:^4.7.0"
+ web3-core: "npm:^4.7.1"
+ web3-errors: "npm:^1.3.1"
+ web3-eth: "npm:^4.11.1"
+ web3-eth-abi: "npm:^4.4.1"
+ web3-eth-accounts: "npm:^4.3.1"
+ web3-eth-contract: "npm:^4.7.2"
web3-eth-ens: "npm:^4.4.0"
web3-eth-iban: "npm:^4.0.7"
web3-eth-personal: "npm:^4.1.0"
@@ -42360,11 +42353,11 @@ __metadata:
web3-providers-http: "npm:^4.2.0"
web3-providers-ws: "npm:^4.0.8"
web3-rpc-methods: "npm:^1.3.0"
- web3-rpc-providers: "npm:^1.0.0-rc.2"
- web3-types: "npm:^1.8.1"
- web3-utils: "npm:^4.3.2"
+ web3-rpc-providers: "npm:^1.0.0-rc.4"
+ web3-types: "npm:^1.10.0"
+ web3-utils: "npm:^4.3.3"
web3-validator: "npm:^2.0.6"
- checksum: 10/cd8db8ace62c73b7c9ae4a1e1eaec99d8c107295e75e1e6fe770c52bcce919ef17c1d84b835d33c595ac72972207f23ae5cbdcb5382656709cc91db82437fc07
+ checksum: 10/8d63e70404914d2717d2675ba19350f112b07e50583a0703a68dd326eeb43a5c82b56f1165f4339cd89e697967581e0cd65fdb42ca0f1150fb7a3ce612f1a829
languageName: node
linkType: hard
From 1cb198ee8ba444eebfcae00dd83593984cb98a58 Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Wed, 18 Dec 2024 16:15:24 +0200
Subject: [PATCH 4/8] feat(suite): add utility to get staking limits by network
---
.../components/suite/FormFractionButtons.tsx | 27 +++---
.../StakeModal/StakeEthForm/Inputs.tsx | 14 ++--
.../suite/src/hooks/wallet/useStakeEthForm.ts | 21 +++--
.../reducers/wallet/selectedAccountReducer.ts | 11 ---
.../suite/src/utils/suite/ethereumStaking.ts | 19 +++--
suite-common/wallet-utils/src/stakingUtils.ts | 82 +++++++++++++++++--
6 files changed, 121 insertions(+), 53 deletions(-)
diff --git a/packages/suite/src/components/suite/FormFractionButtons.tsx b/packages/suite/src/components/suite/FormFractionButtons.tsx
index 2e84f6367b0..79ad5c4820c 100644
--- a/packages/suite/src/components/suite/FormFractionButtons.tsx
+++ b/packages/suite/src/components/suite/FormFractionButtons.tsx
@@ -2,8 +2,9 @@ import styled from 'styled-components';
import { Button, Tooltip } from '@trezor/components';
import { BigNumber } from '@trezor/utils/src/bigNumber';
-import { MIN_ETH_AMOUNT_FOR_STAKING } from '@suite-common/wallet-constants';
-import { getNetworkDisplaySymbol, NetworkSymbol } from '@suite-common/wallet-config';
+import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
+import { Account } from '@suite-common/wallet-types';
+import { getStakingLimitsByNetwork } from '@suite-common/wallet-utils';
import { Translation } from 'src/components/suite';
@@ -24,8 +25,7 @@ interface FormFractionButtonsProps {
setRatioAmount: (divisor: number) => void;
setMax: () => void;
isDisabled?: boolean;
- symbol: NetworkSymbol;
- totalAmount?: number | string;
+ account: Account;
decimals?: number;
}
@@ -33,23 +33,26 @@ export const FormFractionButtons = ({
setRatioAmount,
setMax,
isDisabled = false,
- symbol,
- totalAmount,
+ account,
decimals,
}: FormFractionButtonsProps) => {
+ const { symbol, formattedBalance: totalAmount } = account;
+
+ const { MIN_AMOUNT_FOR_STAKING } = getStakingLimitsByNetwork(account);
+
const isFractionButtonDisabled = (divisor: number) => {
if (!totalAmount || !decimals) return false;
return new BigNumber(totalAmount)
.dividedBy(divisor)
.decimalPlaces(decimals)
- .lte(MIN_ETH_AMOUNT_FOR_STAKING);
+ .lte(MIN_AMOUNT_FOR_STAKING);
};
const is10PercentDisabled = isDisabled || isFractionButtonDisabled(10);
const is25PercentDisabled = isDisabled || isFractionButtonDisabled(4);
const is50PercentDisabled = isDisabled || isFractionButtonDisabled(2);
const isMaxDisabled =
- isDisabled || new BigNumber(totalAmount || '0').lt(MIN_ETH_AMOUNT_FOR_STAKING);
+ isDisabled || new BigNumber(totalAmount || '0').lt(MIN_AMOUNT_FOR_STAKING);
const displaySymbol = getNetworkDisplaySymbol(symbol);
@@ -61,8 +64,8 @@ export const FormFractionButtons = ({
)
@@ -79,8 +82,8 @@ export const FormFractionButtons = ({
)
@@ -97,8 +100,8 @@ export const FormFractionButtons = ({
)
@@ -115,7 +118,7 @@ export const FormFractionButtons = ({
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx
index 16c9525d1ca..1a779b02531 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx
@@ -1,8 +1,7 @@
import { Icon, Banner, Column, Text, Button } from '@trezor/components';
-import { getInputState } from '@suite-common/wallet-utils';
+import { getInputState, getStakingLimitsByNetwork } from '@suite-common/wallet-utils';
import { useFormatters } from '@suite-common/formatters';
import { formInputsMaxLength } from '@suite-common/validators';
-import { MIN_ETH_FOR_WITHDRAWALS } from '@suite-common/wallet-constants';
import { spacings } from '@trezor/theme';
import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
@@ -42,6 +41,8 @@ export const Inputs = () => {
clearForm,
} = useStakeEthFormContext();
+ const { MIN_FOR_WITHDRAWALS, MAX_AMOUNT_FOR_STAKING } = getStakingLimitsByNetwork(account);
+
const cryptoError = errors.cryptoInput;
const fiatError = errors.fiatInput;
const hasValues = Boolean(watch(FIAT_INPUT) || watch(CRYPTO_INPUT));
@@ -57,7 +58,7 @@ export const Inputs = () => {
required: translationString('AMOUNT_IS_NOT_SET'),
validate: {
min: validateMin(translationString),
- max: validateStakingMax(translationString),
+ max: validateStakingMax(translationString, { maxAmount: MAX_AMOUNT_FOR_STAKING }),
decimals: validateDecimals(translationString, { decimals: network.decimals }),
reserveOrBalance: validateReserveOrBalance(translationString, {
account,
@@ -82,8 +83,7 @@ export const Inputs = () => {
}
@@ -132,8 +132,8 @@ export const Inputs = () => {
: 'TR_STAKE_LEFT_AMOUNT_FOR_WITHDRAWAL'
}
values={{
- amount: MIN_ETH_FOR_WITHDRAWALS.toString(),
networkSymbol: displaySymbol,
+ amount: MIN_FOR_WITHDRAWALS.toString(),
}}
/>
@@ -144,8 +144,8 @@ export const Inputs = () => {
diff --git a/packages/suite/src/hooks/wallet/useStakeEthForm.ts b/packages/suite/src/hooks/wallet/useStakeEthForm.ts
index ff57e718442..0136dfd8c1d 100644
--- a/packages/suite/src/hooks/wallet/useStakeEthForm.ts
+++ b/packages/suite/src/hooks/wallet/useStakeEthForm.ts
@@ -9,16 +9,12 @@ import {
fromFiatCurrency,
getFeeLevels,
getFiatRateKey,
+ getStakingLimitsByNetwork,
toFiatCurrency,
} from '@suite-common/wallet-utils';
import { isChanged } from '@suite-common/suite-utils';
import { PrecomposedTransactionFinal, StakeFormState } from '@suite-common/wallet-types';
import { StakeContextValues, selectFiatRatesByFiatRateKey } from '@suite-common/wallet-core';
-import {
- MIN_ETH_AMOUNT_FOR_STAKING,
- MIN_ETH_BALANCE_FOR_STAKING,
- MIN_ETH_FOR_WITHDRAWALS,
-} from '@suite-common/wallet-constants';
import { useDispatch, useSelector, useTranslation } from 'src/hooks/suite';
import { saveComposedTransactionInfo } from 'src/actions/wallet/coinmarket/coinmarketCommonActions';
@@ -57,9 +53,12 @@ export const useStakeEthForm = ({ selectedAccount }: UseStakeFormsProps): StakeC
selectFiatRatesByFiatRateKey(state, getFiatRateKey(symbol, localCurrency), 'current'),
);
+ const { MIN_AMOUNT_FOR_STAKING, MIN_FOR_WITHDRAWALS, MIN_BALANCE_FOR_STAKING } =
+ getStakingLimitsByNetwork(account);
+
const amountLimits: CryptoAmountLimitProps = {
currency: symbol,
- minCrypto: MIN_ETH_AMOUNT_FOR_STAKING.toString(),
+ minCrypto: MIN_AMOUNT_FOR_STAKING.toString(),
maxCrypto: account.formattedBalance,
};
@@ -196,14 +195,14 @@ export const useStakeEthForm = ({ selectedAccount }: UseStakeFormsProps): StakeC
const balanceMinusFee = balance.minus(composedFee);
if (
- cryptoValue.gt(balanceMinusFee.minus(MIN_ETH_FOR_WITHDRAWALS)) &&
+ cryptoValue.gt(balanceMinusFee.minus(MIN_FOR_WITHDRAWALS)) &&
cryptoValue.lt(balanceMinusFee) &&
- cryptoValue.gte(MIN_ETH_AMOUNT_FOR_STAKING)
+ cryptoValue.gte(MIN_AMOUNT_FOR_STAKING)
) {
setIsAdviceForWithdrawalWarningShown(true);
}
},
- [composedFee],
+ [composedFee, MIN_FOR_WITHDRAWALS, MIN_AMOUNT_FOR_STAKING],
);
const onCryptoAmountChange = useCallback(
@@ -288,14 +287,14 @@ export const useStakeEthForm = ({ selectedAccount }: UseStakeFormsProps): StakeC
const amount = new BigNumber(account.formattedBalance).toString();
- if (amount < MIN_ETH_BALANCE_FOR_STAKING.toString()) {
+ if (amount < MIN_BALANCE_FOR_STAKING.toString()) {
setIsLessAmountForWithdrawalWarningShown(true);
}
setValue(OUTPUT_AMOUNT, amount || '', { shouldDirty: true });
await composeRequest(CRYPTO_INPUT);
setIsAmountForWithdrawalWarningShown(true);
- }, [account.formattedBalance, clearErrors, composeRequest, setValue]);
+ }, [account.formattedBalance, clearErrors, composeRequest, setValue, MIN_BALANCE_FOR_STAKING]);
useEffect(() => {
if (formState.errors[CRYPTO_INPUT]) {
diff --git a/packages/suite/src/reducers/wallet/selectedAccountReducer.ts b/packages/suite/src/reducers/wallet/selectedAccountReducer.ts
index dddf53ff897..1416921f80f 100644
--- a/packages/suite/src/reducers/wallet/selectedAccountReducer.ts
+++ b/packages/suite/src/reducers/wallet/selectedAccountReducer.ts
@@ -1,5 +1,4 @@
import { accountsActions } from '@suite-common/wallet-core';
-import { MIN_ETH_BALANCE_FOR_STAKING } from '@suite-common/wallet-constants';
import type { SelectedAccountStatus } from '@suite-common/wallet-types';
import type { Action } from 'src/types/suite';
@@ -45,16 +44,6 @@ export const selectSelectedAccountBalance = (state: SelectedAccountRootState) =>
export const selectSelectedAccountParams = (state: SelectedAccountRootState) =>
state.wallet.selectedAccount.params;
-export const selectSelectedAccountHasSufficientEthForStaking = (
- state: SelectedAccountRootState,
-) => {
- const { formattedBalance, symbol } = selectSelectedAccount(state) ?? {};
-
- if (typeof formattedBalance !== 'string' || symbol !== 'eth') return false;
-
- return MIN_ETH_BALANCE_FOR_STAKING.isLessThanOrEqualTo(formattedBalance);
-};
-
export const selectIsSelectedAccountLoaded = (state: SelectedAccountRootState) =>
state.wallet.selectedAccount.status === 'loaded';
diff --git a/packages/suite/src/utils/suite/ethereumStaking.ts b/packages/suite/src/utils/suite/ethereumStaking.ts
index cc467819974..3b91937d955 100644
--- a/packages/suite/src/utils/suite/ethereumStaking.ts
+++ b/packages/suite/src/utils/suite/ethereumStaking.ts
@@ -11,13 +11,17 @@ import {
DEFAULT_PAYMENT,
STAKE_GAS_LIMIT_RESERVE,
MIN_ETH_AMOUNT_FOR_STAKING,
- MAX_ETH_AMOUNT_FOR_STAKING,
UNSTAKE_INTERCHANGES,
WALLET_SDK_SOURCE,
UNSTAKING_ETH_PERIOD,
} from '@suite-common/wallet-constants';
import type { NetworkSymbol } from '@suite-common/wallet-config';
-import { getEthereumEstimateFeeParams, isPending, sanitizeHex } from '@suite-common/wallet-utils';
+import {
+ getEthereumEstimateFeeParams,
+ isPending,
+ isSupportedEthStakingNetworkSymbol,
+ sanitizeHex,
+} from '@suite-common/wallet-utils';
import TrezorConnect, { EthereumTransaction, Success, InternalTransfer } from '@trezor/connect';
import { BigNumber } from '@trezor/utils/src/bigNumber';
import { ValidatorsQueue } from '@suite-common/wallet-core';
@@ -633,22 +637,27 @@ export const calculateGains = (input: string, apy: number, divisor: number) => {
};
interface ValidateMaxOptions {
+ maxAmount: BigNumber;
except?: boolean;
}
export const validateStakingMax =
- (translationString: TranslationFunction, options?: ValidateMaxOptions) => (value: string) => {
- if (!options?.except && value && BigNumber(value).gt(MAX_ETH_AMOUNT_FOR_STAKING)) {
+ (translationString: TranslationFunction, { except, maxAmount }: ValidateMaxOptions) =>
+ (value: string) => {
+ if (!except && value && BigNumber(value).gt(maxAmount)) {
return translationString('AMOUNT_EXCEEDS_MAX', {
- maxAmount: MAX_ETH_AMOUNT_FOR_STAKING.toString(),
+ maxAmount: maxAmount.toString(),
});
}
};
+
export const simulateUnstake = async ({
amount,
from,
symbol,
}: StakeTxBaseArgs & { amount: string }) => {
+ if (!isSupportedEthStakingNetworkSymbol(symbol)) return null;
+
const ethNetwork = getEthNetworkForWalletSdk(symbol);
const ethereumClient = new Ethereum(ethNetwork);
const { addressContractPool } = getEthNetworkAddresses(symbol);
diff --git a/suite-common/wallet-utils/src/stakingUtils.ts b/suite-common/wallet-utils/src/stakingUtils.ts
index 74bcdaf427a..1286f35a4b1 100644
--- a/suite-common/wallet-utils/src/stakingUtils.ts
+++ b/suite-common/wallet-utils/src/stakingUtils.ts
@@ -1,27 +1,95 @@
-import { Account } from '@suite-common/wallet-types';
+import { Account, StakingPoolExtended } from '@suite-common/wallet-types';
import { NetworkSymbol } from '@suite-common/wallet-config';
+import {
+ MAX_ETH_AMOUNT_FOR_STAKING,
+ MAX_SOL_AMOUNT_FOR_STAKING,
+ MIN_ETH_AMOUNT_FOR_STAKING,
+ MIN_ETH_BALANCE_FOR_STAKING,
+ MIN_ETH_FOR_WITHDRAWALS,
+ MIN_SOL_AMOUNT_FOR_STAKING,
+ MIN_SOL_BALANCE_FOR_STAKING,
+ MIN_SOL_FOR_WITHDRAWALS,
+} from '@suite-common/wallet-constants';
import {
+ getAccountEverstakeStakingPool,
getEthAccountTotalStakingBalance,
isSupportedEthStakingNetworkSymbol,
} from './ethereumStakingUtils';
import {
getSolAccountTotalStakingBalance,
+ getSolStakingAccountsInfo,
isSupportedSolStakingNetworkSymbol,
} from './solanaStakingUtils';
export const getAccountTotalStakingBalance = (account: Account) => {
if (!account) return null;
- if (account.networkType === 'ethereum') {
- return getEthAccountTotalStakingBalance(account);
- }
-
- if (account.networkType === 'solana') {
- return getSolAccountTotalStakingBalance(account);
+ switch (account.networkType) {
+ case 'ethereum':
+ return getEthAccountTotalStakingBalance(account);
+ case 'solana':
+ return getSolAccountTotalStakingBalance(account);
+ default:
+ return null;
}
};
export const isSupportedStakingNetworkSymbol = (symbol: NetworkSymbol) => {
return isSupportedEthStakingNetworkSymbol(symbol) || isSupportedSolStakingNetworkSymbol(symbol);
};
+
+export const getStakingLimitsByNetwork = (account: Account) => {
+ switch (account.networkType) {
+ case 'ethereum':
+ return {
+ MIN_AMOUNT_FOR_STAKING: MIN_ETH_AMOUNT_FOR_STAKING,
+ MAX_AMOUNT_FOR_STAKING: MAX_ETH_AMOUNT_FOR_STAKING,
+ MIN_FOR_WITHDRAWALS: MIN_ETH_FOR_WITHDRAWALS,
+ MIN_BALANCE_FOR_STAKING: MIN_ETH_BALANCE_FOR_STAKING,
+ };
+ case 'solana':
+ return {
+ MIN_AMOUNT_FOR_STAKING: MIN_SOL_AMOUNT_FOR_STAKING,
+ MAX_AMOUNT_FOR_STAKING: MAX_SOL_AMOUNT_FOR_STAKING,
+ MIN_FOR_WITHDRAWALS: MIN_SOL_FOR_WITHDRAWALS,
+ MIN_BALANCE_FOR_STAKING: MIN_SOL_BALANCE_FOR_STAKING,
+ };
+ default:
+ throw new Error(`Unsupported network type: ${account.networkType}`);
+ }
+};
+
+export const getStakingDataForNetwork = (
+ account?: Account,
+): Omit | undefined => {
+ if (!account) return;
+
+ switch (account.networkType) {
+ case 'ethereum':
+ return getAccountEverstakeStakingPool(account);
+ case 'solana': {
+ const {
+ canClaimSol,
+ solClaimableBalance,
+ solStakedBalance,
+ solPendingStakeBalance,
+ solPendingUnstakeBalance,
+ } = getSolStakingAccountsInfo(account) ?? {};
+
+ return {
+ autocompoundBalance: solStakedBalance,
+ claimableAmount: solClaimableBalance,
+ depositedBalance: solStakedBalance,
+ pendingBalance: '',
+ pendingDepositedBalance: '',
+ totalPendingStakeBalance: solPendingStakeBalance,
+ restakedReward: '',
+ withdrawTotalAmount: solPendingUnstakeBalance,
+ canClaim: canClaimSol,
+ };
+ }
+ default:
+ return;
+ }
+};
From f059e14e8fe560471858f71b7976c8e9fceaf851 Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Wed, 18 Dec 2024 16:26:39 +0200
Subject: [PATCH 5/8] feat(suite): implement solana staking dashboard
---
packages/suite-desktop-core/src/config.ts | 1 +
.../components/suite/CoinList/CoinList.tsx | 20 ++-
.../suite/StakingProcess/StakingInfo.tsx | 67 ++++++--
.../suite/StakingProcess/UnstakingInfo.tsx | 54 +++++-
.../PageNames/AccountName/AccountDetails.tsx | 3 +-
.../AdvancedCoinSettingsModal.tsx | 6 +-
.../ClaimModal/ClaimModal.tsx | 4 +-
.../StakeModal/StakeEthForm/Inputs.tsx | 2 +-
.../StakingInfoCards/EstimatedGains.tsx | 24 ++-
.../UnstakeModal/UnstakeEthForm/Options.tsx | 4 +-
.../UnstakeEthForm/UnstakeEthForm.tsx | 4 +-
.../AccountBanners/StakeEthBanner.tsx | 4 +-
.../AccountsMenu/AccountSection.tsx | 8 +-
.../src/hooks/wallet/useUnstakeEthForm.ts | 4 +-
packages/suite/src/support/messages.ts | 24 +++
.../suite/src/utils/suite/ethereumStaking.ts | 23 ---
.../suite/src/utils/suite/getCoinLabel.ts | 10 +-
packages/suite/src/utils/suite/staking.ts | 24 +++
.../dashboard/StakeEthCard/StakeEthCard.tsx | 6 +-
.../components/EthStakingDashboard.tsx | 25 +--
.../hooks/useProgressLabelsData.tsx | 78 ---------
.../SolStakingDashboard.tsx | 65 ++++++-
.../StakingDashboard/StakingDashboard.tsx | 15 +-
.../components/ApyCard.tsx | 0
.../components/ClaimCard.tsx | 4 +-
.../components/EmptyStakingCard.tsx | 7 +-
.../components/PayoutCard.tsx | 5 +-
.../ProgressLabels/ProgressLabel.tsx | 0
.../ProgressLabels/ProgressLabels.tsx | 0
.../components/ProgressLabels/types.ts | 0
.../components/StakingCard.tsx | 45 +++--
.../components/styled.ts | 0
.../hooks/useIsTxStatusShown.ts | 0
.../hooks/useProgressLabelsData.tsx | 159 ++++++++++++++++++
packages/urls/src/urls.ts | 2 +
.../src/solanaStakingConstants.ts | 10 +-
.../wallet-constants/src/stakingConstants.ts | 2 +
.../src/transactions/transactionsReducer.ts | 4 +-
.../wallet-utils/src/solanaStakingUtils.ts | 126 +++++++++++++-
39 files changed, 627 insertions(+), 212 deletions(-)
create mode 100644 packages/suite/src/utils/suite/staking.ts
delete mode 100644 packages/suite/src/views/wallet/staking/components/EthStakingDashboard/hooks/useProgressLabelsData.tsx
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/ApyCard.tsx (100%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/ClaimCard.tsx (96%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/PayoutCard.tsx (93%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/ProgressLabels/ProgressLabel.tsx (100%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/ProgressLabels/ProgressLabels.tsx (100%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/ProgressLabels/types.ts (100%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/StakingCard.tsx (86%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/components/styled.ts (100%)
rename packages/suite/src/views/wallet/staking/components/{EthStakingDashboard => StakingDashboard}/hooks/useIsTxStatusShown.ts (100%)
create mode 100644 packages/suite/src/views/wallet/staking/components/StakingDashboard/hooks/useProgressLabelsData.tsx
diff --git a/packages/suite-desktop-core/src/config.ts b/packages/suite-desktop-core/src/config.ts
index 7c6d4c7f186..e8b2f2937db 100644
--- a/packages/suite-desktop-core/src/config.ts
+++ b/packages/suite-desktop-core/src/config.ts
@@ -27,6 +27,7 @@ export const allowedDomains = [
'blockfrost.dev',
'eth-api-b2c-stage.everstake.one', // staking endpoint for Holesky testnet, works only with VPN
'eth-api-b2c.everstake.one', // staking endpoint for Ethereum mainnet
+ 'dashboard-api.everstake.one', // staking enpoint for Solana
];
export const cspRules = [
diff --git a/packages/suite/src/components/suite/CoinList/CoinList.tsx b/packages/suite/src/components/suite/CoinList/CoinList.tsx
index 4efa7a15d97..3813ab66946 100644
--- a/packages/suite/src/components/suite/CoinList/CoinList.tsx
+++ b/packages/suite/src/components/suite/CoinList/CoinList.tsx
@@ -9,6 +9,7 @@ import { Network, NetworkSymbol } from '@suite-common/wallet-config';
import { Translation } from 'src/components/suite';
import { useDevice, useDiscovery, useSelector } from 'src/hooks/suite';
import { getCoinLabel } from 'src/utils/suite/getCoinLabel';
+import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
import { Coin } from './Coin';
@@ -35,6 +36,8 @@ export const CoinList = ({
onToggle,
}: CoinListProps) => {
const { device, isLocked } = useDevice();
+ const isDebug = useSelector(selectIsDebugModeActive);
+
const blockchain = useSelector(state => state.wallet.blockchain);
const isDeviceLocked = !!device && isLocked();
const { isDiscoveryRunning } = useDiscovery();
@@ -50,7 +53,14 @@ export const CoinList = ({
return (
{networks.map(network => {
- const { symbol, name, support, features, testnet: isTestnet } = network;
+ const {
+ symbol,
+ name,
+ support,
+ features,
+ testnet: isTestnet,
+ networkType,
+ } = network;
const hasCustomBackend = !!blockchain[symbol].backends.selected;
const firmwareSupportRestriction =
@@ -76,7 +86,13 @@ export const CoinList = ({
getCoinUnavailabilityMessage(unavailableReason);
const tooltipString = discoveryTooltip || lockedTooltip || unavailabilityTooltip;
- const label = getCoinLabel(features, isTestnet, hasCustomBackend);
+ const label = getCoinLabel(
+ features,
+ isTestnet,
+ hasCustomBackend,
+ networkType,
+ isDebug,
+ );
return (
{
+ switch (networkType) {
+ case 'ethereum':
+ return {
+ payoutDays: daysToAddToPool,
+ rewardsPeriodHeading: ,
+ rewardsPeriodSubheading: (
+
+ ),
+ rewardsEarningHeading: ,
+ };
+ case 'solana':
+ return {
+ payoutDays: SOLANA_EPOCH_DAYS,
+ rewardsPeriodHeading: ,
+ rewardsPeriodSubheading: ,
+ rewardsEarningHeading: (
+
+ ),
+ };
+ default:
+ return null;
+ }
+};
+
interface StakingInfoProps {
isExpanded?: boolean;
}
@@ -33,13 +76,14 @@ export const StakingInfo = ({ isExpanded }: StakingInfoProps) => {
selectAccountStakeTransactions(state, account?.key ?? ''),
);
- const ethApy = useSelector((state: StakeRootState) =>
+ const apy = useSelector((state: StakeRootState) =>
selectPoolStatsApyData(state, account?.symbol),
);
if (!account) return null;
const daysToAddToPool = getDaysToAddToPool(stakeTxs, data);
+ const infoRowsData = getInfoRowsData(account.networkType, account.symbol, daysToAddToPool);
const infoRows = [
{
@@ -47,25 +91,24 @@ export const StakingInfo = ({ isExpanded }: StakingInfoProps) => {
content: { text: , isBadge: true },
},
{
- heading: ,
- subheading: (
-
- ),
+ heading: infoRowsData?.rewardsPeriodHeading,
+ subheading: infoRowsData?.rewardsPeriodSubheading,
content: {
text: (
<>
- ~
+ ~
+
>
),
},
},
{
- heading: ,
+ heading: infoRowsData?.rewardsEarningHeading,
subheading: ,
- content: { text: `~${ethApy}% p.a.` },
+ content: { text: `~${apy}% p.a.` },
},
];
diff --git a/packages/suite/src/components/suite/StakingProcess/UnstakingInfo.tsx b/packages/suite/src/components/suite/StakingProcess/UnstakingInfo.tsx
index ae28b634b7b..8eed1076d36 100644
--- a/packages/suite/src/components/suite/StakingProcess/UnstakingInfo.tsx
+++ b/packages/suite/src/components/suite/StakingProcess/UnstakingInfo.tsx
@@ -10,7 +10,8 @@ import {
StakeRootState,
AccountsRootState,
} from '@suite-common/wallet-core';
-import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
+import { getNetworkDisplaySymbol, NetworkSymbol, NetworkType } from '@suite-common/wallet-config';
+import { SOLANA_EPOCH_DAYS } from '@suite-common/wallet-constants';
import { Translation } from 'src/components/suite';
import { getDaysToUnstake } from 'src/utils/suite/ethereumStaking';
@@ -18,6 +19,40 @@ import { CoinjoinRootState } from 'src/reducers/wallet/coinjoinReducer';
import { InfoRow } from './InfoRow';
+type InfoRowsData = {
+ readyForClaimDays: number | undefined;
+ deactivatePeriodHeading: JSX.Element;
+ deactivatePeriodSubheading: JSX.Element;
+};
+
+const getInfoRowsData = (
+ networkType: NetworkType,
+ accountSymbol: NetworkSymbol,
+ daysToUnstake?: number,
+): InfoRowsData | null => {
+ switch (networkType) {
+ case 'ethereum':
+ return {
+ readyForClaimDays: daysToUnstake,
+ deactivatePeriodHeading: ,
+ deactivatePeriodSubheading: (
+
+ ),
+ };
+ case 'solana':
+ return {
+ readyForClaimDays: SOLANA_EPOCH_DAYS,
+ deactivatePeriodHeading: ,
+ deactivatePeriodSubheading: ,
+ };
+ default:
+ return null;
+ }
+};
+
interface UnstakingInfoProps {
isExpanded?: boolean;
}
@@ -36,6 +71,7 @@ export const UnstakingInfo = ({ isExpanded }: UnstakingInfoProps) => {
const daysToUnstake = getDaysToUnstake(unstakeTxs, data);
const displaySymbol = getNetworkDisplaySymbol(account.symbol);
+ const infoRowsData = getInfoRowsData(account.networkType, account.symbol, daysToUnstake);
const infoRows = [
{
@@ -46,15 +82,15 @@ export const UnstakingInfo = ({ isExpanded }: UnstakingInfoProps) => {
},
},
{
- heading: ,
- subheading: (
-
- ),
+ heading: infoRowsData?.deactivatePeriodHeading,
+ subheading: infoRowsData?.deactivatePeriodSubheading,
content: {
- text: ,
+ text: (
+
+ ),
},
},
{
diff --git a/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx b/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx
index f2b44601429..ca39c7d0e2e 100644
--- a/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx
+++ b/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx
@@ -6,6 +6,7 @@ import { Account } from '@suite-common/wallet-types';
import { spacingsPx, zIndices, typography } from '@trezor/theme';
import { H2 } from '@trezor/components';
import { CoinLogo } from '@trezor/product-components';
+import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
import {
MetadataLabeling,
@@ -117,7 +118,7 @@ export const AccountDetails = ({ selectedAccount, isBalanceShown }: AccountDetai
showAccountTypeBadge
accountLabel={selectedAccountLabels.accountLabel}
accountType={accountType}
- symbol={selectedAccount.symbol}
+ symbol={getNetworkDisplaySymbol(selectedAccount.symbol)}
index={index}
path={path}
networkType={selectedAccount.networkType}
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AdvancedCoinSettingsModal/AdvancedCoinSettingsModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AdvancedCoinSettingsModal/AdvancedCoinSettingsModal.tsx
index 338d3de364c..19a1ab47bfb 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AdvancedCoinSettingsModal/AdvancedCoinSettingsModal.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AdvancedCoinSettingsModal/AdvancedCoinSettingsModal.tsx
@@ -7,6 +7,7 @@ import { CoinLogo } from '@trezor/product-components';
import { Modal, Translation } from 'src/components/suite';
import { getCoinLabel } from 'src/utils/suite/getCoinLabel';
import { useSelector } from 'src/hooks/suite';
+import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
import { CustomBackends } from './CustomBackends/CustomBackends';
@@ -42,12 +43,13 @@ interface AdvancedCoinSettingsModalProps {
}
export const AdvancedCoinSettingsModal = ({ symbol, onCancel }: AdvancedCoinSettingsModalProps) => {
+ const isDebug = useSelector(selectIsDebugModeActive);
const blockchain = useSelector(state => state.wallet.blockchain);
const network = getNetwork(symbol);
- const { name, features, testnet: isTestnet } = network;
+ const { name, networkType, features, testnet: isTestnet } = network;
const hasCustomBackend = !!blockchain[symbol].backends.selected;
- const label = getCoinLabel(features, isTestnet, hasCustomBackend);
+ const label = getCoinLabel(features, isTestnet, hasCustomBackend, networkType, isDebug);
return (
{
// used instead of formState.isValid, which is sometimes returning false even if there are no errors
const formIsValid = Object.keys(errors).length === 0;
- const { claimableAmount = '0' } = getAccountEverstakeStakingPool(selectedAccount.account) ?? {};
+ const { claimableAmount = '0' } = getStakingDataForNetwork(selectedAccount.account) ?? {};
const isDisabled =
!(formIsValid && hasValues) || isSubmitting || isLocked() || !device?.available;
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx
index 1a779b02531..b6ff0011572 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakeEthForm/Inputs.tsx
@@ -15,7 +15,7 @@ import {
validateReserveOrBalance,
} from 'src/utils/suite/validation';
import { FIAT_INPUT, CRYPTO_INPUT } from 'src/types/wallet/stakeForms';
-import { validateStakingMax } from 'src/utils/suite/ethereumStaking';
+import { validateStakingMax } from 'src/utils/suite/staking';
import { FormFractionButtons } from 'src/components/suite/FormFractionButtons';
export const Inputs = () => {
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakingInfoCards/EstimatedGains.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakingInfoCards/EstimatedGains.tsx
index e172a8371ca..fbaa0b2c14d 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakingInfoCards/EstimatedGains.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeModal/StakingInfoCards/EstimatedGains.tsx
@@ -4,13 +4,13 @@ import { useSelector } from 'react-redux';
import { selectPoolStatsApyData, StakeRootState } from '@suite-common/wallet-core';
import { Column, Grid, Image, Paragraph, Text } from '@trezor/components';
import { negativeSpacings, spacings } from '@trezor/theme';
-import { HELP_CENTER_ETH_STAKING } from '@trezor/urls';
+import { HELP_CENTER_ETH_STAKING, HELP_CENTER_SOL_STAKING } from '@trezor/urls';
import { Translation } from 'src/components/suite/Translation';
import { useStakeEthFormContext } from 'src/hooks/wallet/useStakeEthForm';
import { CRYPTO_INPUT } from 'src/types/wallet/stakeForms';
import { FiatValue, FormattedCryptoAmount, TrezorLink } from 'src/components/suite';
-import { calculateGains } from 'src/utils/suite/ethereumStaking';
+import { calculateGains } from 'src/utils/suite/staking';
export const EstimatedGains = () => {
const { account, getValues, formState } = useStakeEthFormContext();
@@ -22,22 +22,22 @@ export const EstimatedGains = () => {
const cryptoInput = hasInvalidFormState || !value ? '0' : value;
- const ethApy = useSelector((state: StakeRootState) =>
+ const apy = useSelector((state: StakeRootState) =>
selectPoolStatsApyData(state, account?.symbol),
);
const gains = [
{
label: ,
- value: calculateGains(cryptoInput, ethApy, 52),
+ value: calculateGains(cryptoInput, apy, 52),
},
{
label: ,
- value: calculateGains(cryptoInput, ethApy, 12),
+ value: calculateGains(cryptoInput, apy, 12),
},
{
label: ,
- value: calculateGains(cryptoInput, ethApy, 1),
+ value: calculateGains(cryptoInput, apy, 1),
},
];
@@ -45,7 +45,7 @@ export const EstimatedGains = () => {
- {ethApy}%
+ {apy}%
{
id="TR_STAKING_YOUR_EARNINGS"
values={{
a: chunks => (
- {chunks}
+
+ {chunks}
+
),
}}
/>
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/Options.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/Options.tsx
index 821c91748a2..f6ebca7d644 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/Options.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/Options.tsx
@@ -6,7 +6,7 @@ import { Radio, Column, Row, Text } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { NetworkSymbol } from '@suite-common/wallet-config';
import { BigNumber } from '@trezor/utils/src/bigNumber';
-import { getAccountEverstakeStakingPool } from '@suite-common/wallet-utils';
+import { getStakingDataForNetwork } from '@suite-common/wallet-utils';
import { FiatValue, FormattedCryptoAmount, Translation } from 'src/components/suite';
import { useSelector } from 'src/hooks/suite';
@@ -70,7 +70,7 @@ export const Options = ({ symbol }: OptionsProps) => {
autocompoundBalance = '0',
depositedBalance = '0',
restakedReward = '0',
- } = getAccountEverstakeStakingPool(selectedAccount) ?? {};
+ } = getStakingDataForNetwork(selectedAccount) ?? {};
return (
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/UnstakeEthForm.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/UnstakeEthForm.tsx
index d97d303a610..6a625a340b1 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/UnstakeEthForm.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UnstakeModal/UnstakeEthForm/UnstakeEthForm.tsx
@@ -1,8 +1,8 @@
import { InfoItem, Tooltip, Banner, Column, Card } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { selectValidatorsQueueData } from '@suite-common/wallet-core';
-import { getAccountEverstakeStakingPool } from '@suite-common/wallet-utils';
import { BigNumber } from '@trezor/utils/src/bigNumber';
+import { getStakingDataForNetwork } from '@suite-common/wallet-utils';
import { Translation } from 'src/components/suite';
import { useSelector } from 'src/hooks/suite';
@@ -40,7 +40,7 @@ export const UnstakeEthForm = () => {
);
const unstakingPeriod = getUnstakingPeriodInDays(validatorWithdrawTime);
const { canClaim = false, claimableAmount = '0' } =
- getAccountEverstakeStakingPool(selectedAccount) ?? {};
+ getStakingDataForNetwork(selectedAccount) ?? {};
const inputError = errors[CRYPTO_INPUT] || errors[FIAT_INPUT];
const showError = inputError && inputError.type === 'compose';
diff --git a/packages/suite/src/components/wallet/WalletLayout/AccountBanners/StakeEthBanner.tsx b/packages/suite/src/components/wallet/WalletLayout/AccountBanners/StakeEthBanner.tsx
index 0f51f767252..b5727033621 100644
--- a/packages/suite/src/components/wallet/WalletLayout/AccountBanners/StakeEthBanner.tsx
+++ b/packages/suite/src/components/wallet/WalletLayout/AccountBanners/StakeEthBanner.tsx
@@ -23,7 +23,7 @@ export const StakeEthBanner = ({ account }: StakeEthBannerProps) => {
const dispatch = useDispatch();
const { stakeEthBannerClosed } = useSelector(selectSuiteFlags);
const { route } = useSelector(state => state.router);
- const ethApy = useSelector(state => selectPoolStatsApyData(state, account.symbol));
+ const apy = useSelector(state => selectPoolStatsApyData(state, account.symbol));
const theme = useTheme();
const closeBanner = () => {
@@ -65,7 +65,7 @@ export const StakeEthBanner = ({ account }: StakeEthBannerProps) => {
selectCoinDefinitions(state, symbol));
- const hasStaked = useSelector(state => selectAccountHasStaked(state, account.key));
+ const hasEthStaked = useSelector(state => selectEthAccountHasStaked(state, account.key));
const solStakingAccounts = useSelector(state => selectSolStakingAccounts(state, account.key));
// TODO: remove isDebugModeActive when staking will be ready for launch
- const hasStakingAccount = !!solStakingAccounts?.length && isDebugModeActive; // for solana
+ const hasSolStakingAccount = !!solStakingAccounts?.length && isDebugModeActive; // for solana
const isStakeShown =
- isSupportedStakingNetworkSymbol(symbol) && (hasStaked || hasStakingAccount);
+ isSupportedStakingNetworkSymbol(symbol) && (hasEthStaked || hasSolStakingAccount);
const showGroup = ['ethereum', 'solana', 'cardano'].includes(networkType);
diff --git a/packages/suite/src/hooks/wallet/useUnstakeEthForm.ts b/packages/suite/src/hooks/wallet/useUnstakeEthForm.ts
index 94ea4298d29..f6c7270a226 100644
--- a/packages/suite/src/hooks/wallet/useUnstakeEthForm.ts
+++ b/packages/suite/src/hooks/wallet/useUnstakeEthForm.ts
@@ -5,9 +5,9 @@ import useDebounce from 'react-use/lib/useDebounce';
import {
fromFiatCurrency,
- getAccountAutocompoundBalance,
getFeeLevels,
getFiatRateKey,
+ getStakingDataForNetwork,
toFiatCurrency,
} from '@suite-common/wallet-utils';
import { PrecomposedTransactionFinal } from '@suite-common/wallet-types';
@@ -70,7 +70,7 @@ export const useUnstakeEthForm = ({
selectFiatRatesByFiatRateKey(state, getFiatRateKey(symbol, localCurrency), 'current'),
);
- const autocompoundBalance = getAccountAutocompoundBalance(account);
+ const { autocompoundBalance = '0' } = getStakingDataForNetwork(account) ?? {};
const amountLimits: AmountLimitProps = {
currency: symbol,
maxCrypto: autocompoundBalance,
diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts
index 7ca90b1187b..fc4ff6bfc8e 100644
--- a/packages/suite/src/support/messages.ts
+++ b/packages/suite/src/support/messages.ts
@@ -8746,6 +8746,26 @@ export default defineMessages({
id: 'TR_STAKE_ENTER_THE_STAKING_POOL',
defaultMessage: 'Enter the staking pool',
},
+ TR_STAKE_WAIT_FOR_ACTIVATION: {
+ id: 'TR_STAKE_WAIT_FOR_ACTIVATION',
+ defaultMessage: 'Wait for the next epoch until your stake activated',
+ },
+ TR_STAKE_WARM_UP_PERIOD: {
+ id: 'TR_STAKE_WARM_UP_PERIOD',
+ defaultMessage: 'Warm Up period',
+ },
+ TR_STAKE_EARN_REWARDS_EVERY: {
+ id: 'TR_STAKE_EARN_REWARDS_EVERY',
+ defaultMessage: 'Earn rewards every ~{days} days',
+ },
+ TR_STAKE_COOL_DOWN_PERIOD: {
+ id: 'TR_STAKE_COOL_DOWN_PERIOD',
+ defaultMessage: 'Cool Down Period',
+ },
+ TR_STAKE_WAIT_FOR_DEACTIVATION: {
+ id: 'TR_STAKE_WAIT_FOR_DEACTIVATION',
+ defaultMessage: 'Wait for the next epoch until your stake deactivated',
+ },
TR_STAKE_EARN_REWARDS_WEEKLY: {
id: 'TR_STAKE_EARN_REWARDS_WEEKLY',
defaultMessage: 'Earn rewards weekly',
@@ -8900,6 +8920,10 @@ export default defineMessages({
id: 'TR_STAKE_REWARDS',
defaultMessage: 'Rewards',
},
+ TR_STAKE_EXPECTED_REWARDS: {
+ id: 'TR_STAKE_EXPECTED_REWARDS',
+ defaultMessage: 'Expected rewards per 1 epoch (~{days} days)',
+ },
TR_TX_CONFIRMED: {
id: 'TR_TX_CONFIRMED',
defaultMessage: 'Transaction confirmed',
diff --git a/packages/suite/src/utils/suite/ethereumStaking.ts b/packages/suite/src/utils/suite/ethereumStaking.ts
index 3b91937d955..7799398a83d 100644
--- a/packages/suite/src/utils/suite/ethereumStaking.ts
+++ b/packages/suite/src/utils/suite/ethereumStaking.ts
@@ -28,8 +28,6 @@ import { ValidatorsQueue } from '@suite-common/wallet-core';
import { BlockchainEstimatedFee } from '@trezor/connect/src/types/api/blockchainEstimateFee';
import { PartialRecord } from '@trezor/type-utils';
-import { TranslationFunction } from 'src/hooks/suite/useTranslation';
-
const secondsToDays = (seconds: number) => Math.round(seconds / 60 / 60 / 24);
type EthNetwork = 'holesky' | 'mainnet';
@@ -630,27 +628,6 @@ export const getChangedInternalTx = (
return internalTransfer ?? null;
};
-export const calculateGains = (input: string, apy: number, divisor: number) => {
- const amount = new BigNumber(input).multipliedBy(apy).dividedBy(100).dividedBy(divisor);
-
- return amount.toFixed(5, 1);
-};
-
-interface ValidateMaxOptions {
- maxAmount: BigNumber;
- except?: boolean;
-}
-
-export const validateStakingMax =
- (translationString: TranslationFunction, { except, maxAmount }: ValidateMaxOptions) =>
- (value: string) => {
- if (!except && value && BigNumber(value).gt(maxAmount)) {
- return translationString('AMOUNT_EXCEEDS_MAX', {
- maxAmount: maxAmount.toString(),
- });
- }
- };
-
export const simulateUnstake = async ({
amount,
from,
diff --git a/packages/suite/src/utils/suite/getCoinLabel.ts b/packages/suite/src/utils/suite/getCoinLabel.ts
index 66623ee5865..47a65fd6853 100644
--- a/packages/suite/src/utils/suite/getCoinLabel.ts
+++ b/packages/suite/src/utils/suite/getCoinLabel.ts
@@ -1,13 +1,19 @@
import type { TranslationKey } from '@suite-common/intl-types';
-import type { NetworkFeature } from '@suite-common/wallet-config';
+import type { NetworkFeature, NetworkType } from '@suite-common/wallet-config';
export const getCoinLabel = (
features: NetworkFeature[],
isTestnet: boolean,
isCustomBackend: boolean,
+ networkType?: NetworkType,
+ isDebug?: boolean,
): TranslationKey | undefined => {
const hasTokens = features.includes('tokens');
- const hasStaking = features.includes('staking');
+ // TODO: Remove this condition when Solana staking is available
+ const hasStaking =
+ networkType === 'solana'
+ ? isDebug && features.includes('staking') // Solana staking only in debug mode
+ : features.includes('staking');
if (isCustomBackend) {
return 'TR_CUSTOM_BACKEND';
diff --git a/packages/suite/src/utils/suite/staking.ts b/packages/suite/src/utils/suite/staking.ts
new file mode 100644
index 00000000000..dd32d8660a4
--- /dev/null
+++ b/packages/suite/src/utils/suite/staking.ts
@@ -0,0 +1,24 @@
+import { BigNumber } from '@trezor/utils';
+
+import { TranslationFunction } from 'src/hooks/suite/useTranslation';
+
+interface ValidateMaxOptions {
+ maxAmount: BigNumber;
+ except?: boolean;
+}
+
+export const validateStakingMax =
+ (translationString: TranslationFunction, { except, maxAmount }: ValidateMaxOptions) =>
+ (value: string) => {
+ if (!except && value && BigNumber(value).gt(maxAmount)) {
+ return translationString('AMOUNT_EXCEEDS_MAX', {
+ maxAmount: maxAmount.toString(),
+ });
+ }
+ };
+
+export const calculateGains = (input: string, apy: number, divisor: number) => {
+ const amount = new BigNumber(input).multipliedBy(apy).dividedBy(100).dividedBy(divisor);
+
+ return amount.toFixed(5, 1);
+};
diff --git a/packages/suite/src/views/dashboard/StakeEthCard/StakeEthCard.tsx b/packages/suite/src/views/dashboard/StakeEthCard/StakeEthCard.tsx
index ced38e360bb..fa8c861b273 100644
--- a/packages/suite/src/views/dashboard/StakeEthCard/StakeEthCard.tsx
+++ b/packages/suite/src/views/dashboard/StakeEthCard/StakeEthCard.tsx
@@ -46,7 +46,7 @@ export const StakeEthCard = () => {
showDashboardStakingPromoBanner &&
!isBitcoinOnlyDevice;
- const ethApy = useSelector(state => selectPoolStatsApyData(state, bannerSymbol));
+ const apy = useSelector(state => selectPoolStatsApyData(state, bannerSymbol));
const { discovery } = useDiscovery();
const { accounts } = useAccounts(discovery);
@@ -72,7 +72,7 @@ export const StakeEthCard = () => {
(
{
description: ,
},
],
- [ethApy],
+ [apy],
);
if (!isShown) return null;
diff --git a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/EthStakingDashboard.tsx b/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/EthStakingDashboard.tsx
index ace64bc2718..6076c420c33 100644
--- a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/EthStakingDashboard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/EthStakingDashboard.tsx
@@ -10,24 +10,25 @@ import {
selectPoolStatsNextRewardPayout,
selectValidatorsQueue,
} from '@suite-common/wallet-core';
-import { getAccountEverstakeStakingPool } from '@suite-common/wallet-utils';
-import { SelectedAccountStatus } from '@suite-common/wallet-types';
+import { getStakingDataForNetwork } from '@suite-common/wallet-utils';
+import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
+import { SelectedAccountLoaded } from '@suite-common/wallet-types';
import { useDispatch, useSelector } from 'src/hooks/suite';
import { Translation } from 'src/components/suite';
import { DashboardSection } from 'src/components/dashboard';
import { getDaysToAddToPool, getDaysToUnstake } from 'src/utils/suite/ethereumStaking';
-import { StakingCard } from './StakingCard';
-import { ApyCard } from './ApyCard';
-import { PayoutCard } from './PayoutCard';
-import { ClaimCard } from './ClaimCard';
+import { StakingCard } from '../../StakingDashboard/components/StakingCard';
+import { ClaimCard } from '../../StakingDashboard/components/ClaimCard';
+import { StakingDashboard } from '../../StakingDashboard/StakingDashboard';
+import { ApyCard } from '../../StakingDashboard/components/ApyCard';
+import { PayoutCard } from '../../StakingDashboard/components/PayoutCard';
import { Transactions } from './Transactions';
import { InstantStakeBanner } from './InstantStakeBanner';
-import { StakingDashboard } from '../../StakingDashboard/StakingDashboard';
interface EthStakingDashboardProps {
- selectedAccount: SelectedAccountStatus;
+ selectedAccount: SelectedAccountLoaded;
}
export const EthStakingDashboard = ({ selectedAccount }: EthStakingDashboardProps) => {
@@ -38,7 +39,7 @@ export const EthStakingDashboard = ({ selectedAccount }: EthStakingDashboardProp
const { data, isLoading } =
useSelector(state => selectValidatorsQueue(state, account?.symbol)) || {};
- const ethApy = useSelector(state => selectPoolStatsApyData(state, account?.symbol));
+ const apy = useSelector(state => selectPoolStatsApyData(state, account?.symbol));
const nextRewardPayout = useSelector(state =>
selectPoolStatsNextRewardPayout(state, account?.symbol),
);
@@ -64,7 +65,7 @@ export const EthStakingDashboard = ({ selectedAccount }: EthStakingDashboardProp
const daysToAddToPool = getDaysToAddToPool(stakeTxs, data);
const daysToUnstake = getDaysToUnstake(unstakeTxs, data);
- const { canClaim = false } = getAccountEverstakeStakingPool(account) ?? {};
+ const { canClaim = false } = getStakingDataForNetwork(account) ?? {};
return (
}
>
@@ -88,7 +89,7 @@ export const EthStakingDashboard = ({ selectedAccount }: EthStakingDashboardProp
-
+
{
- const progressLabelsData: ProgressLabelData[] = useMemo(
- () => [
- {
- id: 0,
- progressState: (() => {
- if (isStakeConfirming) return 'active';
-
- return 'done';
- })(),
- children: isStakeConfirming ? (
-
- ) : (
-
- ),
- },
- {
- id: 1,
- progressState: (() => {
- if (!isStakeConfirming && isStakePending) return 'active';
- if (!isStakeConfirming && !isStakePending) return 'done';
-
- return 'stale';
- })(),
- children: (
-
-
- {isDaysToAddToPoolShown && (
-
- ~
-
-
- )}
-
- ),
- },
- {
- id: 2,
- progressState: (() => {
- if (!isStakeConfirming && !isStakePending) {
- return 'active';
- }
-
- return 'stale';
- })(),
- children: ,
- },
- ],
- [daysToAddToPool, isDaysToAddToPoolShown, isStakeConfirming, isStakePending],
- );
-
- return { progressLabelsData };
-};
diff --git a/packages/suite/src/views/wallet/staking/components/SolStakingDashboard/SolStakingDashboard.tsx b/packages/suite/src/views/wallet/staking/components/SolStakingDashboard/SolStakingDashboard.tsx
index 98db57dae5b..f2c41e5a615 100644
--- a/packages/suite/src/views/wallet/staking/components/SolStakingDashboard/SolStakingDashboard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/SolStakingDashboard/SolStakingDashboard.tsx
@@ -1,17 +1,74 @@
-import { SelectedAccountStatus } from '@suite-common/wallet-types';
+import { useSelector } from 'react-redux';
+
+import { Column, Flex, Grid, useMediaQuery, variables } from '@trezor/components';
+import { spacings } from '@trezor/theme';
+import { SelectedAccountLoaded } from '@suite-common/wallet-types';
+import { selectPoolStatsApyData, StakeRootState } from '@suite-common/wallet-core';
+import { SOLANA_EPOCH_DAYS } from '@suite-common/wallet-constants';
+import { getStakingDataForNetwork } from '@suite-common/wallet-utils';
+import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
+
+import { DashboardSection } from 'src/components/dashboard';
+import { Translation } from 'src/components/suite';
import { StakingDashboard } from '../StakingDashboard/StakingDashboard';
+import { ApyCard } from '../StakingDashboard/components/ApyCard';
+import { PayoutCard } from '../StakingDashboard/components/PayoutCard';
+import { ClaimCard } from '../StakingDashboard/components/ClaimCard';
+import { StakingCard } from '../StakingDashboard/components/StakingCard';
interface SolStakingDashboardProps {
- selectedAccount: SelectedAccountStatus;
+ selectedAccount: SelectedAccountLoaded;
}
export const SolStakingDashboard = ({ selectedAccount }: SolStakingDashboardProps) => {
+ const { account } = selectedAccount;
+
+ const isBelowLaptop = useMediaQuery(`(max-width: ${variables.SCREEN_SIZE.LG})`);
+
+ const { canClaim = false } = getStakingDataForNetwork(account) ?? {};
+
+ const apy = useSelector((state: StakeRootState) =>
+ selectPoolStatsApyData(state, account?.symbol),
+ );
+
return (
>}
+ dashboard={
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {/* TODO: implement Transactions component */}
+ {/* */}
+
+ }
/>
);
};
diff --git a/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
index 85871f348a4..6d71c3634d1 100644
--- a/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
@@ -1,4 +1,4 @@
-import { selectAccountHasStaked, selectSolStakingAccounts } from '@suite-common/wallet-core';
+import { selectEthAccountHasStaked, selectSolStakingAccounts } from '@suite-common/wallet-core';
import { SelectedAccountStatus } from '@suite-common/wallet-types';
import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
@@ -14,16 +14,17 @@ interface StakingDashboardProps {
}
export const StakingDashboard = ({ selectedAccount, dashboard }: StakingDashboardProps) => {
- const hasStaked = useSelector(state =>
- selectAccountHasStaked(state, selectedAccount?.account?.key ?? ''),
+ const hasEthStaked = useSelector(state =>
+ selectEthAccountHasStaked(state, selectedAccount.account?.key ?? ''),
+ );
+ const solStakingAccounts = useSelector(state =>
+ selectSolStakingAccounts(state, selectedAccount.account?.key),
);
- const { account } = selectedAccount;
-
- const solStakingAccounts = useSelector(state => selectSolStakingAccounts(state, account?.key));
+ if (selectedAccount.status !== 'loaded') return null;
const hasSolStakingAccount = !!solStakingAccounts?.length;
- const shouldShowDashboard = hasStaked || hasSolStakingAccount;
+ const shouldShowDashboard = hasEthStaked || hasSolStakingAccount;
return (
{
const isClaimPending = useMemo(() => claimTxs.some(tx => isPending(tx)), [claimTxs]);
const { canClaim = false, claimableAmount = '0' } =
- getAccountEverstakeStakingPool(selectedAccount) ?? {};
+ getStakingDataForNetwork(selectedAccount) ?? {};
// Show success message when claim tx confirmation is complete.
const prevIsClaimPending = useRef(false);
diff --git a/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx
index a616668c185..534ed99825b 100644
--- a/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx
@@ -30,8 +30,7 @@ export const EmptyStakingCard = () => {
const { isStakingDisabled, stakingMessageContent } = useMessageSystemStaking();
- const ethApy = useSelector(state => selectPoolStatsApyData(state, account?.symbol));
- // TODO: calc solApy
+ const apy = useSelector(state => selectPoolStatsApyData(state, account?.symbol));
const dispatch = useDispatch();
const openStakingEthInANutshellModal = () => {
@@ -52,8 +51,8 @@ export const EmptyStakingCard = () => {
(
{
description: ,
},
],
- [ethApy, displaySymbol],
+ [apy, displaySymbol],
);
return (
diff --git a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/PayoutCard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/PayoutCard.tsx
similarity index 93%
rename from packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/PayoutCard.tsx
rename to packages/suite/src/views/wallet/staking/components/StakingDashboard/components/PayoutCard.tsx
index 35e5a935727..a41a203ea87 100644
--- a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/PayoutCard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/PayoutCard.tsx
@@ -4,7 +4,7 @@ import { BigNumber } from '@trezor/utils/src/bigNumber';
import { Card, Column, Icon, Paragraph } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { BACKUP_REWARD_PAYOUT_DAYS } from '@suite-common/wallet-constants';
-import { getAccountAutocompoundBalance } from '@suite-common/wallet-utils';
+import { getStakingDataForNetwork } from '@suite-common/wallet-utils';
import { Translation } from 'src/components/suite';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
@@ -23,7 +23,8 @@ export const PayoutCard = ({
}: PayoutCardProps) => {
const selectedAccount = useSelector(selectSelectedAccount);
- const autocompoundBalance = getAccountAutocompoundBalance(selectedAccount);
+ const { autocompoundBalance = '0' } = getStakingDataForNetwork(selectedAccount) ?? {};
+
const payout = useMemo(() => {
if (!nextRewardPayout || !daysToAddToPool) return undefined;
diff --git a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/ProgressLabels/ProgressLabel.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/ProgressLabels/ProgressLabel.tsx
similarity index 100%
rename from packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/ProgressLabels/ProgressLabel.tsx
rename to packages/suite/src/views/wallet/staking/components/StakingDashboard/components/ProgressLabels/ProgressLabel.tsx
diff --git a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/ProgressLabels/ProgressLabels.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/ProgressLabels/ProgressLabels.tsx
similarity index 100%
rename from packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/ProgressLabels/ProgressLabels.tsx
rename to packages/suite/src/views/wallet/staking/components/StakingDashboard/components/ProgressLabels/ProgressLabels.tsx
diff --git a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/ProgressLabels/types.ts b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/ProgressLabels/types.ts
similarity index 100%
rename from packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/ProgressLabels/types.ts
rename to packages/suite/src/views/wallet/staking/components/StakingDashboard/components/ProgressLabels/types.ts
diff --git a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/StakingCard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/StakingCard.tsx
similarity index 86%
rename from packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/StakingCard.tsx
rename to packages/suite/src/views/wallet/staking/components/StakingDashboard/components/StakingCard.tsx
index 80ff767f307..a5ad8af633f 100644
--- a/packages/suite/src/views/wallet/staking/components/EthStakingDashboard/components/StakingCard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/StakingCard.tsx
@@ -16,7 +16,13 @@ import {
} from '@trezor/components';
import { spacings } from '@trezor/theme';
import { selectAccountStakeTransactions } from '@suite-common/wallet-core';
-import { getAccountEverstakeStakingPool, isPending } from '@suite-common/wallet-utils';
+import {
+ calculateSolanaStakingReward,
+ getStakingAccountCurrentStatus,
+ getStakingDataForNetwork,
+ isPending,
+} from '@suite-common/wallet-utils';
+import { SOLANA_EPOCH_DAYS } from '@suite-common/wallet-constants';
import { getNetworkDisplaySymbol, type NetworkSymbol } from '@suite-common/wallet-config';
import { FiatValue, Translation, FormattedCryptoAmount } from 'src/components/suite';
@@ -65,12 +71,14 @@ type StakingCardProps = {
isValidatorsQueueLoading?: boolean;
daysToAddToPool?: number;
daysToUnstake?: number;
+ apy?: number;
};
export const StakingCard = ({
isValidatorsQueueLoading,
daysToAddToPool,
daysToUnstake,
+ apy,
}: StakingCardProps) => {
const selectedAccount = useSelector(selectSelectedAccount);
const isBelowLaptop = useMediaQuery(`(max-width: ${variables.SCREEN_SIZE.LG})`);
@@ -89,7 +97,7 @@ export const StakingCard = ({
totalPendingStakeBalance = '0',
withdrawTotalAmount = '0',
claimableAmount = '0',
- } = getAccountEverstakeStakingPool(selectedAccount) ?? {};
+ } = getStakingDataForNetwork(selectedAccount) ?? {};
const canUnstake = new BigNumber(autocompoundBalance).gt(0);
const isStakePending = new BigNumber(totalPendingStakeBalance).gt(0);
@@ -110,11 +118,15 @@ export const StakingCard = ({
);
const isStakeConfirming = stakeTxs.some(tx => isPending(tx));
- const { progressLabelsData } = useProgressLabelsData({
+ const solStakingAccountStatus = getStakingAccountCurrentStatus(selectedAccount);
+
+ const progressLabelsData = useProgressLabelsData({
daysToAddToPool,
isDaysToAddToPoolShown,
isStakeConfirming,
isStakePending,
+ selectedAccount,
+ solStakingAccountStatus,
});
const dispatch = useDispatch();
@@ -133,6 +145,10 @@ export const StakingCard = ({
return null;
}
+ const solReward = calculateSolanaStakingReward(depositedBalance, apy?.toString());
+
+ const stakingReward = selectedAccount.networkType === 'solana' ? solReward : restakedReward;
+
return (
@@ -151,11 +167,10 @@ export const StakingCard = ({
data-testid="@account/staking/pending"
/>
)}
-
}
iconName="lock"
- symbol={selectedAccount.symbol}
+ symbol={selectedAccount?.symbol}
cryptoAmount={depositedBalance}
fiatAmount={depositedBalance}
data-testid="@account/staking/staked"
@@ -164,7 +179,16 @@ export const StakingCard = ({
-
-
+
-
{isPendingUnstakeShown && (
-
{
+ const ethereumProgressLabelsData: ProgressLabelData[] = useMemo(
+ () => [
+ {
+ id: 0,
+ progressState: (() => {
+ if (isStakeConfirming) return 'active';
+
+ return 'done';
+ })(),
+ children: isStakeConfirming ? (
+
+ ) : (
+
+ ),
+ },
+ {
+ id: 1,
+ progressState: (() => {
+ if (!isStakeConfirming && isStakePending) return 'active';
+ if (!isStakeConfirming && !isStakePending) return 'done';
+
+ return 'stale';
+ })(),
+ children: (
+
+
+ {isDaysToAddToPoolShown && (
+
+ ~
+
+
+ )}
+
+ ),
+ },
+ {
+ id: 2,
+ progressState: (() => {
+ if (!isStakeConfirming && !isStakePending) {
+ return 'active';
+ }
+
+ return 'stale';
+ })(),
+ children: ,
+ },
+ ],
+ [daysToAddToPool, isDaysToAddToPoolShown, isStakeConfirming, isStakePending],
+ );
+
+ const solanaProgressLabelsData: ProgressLabelData[] = useMemo(
+ () => [
+ {
+ id: 0,
+ progressState: (() => {
+ if (solStakingAccountStatus === 'inactive') return 'active';
+
+ return 'done';
+ })(),
+ children: isStakeConfirming ? (
+
+ ) : (
+
+ ),
+ },
+ {
+ id: 1,
+ progressState: (() => {
+ if (solStakingAccountStatus === 'activating') return 'active';
+ if (solStakingAccountStatus !== 'activating') return 'done';
+
+ return 'stale';
+ })(),
+ children: (
+
+
+
+
+ ~
+
+
+
+ ),
+ },
+ {
+ id: 2,
+ progressState: (() => {
+ if (solStakingAccountStatus === 'active') {
+ return 'active';
+ }
+
+ return 'stale';
+ })(),
+ children: (
+
+
+
+
+ ~
+
+
+
+ ),
+ },
+ ],
+ [solStakingAccountStatus, isStakeConfirming],
+ );
+
+ switch (selectedAccount?.networkType) {
+ case 'ethereum':
+ return ethereumProgressLabelsData;
+ case 'solana':
+ return solanaProgressLabelsData;
+ default:
+ return [];
+ }
+};
diff --git a/packages/urls/src/urls.ts b/packages/urls/src/urls.ts
index 308cbddf7d6..2bf689cb709 100644
--- a/packages/urls/src/urls.ts
+++ b/packages/urls/src/urls.ts
@@ -96,6 +96,8 @@ export const HELP_CENTER_DEVICE_AUTHENTICATION: Url =
'https://trezor.io/learn/a/trezor-safe-device-authentication-check';
export const HELP_CENTER_ETH_STAKING: Url =
'https://trezor.io/learn/a/stake-ethereum-eth-in-trezor-suite';
+export const HELP_CENTER_SOL_STAKING: Url =
+ 'https://trezor.io/learn/a/stake-solana-sol-in-trezor-suite';
export const HELP_CENTER_SEED_CARD_URL: Url = 'https://trezor.io/learn/a/recovery-seed-card';
export const HELP_CENTER_MULTI_SHARE_BACKUP_URL: Url =
'https://trezor.io/learn/a/multi-share-backup-on-trezor';
diff --git a/suite-common/wallet-constants/src/solanaStakingConstants.ts b/suite-common/wallet-constants/src/solanaStakingConstants.ts
index 02b4d4f12ac..a5dd6362051 100644
--- a/suite-common/wallet-constants/src/solanaStakingConstants.ts
+++ b/suite-common/wallet-constants/src/solanaStakingConstants.ts
@@ -1,9 +1,9 @@
import { BigNumber } from '@trezor/utils';
-// TODO: change to solana constants
-export const MIN_SOL_AMOUNT_FOR_STAKING = new BigNumber(0.1);
-export const MAX_SOL_AMOUNT_FOR_STAKING = new BigNumber(1_000_000);
-export const MIN_SOL_FOR_WITHDRAWALS = new BigNumber(0.03);
+export const BACKUP_SOL_APY = 6.87;
+export const MIN_SOL_AMOUNT_FOR_STAKING = new BigNumber(0.01);
+export const MAX_SOL_AMOUNT_FOR_STAKING = new BigNumber(10_000_000);
+export const MIN_SOL_FOR_WITHDRAWALS = new BigNumber(0.000005);
export const MIN_SOL_BALANCE_FOR_STAKING = MIN_SOL_AMOUNT_FOR_STAKING.plus(MIN_SOL_FOR_WITHDRAWALS);
-export const LAMPORTS_PER_SOL = 1_000_000_000;
+export const SOLANA_EPOCH_DAYS = 3;
diff --git a/suite-common/wallet-constants/src/stakingConstants.ts b/suite-common/wallet-constants/src/stakingConstants.ts
index f9edbf858a2..98cfac37cdc 100644
--- a/suite-common/wallet-constants/src/stakingConstants.ts
+++ b/suite-common/wallet-constants/src/stakingConstants.ts
@@ -3,3 +3,5 @@
// It is a constant which allows the SDK to define which app calls its functions.
// Each app which integrates the SDK has its own source, e.g. source for Trezor Suite is '1'.
export const WALLET_SDK_SOURCE = '1';
+
+export const BACKUP_APY = 4.13;
diff --git a/suite-common/wallet-core/src/transactions/transactionsReducer.ts b/suite-common/wallet-core/src/transactions/transactionsReducer.ts
index 83a4399aed9..e706ba0c737 100644
--- a/suite-common/wallet-core/src/transactions/transactionsReducer.ts
+++ b/suite-common/wallet-core/src/transactions/transactionsReducer.ts
@@ -374,7 +374,7 @@ export const selectAccountClaimTransactions = createMemoizedSelector(
),
);
-export const selectAccountHasStaked = createMemoizedSelector(
+export const selectEthAccountHasStaked = createMemoizedSelector(
[selectAccountStakeTransactions, selectAccountByKey],
(stakeTxs, account) => {
if (!account) return false;
@@ -386,7 +386,7 @@ export const selectAccountHasStaked = createMemoizedSelector(
export const selectAssetAccountsThatStaked = (
state: TransactionsRootState & AccountsRootState,
accounts: Account[],
-) => accounts.filter(account => selectAccountHasStaked(state, account.key));
+) => accounts.filter(account => selectEthAccountHasStaked(state, account.key));
export const selectAccountTransactionsFetchStatus = (
state: TransactionsRootState,
diff --git a/suite-common/wallet-utils/src/solanaStakingUtils.ts b/suite-common/wallet-utils/src/solanaStakingUtils.ts
index 2eeb5c12c8c..8a144beed25 100644
--- a/suite-common/wallet-utils/src/solanaStakingUtils.ts
+++ b/suite-common/wallet-utils/src/solanaStakingUtils.ts
@@ -1,6 +1,6 @@
-import { Solana, SolNetwork } from '@everstake/wallet-sdk';
+import { Solana, SolDelegation, SolNetwork, StakeAccount, StakeState } from '@everstake/wallet-sdk';
-import { NetworkSymbol } from '@suite-common/wallet-config';
+import { getNetworkFeatures, NetworkSymbol } from '@suite-common/wallet-config';
import { BigNumber, isArrayMember } from '@trezor/utils';
import { SolanaStakingAccount } from '@trezor/blockchain-link-types/src/solana';
import {
@@ -8,8 +8,10 @@ import {
supportedSolanaNetworkSymbols,
SupportedSolanaNetworkSymbols,
} from '@suite-common/wallet-types';
-import { LAMPORTS_PER_SOL } from '@suite-common/wallet-constants';
import { PartialRecord } from '@trezor/type-utils';
+import { SOLANA_EPOCH_DAYS } from '@suite-common/wallet-constants';
+
+import { formatNetworkAmount } from './accountUtils';
export function isSupportedSolStakingNetworkSymbol(
symbol: NetworkSymbol,
@@ -17,11 +19,17 @@ export function isSupportedSolStakingNetworkSymbol(
return isArrayMember(symbol, supportedSolanaNetworkSymbols);
}
-export const formatSolanaStakingAmount = (amount: string | null) => {
- if (!amount) return null;
+export const getSolanaStakingSymbols = (networkSymbols: NetworkSymbol[]) =>
+ networkSymbols.reduce((acc, networkSymbol) => {
+ if (
+ isSupportedSolStakingNetworkSymbol(networkSymbol) &&
+ getNetworkFeatures(networkSymbol).includes('staking')
+ ) {
+ acc.push(networkSymbol);
+ }
- return new BigNumber(amount).div(LAMPORTS_PER_SOL).toFixed(9);
-};
+ return acc;
+ }, [] as SupportedSolanaNetworkSymbols[]);
interface SolNetworkConfig {
network: SolNetwork;
@@ -67,6 +75,108 @@ export const getSolAccountTotalStakingBalance = (account: Account) => {
if (!solStakingAccounts) return null;
const totalStakingBalance = calculateTotalSolStakingBalance(solStakingAccounts);
+ if (!totalStakingBalance) return null;
+
+ return formatNetworkAmount(totalStakingBalance, account.symbol);
+};
+
+export const calculateSolanaStakingReward = (accountBalance?: string, apy?: string) => {
+ if (!accountBalance || !apy) return '0';
+
+ return new BigNumber(accountBalance ?? '')
+ .multipliedBy(apy ?? '0')
+ .dividedBy(100)
+ .dividedBy(365)
+ .multipliedBy(SOLANA_EPOCH_DAYS)
+ .toFixed(9)
+ .toString();
+};
+
+export const getStakingAccountStatus = (
+ solStakingAccount: SolDelegation['account'],
+ epoch: number,
+) => {
+ const stakeAccountClient = new StakeAccount(solStakingAccount);
+
+ return stakeAccountClient.stakeAccountState(epoch);
+};
+
+interface StakingAccountWithStatus extends SolDelegation {
+ status: string;
+}
+
+export const getSolanaStakingAccountsWithStatus = (
+ account: Account,
+): StakingAccountWithStatus[] | null => {
+ if (account.networkType !== 'solana') return null;
+
+ const { solStakingAccounts, solEpoch } = account.misc;
+ if (!solStakingAccounts?.length || !solEpoch) return null;
+
+ const stakingAccountsWithStatus = solStakingAccounts.map(solStakingAccount => {
+ const status = getStakingAccountStatus(solStakingAccount.account, solEpoch);
+
+ return {
+ ...solStakingAccount,
+ status,
+ };
+ });
+
+ return stakingAccountsWithStatus;
+};
- return formatSolanaStakingAmount(totalStakingBalance);
+export const getSolanaStakingAccountsByStatus = (account: Account, status: string) => {
+ const stakingAccountsWithStatus = getSolanaStakingAccountsWithStatus(account);
+
+ if (!stakingAccountsWithStatus) return [];
+
+ return stakingAccountsWithStatus.filter(
+ solStakingAccount => solStakingAccount.status === status,
+ );
+};
+
+export const getStakingAccountCurrentStatus = (account?: Account) => {
+ if (account?.networkType !== 'solana') return null;
+
+ const statusesToCheck = [StakeState.inactive, StakeState.activating];
+
+ for (const status of statusesToCheck) {
+ const stakingAccounts = getSolanaStakingAccountsByStatus(account, status);
+ if (stakingAccounts.length) return status;
+ }
+
+ return null;
+};
+
+export const getSolStakingAccountTotalBalanceByStatus = (account: Account, status: string) => {
+ if (account.networkType !== 'solana') return '0';
+
+ const selectedStakingAccounts = getSolanaStakingAccountsByStatus(account, status);
+ const stakingBalance = calculateTotalSolStakingBalance(selectedStakingAccounts) ?? '0';
+
+ return formatNetworkAmount(stakingBalance, account.symbol);
+};
+
+type StakeStateType = (typeof StakeState)[keyof typeof StakeState];
+
+export const getSolStakingAccountsInfo = (account: Account) => {
+ const balanceResults = Object.entries(StakeState).map(([key, status]) => {
+ const balance = getSolStakingAccountTotalBalanceByStatus(account, status);
+
+ return [key, balance];
+ });
+
+ const balances: Record = balanceResults.reduce(
+ (acc, [key, balance]) => ({ ...acc, [key]: balance }),
+ {},
+ );
+
+ return {
+ solStakedBalance: balances[StakeState.active],
+ solClaimableBalance: balances[StakeState.deactivated],
+ solPendingStakeBalance: balances[StakeState.activating],
+ solPendingUnstakeBalance: balances[StakeState.deactivating],
+ canClaimSol: new BigNumber(balances[StakeState.deactivated]).gt(0),
+ canUnstakeSol: new BigNumber(balances[StakeState.active]).gt(0),
+ };
};
From 873a8e274772b253e0c2303d3ef5b2867623ae68 Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Wed, 18 Dec 2024 16:29:26 +0200
Subject: [PATCH 6/8] feat(suite): add solana unstaking and claiming methods
---
.../wallet/stake/stakeFormSolanaActions.ts | 30 +++++++-
.../suite/src/utils/suite/solanaStaking.ts | 68 +++++++++++++++++--
.../src/solanaStakingConstants.ts | 1 +
.../wallet-core/src/stake/stakeSelectors.ts | 4 +-
4 files changed, 94 insertions(+), 9 deletions(-)
diff --git a/packages/suite/src/actions/wallet/stake/stakeFormSolanaActions.ts b/packages/suite/src/actions/wallet/stake/stakeFormSolanaActions.ts
index 6bae1e158c1..ec16f718ced 100644
--- a/packages/suite/src/actions/wallet/stake/stakeFormSolanaActions.ts
+++ b/packages/suite/src/actions/wallet/stake/stakeFormSolanaActions.ts
@@ -15,11 +15,17 @@ import {
MIN_SOL_AMOUNT_FOR_STAKING,
MIN_SOL_BALANCE_FOR_STAKING,
MIN_SOL_FOR_WITHDRAWALS,
+ SOL_STAKING_OPERATION_FEE,
} from '@suite-common/wallet-constants';
import { Dispatch, GetState } from 'src/types/suite';
import { selectAddressDisplayType } from 'src/reducers/suite/suiteReducer';
-import { getPubKeyFromAddress, prepareStakeSolTx } from 'src/utils/suite/solanaStaking';
+import {
+ getPubKeyFromAddress,
+ prepareClaimSolTx,
+ prepareStakeSolTx,
+ prepareUnstakeSolTx,
+} from 'src/utils/suite/solanaStaking';
import { calculate, composeStakingTransaction } from './stakeFormActions';
@@ -30,7 +36,8 @@ const calculateTransaction = (
compareWithAmount = true,
symbol: NetworkSymbol,
): PrecomposedTransaction => {
- const feeInLamports = new BigNumber(feeLevel.feePerTx ?? '0').toString();
+ // TODO: change to the dynamic fee
+ const feeInLamports = new BigNumber(SOL_STAKING_OPERATION_FEE).toString();
const stakingParams = {
feeInBaseUnits: feeInLamports,
@@ -100,6 +107,25 @@ export const signTransaction =
});
}
+ if (stakeType === 'unstake') {
+ txData = await prepareUnstakeSolTx({
+ from: account.descriptor,
+ path: account.path,
+ amount: formValues.outputs[0].amount,
+ symbol: account.symbol,
+ selectedBlockchain,
+ });
+ }
+
+ if (stakeType === 'claim') {
+ txData = await prepareClaimSolTx({
+ from: account.descriptor,
+ path: account.path,
+ symbol: account.symbol,
+ selectedBlockchain,
+ });
+ }
+
if (!txData) {
dispatch(
notificationsActions.addToast({
diff --git a/packages/suite/src/utils/suite/solanaStaking.ts b/packages/suite/src/utils/suite/solanaStaking.ts
index 09643c38e2b..ec15fd8ea96 100644
--- a/packages/suite/src/utils/suite/solanaStaking.ts
+++ b/packages/suite/src/utils/suite/solanaStaking.ts
@@ -1,9 +1,11 @@
import { VersionedTransaction, PublicKey } from '@solana/web3.js-version1';
import { NetworkSymbol } from '@suite-common/wallet-config';
-import { LAMPORTS_PER_SOL, WALLET_SDK_SOURCE } from '@suite-common/wallet-constants';
-import { selectSolanaWalletSdkNetwork } from '@suite-common/wallet-utils';
-import { BigNumber } from '@trezor/utils';
+import { WALLET_SDK_SOURCE } from '@suite-common/wallet-constants';
+import {
+ networkAmountToSmallestUnit,
+ selectSolanaWalletSdkNetwork,
+} from '@suite-common/wallet-utils';
import type { SolanaSignTransaction } from '@trezor/connect';
import { Blockchain } from '@suite-common/wallet-types';
@@ -65,8 +67,64 @@ export const prepareStakeSolTx = async ({
try {
const solanaClient = selectSolanaWalletSdkNetwork(symbol, selectedBlockchain.url);
- const lamports = new BigNumber(LAMPORTS_PER_SOL).multipliedBy(amount).toNumber(); // stake method expects lamports as a number
- const tx = await solanaClient.stake(from, lamports, WALLET_SDK_SOURCE);
+ const lamports = networkAmountToSmallestUnit(amount, symbol);
+ const tx = await solanaClient.stake(from, Number(lamports), WALLET_SDK_SOURCE);
+ const transformedTx = transformTx(tx.result, path);
+
+ return {
+ success: true,
+ tx: transformedTx,
+ };
+ } catch (e) {
+ console.error(e);
+
+ return {
+ success: false,
+ errorMessage: e.message,
+ };
+ }
+};
+
+export const prepareUnstakeSolTx = async ({
+ from,
+ path,
+ amount,
+ symbol,
+ selectedBlockchain,
+}: PrepareStakeSolTxParams): Promise => {
+ try {
+ const solanaClient = selectSolanaWalletSdkNetwork(symbol, selectedBlockchain.url);
+
+ const lamports = networkAmountToSmallestUnit(amount, symbol);
+ const tx = await solanaClient.unstake(from, Number(lamports), WALLET_SDK_SOURCE);
+ const transformedTx = transformTx(tx.result, path);
+
+ return {
+ success: true,
+ tx: transformedTx,
+ };
+ } catch (e) {
+ console.error(e);
+
+ return {
+ success: false,
+ errorMessage: e.message,
+ };
+ }
+};
+
+type PrepareClaimSolTxParams = Omit;
+
+export const prepareClaimSolTx = async ({
+ from,
+ path,
+ symbol,
+ selectedBlockchain,
+}: PrepareClaimSolTxParams): Promise => {
+ try {
+ const solanaClient = selectSolanaWalletSdkNetwork(symbol, selectedBlockchain.url);
+
+ const tx = await solanaClient.claim(from);
const transformedTx = transformTx(tx.result, path);
return {
diff --git a/suite-common/wallet-constants/src/solanaStakingConstants.ts b/suite-common/wallet-constants/src/solanaStakingConstants.ts
index a5dd6362051..1c1cb71c776 100644
--- a/suite-common/wallet-constants/src/solanaStakingConstants.ts
+++ b/suite-common/wallet-constants/src/solanaStakingConstants.ts
@@ -5,5 +5,6 @@ export const MIN_SOL_AMOUNT_FOR_STAKING = new BigNumber(0.01);
export const MAX_SOL_AMOUNT_FOR_STAKING = new BigNumber(10_000_000);
export const MIN_SOL_FOR_WITHDRAWALS = new BigNumber(0.000005);
export const MIN_SOL_BALANCE_FOR_STAKING = MIN_SOL_AMOUNT_FOR_STAKING.plus(MIN_SOL_FOR_WITHDRAWALS);
+export const SOL_STAKING_OPERATION_FEE = new BigNumber(70_000); // 0.00007 SOL
export const SOLANA_EPOCH_DAYS = 3;
diff --git a/suite-common/wallet-core/src/stake/stakeSelectors.ts b/suite-common/wallet-core/src/stake/stakeSelectors.ts
index 6f27cd910ce..be1cef4f5f4 100644
--- a/suite-common/wallet-core/src/stake/stakeSelectors.ts
+++ b/suite-common/wallet-core/src/stake/stakeSelectors.ts
@@ -6,9 +6,9 @@ import { StakeRootState } from './stakeReducer';
export const selectEverstakeData = (
state: StakeRootState,
- networkSymbol: NetworkSymbol,
+ symbol: NetworkSymbol,
endpointType: 'poolStats' | 'validatorsQueue' | 'getAssets',
-) => state.wallet.stake?.data?.[networkSymbol]?.[endpointType];
+) => state.wallet.stake?.data?.[symbol]?.[endpointType];
export const selectPoolStatsApyData = (state: StakeRootState, symbol?: NetworkSymbol) => {
const { data } = state.wallet.stake ?? {};
From f80ce457c44804ffb6e92ab190803556019a2d0c Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Fri, 20 Dec 2024 14:01:44 +0200
Subject: [PATCH 7/8] feat(suite): include Solana staking balance in assets
---
.../AccountsMenu/AccountSection.tsx | 9 ++++---
.../AssetsView/AssetCard/AssetCard.tsx | 16 +++++++++---
.../AssetsView/AssetTable/AssetRow.tsx | 16 +++++++++---
.../views/dashboard/AssetsView/AssetsView.tsx | 7 ++++--
.../StakingDashboard/StakingDashboard.tsx | 9 +++----
.../src/accounts/accountsReducer.ts | 6 +++++
.../src/transactions/transactionsReducer.ts | 25 +++++++++++++++++--
7 files changed, 67 insertions(+), 21 deletions(-)
diff --git a/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx b/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx
index 6c97617b7e4..fdf79dcd751 100644
--- a/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx
+++ b/packages/suite/src/components/wallet/WalletLayout/AccountsMenu/AccountSection.tsx
@@ -1,6 +1,6 @@
import { Account } from '@suite-common/wallet-types';
import { selectCoinDefinitions } from '@suite-common/token-definitions';
-import { selectEthAccountHasStaked, selectSolStakingAccounts } from '@suite-common/wallet-core';
+import { selectEthAccountHasStaked, selectSolAccountHasStaked } from '@suite-common/wallet-core';
import { isSupportedStakingNetworkSymbol } from '@suite-common/wallet-utils';
import { useSelector } from 'src/hooks/suite';
@@ -37,12 +37,13 @@ export const AccountSection = ({
const coinDefinitions = useSelector(state => selectCoinDefinitions(state, symbol));
const hasEthStaked = useSelector(state => selectEthAccountHasStaked(state, account.key));
- const solStakingAccounts = useSelector(state => selectSolStakingAccounts(state, account.key));
+ const hasSolStaked = useSelector(state => selectSolAccountHasStaked(state, account.key));
+
// TODO: remove isDebugModeActive when staking will be ready for launch
- const hasSolStakingAccount = !!solStakingAccounts?.length && isDebugModeActive; // for solana
+ const isSolStakingEnabled = hasSolStaked && isDebugModeActive;
const isStakeShown =
- isSupportedStakingNetworkSymbol(symbol) && (hasEthStaked || hasSolStakingAccount);
+ isSupportedStakingNetworkSymbol(symbol) && (hasEthStaked || isSolStakingEnabled);
const showGroup = ['ethereum', 'solana', 'cardano'].includes(networkType);
diff --git a/packages/suite/src/views/dashboard/AssetsView/AssetCard/AssetCard.tsx b/packages/suite/src/views/dashboard/AssetsView/AssetCard/AssetCard.tsx
index 02450123243..4490fc1cdaa 100644
--- a/packages/suite/src/views/dashboard/AssetsView/AssetCard/AssetCard.tsx
+++ b/packages/suite/src/views/dashboard/AssetsView/AssetCard/AssetCard.tsx
@@ -19,7 +19,7 @@ import { AssetFiatBalance } from '@suite-common/assets';
import { TokenInfo } from '@trezor/connect';
import { Account, RatesByKey } from '@suite-common/wallet-types';
import { isTestnet } from '@suite-common/wallet-utils';
-import { selectAssetAccountsThatStaked } from '@suite-common/wallet-core';
+import { selectDebugFilteredAssetAccountsThatStaked } from '@suite-common/wallet-core';
import { selectCoinDefinitions } from '@suite-common/token-definitions';
import { FiatCurrencyCode } from '@suite-common/suite-config';
@@ -33,6 +33,7 @@ import {
import { useAccountSearch, useLoadingSkeleton, useSelector } from 'src/hooks/suite';
import { goto } from 'src/actions/suite/routerActions';
import { FiatHeader } from 'src/components/wallet/FiatHeader';
+import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
import { CoinmarketBuyButton } from '../CoinmarketBuyButton';
import { AssetCardInfo, AssetCardInfoSkeleton } from './AssetCardInfo';
@@ -123,14 +124,21 @@ export const AssetCard = ({
const stakingAccountsForAsset = stakingAccounts.filter(account => account.symbol === symbol);
const coinDefinitions = useSelector(state => selectCoinDefinitions(state, symbol));
- const accountsThatStaked = useSelector(state =>
- selectAssetAccountsThatStaked(state, stakingAccountsForAsset),
+
+ // TODO: remove this logic when Solana staking is available
+ const isDebugModeActive = useSelector(selectIsDebugModeActive);
+ const debugFilteredStakedAccounts = useSelector(state =>
+ selectDebugFilteredAssetAccountsThatStaked(
+ state,
+ stakingAccountsForAsset,
+ isDebugModeActive,
+ ),
);
const { tokensFiatBalance, assetStakingBalance, shouldRenderStakingRow, shouldRenderTokenRow } =
handleTokensAndStakingData(
assetTokens,
- accountsThatStaked,
+ debugFilteredStakedAccounts,
symbol,
localCurrency,
coinDefinitions,
diff --git a/packages/suite/src/views/dashboard/AssetsView/AssetTable/AssetRow.tsx b/packages/suite/src/views/dashboard/AssetsView/AssetTable/AssetRow.tsx
index ef90a0696e0..862da2bb791 100644
--- a/packages/suite/src/views/dashboard/AssetsView/AssetTable/AssetRow.tsx
+++ b/packages/suite/src/views/dashboard/AssetsView/AssetTable/AssetRow.tsx
@@ -8,7 +8,7 @@ import { isTestnet } from '@suite-common/wallet-utils';
import { spacings } from '@trezor/theme';
import { TokenInfo } from '@trezor/blockchain-link-types';
import { selectCoinDefinitions } from '@suite-common/token-definitions';
-import { selectAssetAccountsThatStaked } from '@suite-common/wallet-core';
+import { selectDebugFilteredAssetAccountsThatStaked } from '@suite-common/wallet-core';
import { Account, RatesByKey } from '@suite-common/wallet-types';
import { AssetFiatBalance } from '@suite-common/assets';
import { FiatCurrencyCode } from '@suite-common/suite-config';
@@ -24,6 +24,7 @@ import {
import { goto } from 'src/actions/suite/routerActions';
import { useAccountSearch, useDispatch, useSelector } from 'src/hooks/suite';
import { TokenIconSetWrapper } from 'src/components/wallet/TokenIconSetWrapper';
+import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
import { AssetCoinLogo } from '../AssetCoinLogo';
import { AssetCoinName } from '../AssetCoinName';
@@ -81,8 +82,15 @@ export const AssetRow = memo(
const stakingAccountsForAsset = stakingAccounts.filter(
account => account.symbol === network.symbol,
);
- const accountsThatStaked = useSelector(state =>
- selectAssetAccountsThatStaked(state, stakingAccountsForAsset),
+
+ // TODO: remove this logic when Solana staking is available
+ const isDebugModeActive = useSelector(selectIsDebugModeActive);
+ const debugFilteredStakedAccounts = useSelector(state =>
+ selectDebugFilteredAssetAccountsThatStaked(
+ state,
+ stakingAccountsForAsset,
+ isDebugModeActive,
+ ),
);
const {
@@ -92,7 +100,7 @@ export const AssetRow = memo(
shouldRenderTokenRow,
} = handleTokensAndStakingData(
assetTokens,
- accountsThatStaked,
+ debugFilteredStakedAccounts,
symbol,
localCurrency,
coinDefinitions,
diff --git a/packages/suite/src/views/dashboard/AssetsView/AssetsView.tsx b/packages/suite/src/views/dashboard/AssetsView/AssetsView.tsx
index f5133732a32..80193c305ed 100644
--- a/packages/suite/src/views/dashboard/AssetsView/AssetsView.tsx
+++ b/packages/suite/src/views/dashboard/AssetsView/AssetsView.tsx
@@ -10,6 +10,7 @@ import {
getFiatRateKey,
toFiatCurrency,
isSupportedEthStakingNetworkSymbol,
+ isSupportedSolStakingNetworkSymbol,
} from '@suite-common/wallet-utils';
import {
type NetworkSymbol,
@@ -139,8 +140,10 @@ export const AssetsView = () => {
? assetNativeCryptoBalance.toNumber()
: '0',
assetTokens: assetTokens?.length ? assetTokens : undefined,
- stakingAccounts: accounts.filter(account =>
- isSupportedEthStakingNetworkSymbol(account.symbol),
+ stakingAccounts: accounts.filter(
+ account =>
+ isSupportedEthStakingNetworkSymbol(account.symbol) ||
+ isSupportedSolStakingNetworkSymbol(account.symbol),
),
accounts,
};
diff --git a/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
index 6d71c3634d1..253196940a2 100644
--- a/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/StakingDashboard.tsx
@@ -1,4 +1,4 @@
-import { selectEthAccountHasStaked, selectSolStakingAccounts } from '@suite-common/wallet-core';
+import { selectEthAccountHasStaked, selectSolAccountHasStaked } from '@suite-common/wallet-core';
import { SelectedAccountStatus } from '@suite-common/wallet-types';
import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
@@ -17,14 +17,13 @@ export const StakingDashboard = ({ selectedAccount, dashboard }: StakingDashboar
const hasEthStaked = useSelector(state =>
selectEthAccountHasStaked(state, selectedAccount.account?.key ?? ''),
);
- const solStakingAccounts = useSelector(state =>
- selectSolStakingAccounts(state, selectedAccount.account?.key),
+ const hasSolStaked = useSelector(state =>
+ selectSolAccountHasStaked(state, selectedAccount.account?.key),
);
if (selectedAccount.status !== 'loaded') return null;
- const hasSolStakingAccount = !!solStakingAccounts?.length;
- const shouldShowDashboard = hasEthStaked || hasSolStakingAccount;
+ const shouldShowDashboard = hasEthStaked || hasSolStaked;
return (
{
+ if (!account || account.networkType !== 'solana') return false;
+
+ return !!account.misc.solStakingAccounts?.length;
+});
diff --git a/suite-common/wallet-core/src/transactions/transactionsReducer.ts b/suite-common/wallet-core/src/transactions/transactionsReducer.ts
index e706ba0c737..110d55a2ac0 100644
--- a/suite-common/wallet-core/src/transactions/transactionsReducer.ts
+++ b/suite-common/wallet-core/src/transactions/transactionsReducer.ts
@@ -32,7 +32,11 @@ import {
fetchTransactionsPageThunk,
fetchAllTransactionsForAccountThunk,
} from './transactionsThunks';
-import { AccountsRootState, selectAccountByKey } from '../accounts/accountsReducer';
+import {
+ AccountsRootState,
+ selectAccountByKey,
+ selectSolAccountHasStaked,
+} from '../accounts/accountsReducer';
export type AccountTransactionsFetchStatusDetail =
| {
@@ -386,7 +390,24 @@ export const selectEthAccountHasStaked = createMemoizedSelector(
export const selectAssetAccountsThatStaked = (
state: TransactionsRootState & AccountsRootState,
accounts: Account[],
-) => accounts.filter(account => selectEthAccountHasStaked(state, account.key));
+) =>
+ accounts.filter(
+ account =>
+ selectEthAccountHasStaked(state, account.key) ||
+ selectSolAccountHasStaked(state, account.key),
+ );
+
+export const selectDebugFilteredAssetAccountsThatStaked = (
+ state: TransactionsRootState & AccountsRootState,
+ accounts: Account[],
+ isDebugModeActive: boolean,
+) => {
+ const accountsThatStaked = selectAssetAccountsThatStaked(state, accounts);
+
+ return !isDebugModeActive
+ ? accountsThatStaked.filter(account => account.networkType !== 'solana')
+ : accountsThatStaked;
+};
export const selectAccountTransactionsFetchStatus = (
state: TransactionsRootState,
From fe44017ac5ae490e3283d88e30d3fb0112a11142 Mon Sep 17 00:00:00 2001
From: tomasklim
Date: Sat, 28 Dec 2024 10:11:31 +0100
Subject: [PATCH 8/8] chore(suite): make stake in nutshell generic
---
.../PageHeader/PageNames/AccountName/AccountDetails.tsx | 3 +--
.../StakeInANutshellModal.tsx} | 4 ++--
.../modals/ReduxModal/UserContextModal/UserContextModal.tsx | 6 +++---
packages/suite/src/components/suite/modals/index.tsx | 2 +-
.../StakingDashboard/components/EmptyStakingCard.tsx | 6 +++---
suite-common/suite-types/src/modal.ts | 2 +-
6 files changed, 11 insertions(+), 12 deletions(-)
rename packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/{StakeEthInANutshellModal/StakeEthInANutshellModal.tsx => StakeInANutshellModal/StakeInANutshellModal.tsx} (97%)
diff --git a/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx b/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx
index ca39c7d0e2e..f2b44601429 100644
--- a/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx
+++ b/packages/suite/src/components/suite/layouts/SuiteLayout/PageHeader/PageNames/AccountName/AccountDetails.tsx
@@ -6,7 +6,6 @@ import { Account } from '@suite-common/wallet-types';
import { spacingsPx, zIndices, typography } from '@trezor/theme';
import { H2 } from '@trezor/components';
import { CoinLogo } from '@trezor/product-components';
-import { getNetworkDisplaySymbol } from '@suite-common/wallet-config';
import {
MetadataLabeling,
@@ -118,7 +117,7 @@ export const AccountDetails = ({ selectedAccount, isBalanceShown }: AccountDetai
showAccountTypeBadge
accountLabel={selectedAccountLabels.accountLabel}
accountType={accountType}
- symbol={getNetworkDisplaySymbol(selectedAccount.symbol)}
+ symbol={selectedAccount.symbol}
index={index}
path={path}
networkType={selectedAccount.networkType}
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeEthInANutshellModal/StakeEthInANutshellModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeInANutshellModal/StakeInANutshellModal.tsx
similarity index 97%
rename from packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeEthInANutshellModal/StakeEthInANutshellModal.tsx
rename to packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeInANutshellModal/StakeInANutshellModal.tsx
index 6039015f9c0..6d2ab14518d 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeEthInANutshellModal/StakeEthInANutshellModal.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/StakeInANutshellModal/StakeInANutshellModal.tsx
@@ -50,11 +50,11 @@ const STAKING_DETAILS: StakingDetails[] = [
},
];
-interface StakeEthInANutshellModalProps {
+interface StakeInANutshellModalProps {
onCancel: () => void;
}
-export const StakeEthInANutshellModal = ({ onCancel }: StakeEthInANutshellModalProps) => {
+export const StakeInANutshellModal = ({ onCancel }: StakeInANutshellModalProps) => {
const account = useSelector(selectSelectedAccount);
const dispatch = useDispatch();
const { validatorWithdrawTime } = useSelector(state =>
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx
index 215e70cb0a6..ebdfc95684f 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx
@@ -33,7 +33,7 @@ import {
AuthenticateDeviceModal,
AuthenticateDeviceFailModal,
DeviceAuthenticityOptOutModal,
- StakeEthInANutshellModal,
+ StakeInANutshellModal,
StakeModal,
UnstakeModal,
ClaimModal,
@@ -185,8 +185,8 @@ export const UserContextModal = ({
return ;
case 'authenticate-device-fail':
return ;
- case 'stake-eth-in-a-nutshell':
- return ;
+ case 'stake-in-a-nutshell':
+ return ;
case 'stake':
return ;
case 'unstake':
diff --git a/packages/suite/src/components/suite/modals/index.tsx b/packages/suite/src/components/suite/modals/index.tsx
index e4cfc440411..a65b172ddcf 100644
--- a/packages/suite/src/components/suite/modals/index.tsx
+++ b/packages/suite/src/components/suite/modals/index.tsx
@@ -41,7 +41,7 @@ export { UnecoCoinjoinModal } from './ReduxModal/UserContextModal/UnecoCoinjoinM
export { AuthenticateDeviceModal } from './ReduxModal/UserContextModal/AuthenticateDeviceModal';
export { AuthenticateDeviceFailModal } from './ReduxModal/UserContextModal/AuthenticateDeviceFailModal';
export { DeviceAuthenticityOptOutModal } from './ReduxModal/UserContextModal/DeviceAuthenticityOptOutModal';
-export { StakeEthInANutshellModal } from './ReduxModal/UserContextModal/StakeEthInANutshellModal/StakeEthInANutshellModal';
+export { StakeInANutshellModal } from './ReduxModal/UserContextModal/StakeInANutshellModal/StakeInANutshellModal';
export { StakeModal } from './ReduxModal/UserContextModal/StakeModal/StakeModal';
export { UnstakeModal } from './ReduxModal/UserContextModal/UnstakeModal/UnstakeModal';
export { ClaimModal } from './ReduxModal/UserContextModal/ClaimModal/ClaimModal';
diff --git a/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx
index 534ed99825b..d25e2964cda 100644
--- a/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx
+++ b/packages/suite/src/views/wallet/staking/components/StakingDashboard/components/EmptyStakingCard.tsx
@@ -33,9 +33,9 @@ export const EmptyStakingCard = () => {
const apy = useSelector(state => selectPoolStatsApyData(state, account?.symbol));
const dispatch = useDispatch();
- const openStakingEthInANutshellModal = () => {
+ const openStakeInANutshellModal = () => {
if (!isStakingDisabled) {
- dispatch(openModal({ type: 'stake-eth-in-a-nutshell' }));
+ dispatch(openModal({ type: 'stake-in-a-nutshell' }));
}
};
@@ -119,7 +119,7 @@ export const EmptyStakingCard = () => {