From 873a8e274772b253e0c2303d3ef5b2867623ae68 Mon Sep 17 00:00:00 2001 From: Pavlo Syrotyna Date: Wed, 18 Dec 2024 16:29:26 +0200 Subject: [PATCH] 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 ?? {};