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 ?? {};