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 = () => {