diff --git a/packages/blockchain-link-types/src/blockfrost.ts b/packages/blockchain-link-types/src/blockfrost.ts
index afcb214d94e..4ee80dcd594 100644
--- a/packages/blockchain-link-types/src/blockfrost.ts
+++ b/packages/blockchain-link-types/src/blockfrost.ts
@@ -169,6 +169,22 @@ export interface BlockfrostAccountInfo {
total: number;
index: number;
};
+ misc: {
+ staking: {
+ address: string;
+ isActive: boolean;
+ rewards: string;
+ poolId: string | null;
+ drep: {
+ drep_id: string;
+ hex: string;
+ amount: string;
+ active: boolean;
+ active_epoch: number | null;
+ has_script: boolean;
+ } | null;
+ };
+ };
}
export interface ParseAssetResult {
diff --git a/packages/blockchain-link-types/src/common.ts b/packages/blockchain-link-types/src/common.ts
index 3d5827fa28c..4bf76b3b682 100644
--- a/packages/blockchain-link-types/src/common.ts
+++ b/packages/blockchain-link-types/src/common.ts
@@ -223,6 +223,14 @@ export interface AccountInfo {
isActive: boolean;
rewards: string;
poolId: string | null;
+ drep: {
+ drep_id: string;
+ hex: string;
+ amount: string;
+ active: boolean;
+ active_epoch: number | null;
+ has_script: boolean;
+ } | null;
};
// SOL
owner?: string; // The Solana program owning the account
diff --git a/packages/suite/src/actions/wallet/cardanoStakingActions.ts b/packages/suite/src/actions/wallet/cardanoStakingActions.ts
index 2eb4b13990d..6c66d8888eb 100644
--- a/packages/suite/src/actions/wallet/cardanoStakingActions.ts
+++ b/packages/suite/src/actions/wallet/cardanoStakingActions.ts
@@ -1,14 +1,24 @@
import { getUnixTime } from 'date-fns';
import { BlockchainBlock } from '@trezor/connect';
-import { CARDANO_STAKE_POOL_PREVIEW_URL, CARDANO_STAKE_POOL_MAINNET_URL } from '@trezor/urls';
-import { isPending, getAccountTransactions } from '@suite-common/wallet-utils';
+import {
+ CARDANO_STAKE_POOL_PREVIEW_URL,
+ CARDANO_STAKE_POOL_MAINNET_URL,
+ CARDANO_MAINNET_DREP,
+ CARDANO_PREVIEW_DREP,
+} from '@trezor/urls';
+import { isPending, getAccountTransactions, getNetworkName } from '@suite-common/wallet-utils';
import { CARDANO_DEFAULT_TTL_OFFSET } from '@suite-common/wallet-constants';
import { transactionsActions } from '@suite-common/wallet-core';
import { getNetworkOptional } from '@suite-common/wallet-config';
import { CARDANO_STAKING } from 'src/actions/wallet/constants';
-import { PendingStakeTx, PoolsResponse, CardanoNetwork } from 'src/types/wallet/cardanoStaking';
+import {
+ PendingStakeTx,
+ PoolsResponse,
+ CardanoNetwork,
+ DRepResponse,
+} from 'src/types/wallet/cardanoStaking';
import { Account, WalletAccountTransaction } from 'src/types/wallet';
import { Dispatch, GetState } from 'src/types/suite';
@@ -16,8 +26,9 @@ export type CardanoStakingAction =
| { type: typeof CARDANO_STAKING.ADD_PENDING_STAKE_TX; pendingStakeTx: PendingStakeTx }
| { type: typeof CARDANO_STAKING.REMOVE_PENDING_STAKE_TX; accountKey: string }
| {
- type: typeof CARDANO_STAKING.SET_TREZOR_POOLS;
+ type: typeof CARDANO_STAKING.SET_TREZOR_DATA;
trezorPools: PoolsResponse;
+ trezorDRep: DRepResponse;
network: CardanoNetwork;
}
| { type: typeof CARDANO_STAKING.SET_FETCH_ERROR; error: boolean; network: CardanoNetwork }
@@ -102,8 +113,8 @@ export const validatePendingStakeTxOnTx =
}
};
-export const fetchTrezorPools = (network: 'ADA' | 'tADA') => async (dispatch: Dispatch) => {
- const cardanoNetwork = network === 'ADA' ? 'mainnet' : 'preview';
+export const fetchTrezorData = (network: 'ADA' | 'tADA') => async (dispatch: Dispatch) => {
+ const cardanoNetwork = getNetworkName(network);
dispatch({
type: CARDANO_STAKING.SET_FETCH_LOADING,
@@ -111,25 +122,38 @@ export const fetchTrezorPools = (network: 'ADA' | 'tADA') => async (dispatch: Di
network: cardanoNetwork,
});
- // Fetch ID of Trezor stake pool that will be used in delegation transaction
- const url =
- cardanoNetwork === 'mainnet'
- ? CARDANO_STAKE_POOL_MAINNET_URL
- : CARDANO_STAKE_POOL_PREVIEW_URL;
-
try {
- const response = await fetch(url, { credentials: 'same-origin' });
- const responseJson = await response.json();
-
- if (!responseJson || !('next' in responseJson) || !('pools' in responseJson)) {
- // todo: even if this happens, error will be overridden by this bug
- // https://github.com/trezor/trezor-suite/issues/5485
+ // Fetch ID of Trezor stake pool that will be used in delegation transaction
+ const urlPools =
+ cardanoNetwork === 'mainnet'
+ ? CARDANO_STAKE_POOL_MAINNET_URL
+ : CARDANO_STAKE_POOL_PREVIEW_URL;
+
+ const responsePools = await fetch(urlPools, { credentials: 'same-origin' });
+ const responsePoolsJson = await responsePools.json();
+
+ if (
+ !responsePoolsJson ||
+ !('next' in responsePoolsJson) ||
+ !('pools' in responsePoolsJson)
+ ) {
throw new Error('Cardano: fetchTrezorPools: Invalid data format');
}
+ // Fetch DRep for transaction withdrawal
+ const urlDRep = cardanoNetwork === 'mainnet' ? CARDANO_MAINNET_DREP : CARDANO_PREVIEW_DREP;
+
+ const responseDRep = await fetch(urlDRep, { credentials: 'same-origin' });
+ const responseDRepJson = await responseDRep.json();
+
+ if (!responseDRepJson || !('drep' in responseDRepJson)) {
+ throw new Error('Cardano: fetchTrezorDRep: Invalid data format');
+ }
+
dispatch({
- type: CARDANO_STAKING.SET_TREZOR_POOLS,
- trezorPools: responseJson as PoolsResponse,
+ type: CARDANO_STAKING.SET_TREZOR_DATA,
+ trezorPools: responsePoolsJson as PoolsResponse,
+ trezorDRep: responseDRepJson as DRepResponse,
network: cardanoNetwork,
});
} catch {
diff --git a/packages/suite/src/actions/wallet/constants/cardanoStakingConstants.ts b/packages/suite/src/actions/wallet/constants/cardanoStakingConstants.ts
index 3a68120c898..1c1bad7d2c0 100644
--- a/packages/suite/src/actions/wallet/constants/cardanoStakingConstants.ts
+++ b/packages/suite/src/actions/wallet/constants/cardanoStakingConstants.ts
@@ -1,6 +1,6 @@
export const ADD_PENDING_STAKE_TX = '@cardano-staking/set-pending-stake-tx';
export const REMOVE_PENDING_STAKE_TX = '@cardano-staking/remove-pending-stake-tx';
export const IS_LOADING = '@cardano-staking/is-loading';
-export const SET_TREZOR_POOLS = '@cardano-staking/set-trezor-pools';
+export const SET_TREZOR_DATA = '@cardano-staking/set-trezor-data';
export const SET_FETCH_LOADING = '@cardano-staking/set-fetch-loading';
export const SET_FETCH_ERROR = '@cardano-staking/set-fetch-error';
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/CardanoWithdrawModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/CardanoWithdrawModal.tsx
new file mode 100644
index 00000000000..03cdc75f55f
--- /dev/null
+++ b/packages/suite/src/components/suite/modals/ReduxModal/CardanoWithdrawModal.tsx
@@ -0,0 +1,61 @@
+import { Button, Icon, Link, NewModal, Row, Column, Text, Card } from '@trezor/components';
+import { spacings } from '@trezor/theme';
+import { getNetworkName } from '@suite-common/wallet-utils';
+
+import { useSelector } from 'src/hooks/suite/useSelector';
+import { useCardanoStaking } from 'src/hooks/wallet/useCardanoStaking';
+import { Translation } from 'src/components/suite';
+import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
+
+export const CardanoWithdrawModal = ({ onCancel }: { onCancel: () => void }) => {
+ const { voteAbstain, voteDelegate } = useCardanoStaking();
+ const account = useSelector(state => selectSelectedAccount(state));
+
+ if (!account || account.networkType !== 'cardano') {
+ throw Error(
+ 'CardanoWithdrawModal used for other network or account in selectedAccount is undefined',
+ );
+ }
+
+ const cardanoNetwork = getNetworkName(account.symbol);
+ const { trezorDRep } = useSelector(state => state.wallet.cardanoStaking[cardanoNetwork]);
+ const trezorDRepBech32 = trezorDRep?.drep.bech32;
+
+ return (
+ }
+ description={}
+ >
+
+
+
+
+
+
+
+
+
+ {trezorDRepBech32}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
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..fedd3135c84 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/UserContextModal.tsx
@@ -50,6 +50,7 @@ import type { ReduxModalProps } from '../ReduxModal';
import { EverstakeModal } from './UnstakeModal/EverstakeModal';
import { PassphraseMismatchModal } from './PassphraseMismatchModal';
import { FirmwareRevisionOptOutModal } from './FirmwareRevisionOptOutModal';
+import { CardanoWithdrawModal } from '../CardanoWithdrawModal';
/** Modals opened as a result of user action */
export const UserContextModal = ({
@@ -105,6 +106,8 @@ export const UserContextModal = ({
);
case 'review-transaction':
return ;
+ case 'cardano-withdraw-modal':
+ return ;
case 'coinmarket-buy-terms': {
return (
{
throw Error('useCardanoStaking used for other network');
}
+ const alreadyVoted = account.misc?.staking?.drep !== null;
const device = useSelector(selectSelectedDevice);
+ const cardanoNetwork = getNetworkName(account.symbol);
+ const { trezorDRep } = useSelector(state => state.wallet.cardanoStaking[cardanoNetwork]);
const isDeviceLocked = useSelector(selectIsDeviceLocked);
const cardanoStaking = useSelector(state => state.wallet.cardanoStaking);
const dispatch = useDispatch();
@@ -80,6 +86,7 @@ export const useCardanoStaking = (): CardanoStaking => {
>({
status: false,
});
+
const [error, setError] = useState(undefined);
const stakingPath = getStakingPath(account);
const pendingStakeTx = cardanoStaking.pendingTx.find(tx => tx.accountKey === account.key);
@@ -89,9 +96,9 @@ export const useCardanoStaking = (): CardanoStaking => {
address: stakeAddress,
poolId: registeredPoolId,
isActive: isStakingActive,
+ drep: accountDRep,
} = account.misc.staking;
- const cardanoNetwork = account.symbol === 'ada' ? 'mainnet' : 'preview';
const { trezorPools, isFetchLoading, isFetchError } = cardanoStaking[cardanoNetwork];
const currentPool =
registeredPoolId && trezorPools
@@ -99,8 +106,9 @@ export const useCardanoStaking = (): CardanoStaking => {
: null;
const isStakingOnTrezorPool = !isFetchLoading && !isFetchError ? !!currentPool : true; // fallback to true to prevent flickering in UI while we fetch the data
const isCurrentPoolOversaturated = currentPool ? isPoolOverSaturated(currentPool) : false;
+
const prepareTxPlan = useCallback(
- async (action: 'delegate' | 'withdrawal') => {
+ async (action: CardanoAction) => {
const changeAddress = getUnusedChangeAddress(account);
if (!changeAddress || !account.utxo || !account.addresses) return null;
@@ -110,10 +118,26 @@ export const useCardanoStaking = (): CardanoStaking => {
? getStakePoolForDelegation(trezorPools, account.balance).hex
: '';
- const certificates =
+ let certificates =
action === 'delegate'
? getDelegationCertificates(stakingPath, pool, !isStakingActive)
: [];
+
+ if (action === 'voteAbstain') {
+ const dRep = { type: PROTO.CardanoDRepType.ABSTAIN };
+
+ certificates = getVotingCertificates(stakingPath, dRep);
+ }
+
+ if (action === 'voteDelegate') {
+ const dRep = {
+ type: PROTO.CardanoDRepType.KEY_HASH,
+ hex: trezorDRep?.drep?.hex,
+ };
+
+ certificates = getVotingCertificates(stakingPath, dRep);
+ }
+
const withdrawals =
action === 'withdrawal'
? [
@@ -142,11 +166,19 @@ export const useCardanoStaking = (): CardanoStaking => {
return { txPlan: response.payload[0], certificates, withdrawals };
},
- [account, stakingPath, isStakingActive, rewardsAmount, stakeAddress, trezorPools],
+ [
+ trezorDRep,
+ account,
+ trezorPools,
+ stakingPath,
+ isStakingActive,
+ rewardsAmount,
+ stakeAddress,
+ ],
);
const calculateFeeAndDeposit = useCallback(
- async (action: 'delegate' | 'withdrawal') => {
+ async (action: CardanoAction) => {
setLoading(true);
try {
const composeRes = await prepareTxPlan(action);
@@ -186,8 +218,9 @@ export const useCardanoStaking = (): CardanoStaking => {
);
const signAndPushTransaction = useCallback(
- async (action: 'delegate' | 'withdrawal') => {
+ async (action: CardanoAction) => {
const composeRes = await prepareTxPlan(action);
+
if (!composeRes) return;
const { txPlan, certificates, withdrawals } = composeRes;
@@ -257,18 +290,33 @@ export const useCardanoStaking = (): CardanoStaking => {
);
const action = useCallback(
- async (action: 'delegate' | 'withdrawal') => {
+ async (actionType: CardanoAction) => {
setError(undefined);
setLoading(true);
try {
- await signAndPushTransaction(action);
- } catch (error) {
- if (error.message === 'UTXO_BALANCE_INSUFFICIENT') {
+ switch (actionType) {
+ case 'withdrawal':
+ await signAndPushTransaction('withdrawal');
+ break;
+ case 'delegate':
+ await signAndPushTransaction('delegate');
+ break;
+ case 'voteAbstain':
+ await signAndPushTransaction('voteAbstain');
+ break;
+ case 'voteDelegate':
+ await signAndPushTransaction('voteDelegate');
+ break;
+ default:
+ break;
+ }
+ } catch (err: any) {
+ if (err.message === 'UTXO_BALANCE_INSUFFICIENT') {
setError('AMOUNT_IS_NOT_ENOUGH');
dispatch(
notificationsActions.addToast({
type:
- action === 'delegate'
+ actionType === 'delegate'
? 'cardano-delegate-error'
: 'cardano-withdrawal-error',
error: 'UTXO_BALANCE_INSUFFICIENT',
@@ -278,7 +326,7 @@ export const useCardanoStaking = (): CardanoStaking => {
dispatch(
notificationsActions.addToast({
type: 'sign-tx-error',
- error: error.message,
+ error: err.message,
}),
);
}
@@ -289,7 +337,9 @@ export const useCardanoStaking = (): CardanoStaking => {
);
const delegate = useCallback(() => action('delegate'), [action]);
- const withdraw = useCallback(() => action('withdrawal'), [action]);
+ const withdrawal = useCallback(() => action('withdrawal'), [action]);
+ const voteAbstain = useCallback(() => action('voteAbstain'), [action]);
+ const voteDelegate = useCallback(() => action('voteDelegate'), [action]);
return {
deposit,
@@ -298,6 +348,7 @@ export const useCardanoStaking = (): CardanoStaking => {
pendingStakeTx,
deviceAvailable: getDeviceAvailability(device, isDeviceLocked),
delegatingAvailable,
+ alreadyVoted,
withdrawingAvailable,
registeredPoolId,
isActive: isStakingActive,
@@ -307,7 +358,11 @@ export const useCardanoStaking = (): CardanoStaking => {
isStakingOnTrezorPool,
isCurrentPoolOversaturated,
delegate,
- withdraw,
+ withdrawal,
+ voteAbstain,
+ voteDelegate,
+ trezorDRep,
+ accountDRepHex: accountDRep?.hex,
calculateFeeAndDeposit,
trezorPools,
error,
diff --git a/packages/suite/src/reducers/wallet/__tests__/cardanoStakingReducer.test.ts b/packages/suite/src/reducers/wallet/__tests__/cardanoStakingReducer.test.ts
index bb2ba9b7156..90518c3b7de 100644
--- a/packages/suite/src/reducers/wallet/__tests__/cardanoStakingReducer.test.ts
+++ b/packages/suite/src/reducers/wallet/__tests__/cardanoStakingReducer.test.ts
@@ -58,10 +58,12 @@ describe('cardanoStakingReducer reducer', () => {
pools: [],
next: { hex: 'a', bech32: 'b', live_stake: 'a', saturation: 'a' },
},
+ trezorDRep: undefined,
isFetchError: false,
isFetchLoading: false,
},
preview: {
+ trezorDRep: undefined,
trezorPools: {
pools: [],
next: { hex: 'a', bech32: 'b', live_stake: 'a', saturation: 'a' },
@@ -162,10 +164,10 @@ describe('cardanoStakingReducer reducer', () => {
});
});
- it('CARDANO_STAKING.SET_TREZOR_POOLS mainnet', () => {
+ it('CARDANO_STAKING.SET_TREZOR_DATA mainnet', () => {
expect(
reducer(undefined, {
- type: CARDANO_STAKING.SET_TREZOR_POOLS,
+ type: CARDANO_STAKING.SET_TREZOR_DATA,
network: 'mainnet',
trezorPools: {
next: {
@@ -229,7 +231,7 @@ describe('cardanoStakingReducer reducer', () => {
it('CARDANO_STAKING.SET_TREZOR_POOLS preview', () => {
expect(
reducer(undefined, {
- type: CARDANO_STAKING.SET_TREZOR_POOLS,
+ type: CARDANO_STAKING.SET_TREZOR_DATA,
network: 'preview',
trezorPools: {
next: {
diff --git a/packages/suite/src/reducers/wallet/cardanoStakingReducer.ts b/packages/suite/src/reducers/wallet/cardanoStakingReducer.ts
index 921aede68e8..cdd200d54d0 100644
--- a/packages/suite/src/reducers/wallet/cardanoStakingReducer.ts
+++ b/packages/suite/src/reducers/wallet/cardanoStakingReducer.ts
@@ -4,17 +4,24 @@ import { BigNumber } from '@trezor/utils/src/bigNumber';
import { CARDANO_STAKING } from 'src/actions/wallet/constants';
import { WalletAction } from 'src/types/wallet';
-import { CardanoNetwork, PendingStakeTx, PoolsResponse } from 'src/types/wallet/cardanoStaking';
+import {
+ CardanoNetwork,
+ DRepResponse,
+ PendingStakeTx,
+ PoolsResponse,
+} from 'src/types/wallet/cardanoStaking';
export interface State {
pendingTx: PendingStakeTx[];
mainnet: {
trezorPools: PoolsResponse | undefined;
+ trezorDRep: DRepResponse | undefined;
isFetchLoading: boolean;
isFetchError: boolean;
};
preview: {
trezorPools: PoolsResponse | undefined;
+ trezorDRep: DRepResponse | undefined;
isFetchLoading: boolean;
isFetchError: boolean;
};
@@ -24,11 +31,13 @@ export const initialState: State = {
pendingTx: [],
mainnet: {
trezorPools: undefined,
+ trezorDRep: undefined,
isFetchLoading: false,
isFetchError: false,
},
preview: {
trezorPools: undefined,
+ trezorDRep: undefined,
isFetchLoading: false,
isFetchError: false,
},
@@ -43,10 +52,16 @@ const remove = (state: State, accountKey: string) => {
state.pendingTx.splice(index, 1);
};
-const setTrezorPools = (state: State, trezorPools: PoolsResponse, network: CardanoNetwork) => {
+const setTrezorData = (
+ state: State,
+ trezorPools: PoolsResponse,
+ trezorDRep: DRepResponse,
+ network: CardanoNetwork,
+) => {
// sorted from least saturated to most
trezorPools.pools.sort((a, b) => new BigNumber(a.live_stake).comparedTo(b.live_stake));
state[network].trezorPools = trezorPools;
+ state[network].trezorDRep = trezorDRep;
};
const setLoading = (state: State, isLoading: boolean, network: CardanoNetwork) => {
@@ -64,8 +79,8 @@ const cardanoStakingReducer = (state: State = initialState, action: WalletAction
return add(draft, action.pendingStakeTx);
case CARDANO_STAKING.REMOVE_PENDING_STAKE_TX:
return remove(draft, action.accountKey);
- case CARDANO_STAKING.SET_TREZOR_POOLS:
- return setTrezorPools(draft, action.trezorPools, action.network);
+ case CARDANO_STAKING.SET_TREZOR_DATA:
+ return setTrezorData(draft, action.trezorPools, action.trezorDRep, action.network);
case CARDANO_STAKING.SET_FETCH_LOADING:
return setLoading(draft, action.loading, action.network);
case CARDANO_STAKING.SET_FETCH_ERROR:
diff --git a/packages/suite/src/support/extraDependencies.ts b/packages/suite/src/support/extraDependencies.ts
index 3f30bddb60f..257598af77f 100644
--- a/packages/suite/src/support/extraDependencies.ts
+++ b/packages/suite/src/support/extraDependencies.ts
@@ -60,7 +60,7 @@ const connectInitSettings = {
export const extraDependencies: ExtraDependencies = {
thunks: {
cardanoValidatePendingTxOnBlock: cardanoStakingActions.validatePendingTxOnBlock,
- cardanoFetchTrezorPools: cardanoStakingActions.fetchTrezorPools,
+ cardanoFetchTrezorData: cardanoStakingActions.fetchTrezorData,
initMetadata: metadataLabelingActions.init,
fetchAndSaveMetadata: metadataLabelingActions.fetchAndSaveMetadata,
addAccountMetadata: metadataLabelingActions.addAccountMetadata,
diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts
index a58109ce8d2..ff52d7522d8 100644
--- a/packages/suite/src/support/messages.ts
+++ b/packages/suite/src/support/messages.ts
@@ -7521,6 +7521,26 @@ export default defineMessages({
id: 'TR_CARDANO_FINGERPRINT_HEADLINE',
defaultMessage: 'Fingerprint',
},
+ TR_CARDANO_WITHDRAW_MODAL_TITLE: {
+ id: 'TR_CARDANO_WITHDRAW_MODAL_TITLE',
+ defaultMessage: 'Delegate voting rights',
+ },
+ TR_CARDANO_WITHDRAW_MODAL_TITLE_DESCRIPTION: {
+ id: 'TR_CARDANO_WITHDRAW_MODAL_TITLE_DESCRIPTION',
+ defaultMessage: `When withdrawing your rewards, you can choose to support the Cardano ecosystem by delegating your community voting rights. Your votes will be delegated to 5Binaries, the operators of Cardano staking in Trezor Suite. This helps strengthen the network's resilience, sustainability, and community-driven governance. If you prefer, you can easily opt out of governance.`,
+ },
+ TR_CARDANO_WITHDRAW_MODAL_SUB_TITLE: {
+ id: 'TR_CARDANO_WITHDRAW_MODAL_SUB_TITLE',
+ defaultMessage: 'Delegate Representative (DRep)',
+ },
+ TR_CARDANO_WITHDRAW_MODAL_BUTTON_ABSTAIN: {
+ id: 'TR_CARDANO_WITHDRAW_MODAL_BUTTON_ABSTAIN',
+ defaultMessage: 'Opt Out',
+ },
+ TR_CARDANO_WITHDRAW_MODAL_BUTTON_DELEGATE: {
+ id: 'TR_CARDANO_WITHDRAW_MODAL_BUTTON_DELEGATE',
+ defaultMessage: 'Delegate',
+ },
TR_EXCEEDS_MAX: {
id: 'TR_EXCEEDS_MAX',
defaultMessage: 'Exceeds max length',
diff --git a/packages/suite/src/types/wallet/cardanoStaking.ts b/packages/suite/src/types/wallet/cardanoStaking.ts
index bc7a81d0d53..9dcb80220b5 100644
--- a/packages/suite/src/types/wallet/cardanoStaking.ts
+++ b/packages/suite/src/types/wallet/cardanoStaking.ts
@@ -5,5 +5,6 @@ export type {
StakePool,
PoolsResponse,
ActionAvailability,
+ DRepResponse,
CardanoStaking,
} from '@suite-common/wallet-types';
diff --git a/packages/suite/src/views/wallet/staking/components/CardanoRewards.tsx b/packages/suite/src/views/wallet/staking/components/CardanoRewards.tsx
index 2d3d61b6b3e..53401c3a761 100644
--- a/packages/suite/src/views/wallet/staking/components/CardanoRewards.tsx
+++ b/packages/suite/src/views/wallet/staking/components/CardanoRewards.tsx
@@ -5,10 +5,12 @@ import { Card, Column, Icon } from '@trezor/components';
import { DeviceModelInternal } from '@trezor/connect';
import { spacings } from '@trezor/theme';
+import { useDispatch } from 'src/hooks/suite';
import { getReasonForDisabledAction, useCardanoStaking } from 'src/hooks/wallet/useCardanoStaking';
import { Translation } from 'src/components/suite/Translation';
import { Account } from 'src/types/wallet';
import { HiddenPlaceholder } from 'src/components/suite/HiddenPlaceholder';
+import { openModal } from 'src/actions/suite/modalActions';
import { DeviceButton } from './DeviceButton';
import {
@@ -33,14 +35,17 @@ export const CardanoRewards = ({ account, deviceModel }: CardanoRewardsProps) =>
const {
address,
rewards,
- withdraw,
calculateFeeAndDeposit,
loading,
+ withdrawal,
withdrawingAvailable,
deviceAvailable,
+ alreadyVoted,
pendingStakeTx,
} = useCardanoStaking();
+ const dispatch = useDispatch();
+
useEffect(() => {
calculateFeeAndDeposit('withdrawal');
}, [calculateFeeAndDeposit]);
@@ -102,7 +107,13 @@ export const CardanoRewards = ({ account, deviceModel }: CardanoRewardsProps) =>
isLoading={loading}
isDisabled={isRewardsWithdrawDisabled}
deviceModelInternal={deviceModel}
- onClick={withdraw}
+ onClick={() => {
+ if (alreadyVoted) {
+ withdrawal();
+ } else {
+ dispatch(openModal({ type: 'cardano-withdraw-modal' }));
+ }
+ }}
tooltipContent={
!reasonMessageId ||
(deviceAvailable.status && withdrawingAvailable.status) ? undefined : (
diff --git a/packages/urls/src/urls.ts b/packages/urls/src/urls.ts
index 5aad9f142a2..8f85f660813 100644
--- a/packages/urls/src/urls.ts
+++ b/packages/urls/src/urls.ts
@@ -122,6 +122,10 @@ export const CARDANO_STAKE_POOL_MAINNET_URL: Url =
'https://trezor-cardano-mainnet.blockfrost.io/api/v0/pools/';
export const CARDANO_STAKE_POOL_PREVIEW_URL: Url =
'https://trezor-cardano-preview.blockfrost.io/api/v0/pools/';
+export const CARDANO_MAINNET_DREP: Url =
+ 'https://trezor-cardano-mainnet.blockfrost.io/api/v0/dreps/';
+export const CARDANO_PREVIEW_DREP: Url =
+ 'https://trezor-cardano-preview.blockfrost.io/api/v0/dreps/';
export const CHROME_URL: Url = 'https://www.google.com/chrome/';
export const CHROME_UPDATE_URL: Url = 'https://support.google.com/chrome/answer/95414';
diff --git a/suite-common/redux-utils/src/extraDependenciesType.ts b/suite-common/redux-utils/src/extraDependenciesType.ts
index b388c6362ff..de596477c12 100644
--- a/suite-common/redux-utils/src/extraDependenciesType.ts
+++ b/suite-common/redux-utils/src/extraDependenciesType.ts
@@ -42,7 +42,7 @@ export type ExtraDependencies = {
block: BlockchainBlock;
timestamp: number;
}>;
- cardanoFetchTrezorPools: SuiteCompatibleThunk<'tADA' | 'ADA'>;
+ cardanoFetchTrezorData: SuiteCompatibleThunk<'tADA' | 'ADA'>;
initMetadata: SuiteCompatibleThunk;
fetchAndSaveMetadata: SuiteCompatibleThunk;
addAccountMetadata: SuiteCompatibleThunk<
diff --git a/suite-common/suite-types/src/modal.ts b/suite-common/suite-types/src/modal.ts
index a8f2e0a47b2..3302ebb8873 100644
--- a/suite-common/suite-types/src/modal.ts
+++ b/suite-common/suite-types/src/modal.ts
@@ -185,6 +185,9 @@ export type UserContextPayload =
| {
type: 'passphrase-mismatch-warning';
}
+ | {
+ type: 'cardano-withdraw-modal';
+ }
| {
type: 'connect-popup';
onConfirm: () => void;
diff --git a/suite-common/test-utils/src/extraDependenciesMock.ts b/suite-common/test-utils/src/extraDependenciesMock.ts
index e140e87bba6..d5e5c65750b 100644
--- a/suite-common/test-utils/src/extraDependenciesMock.ts
+++ b/suite-common/test-utils/src/extraDependenciesMock.ts
@@ -60,7 +60,7 @@ export const mockReducer = (name: string) => (state: any, action: any) => {
export const extraDependenciesMock: ExtraDependencies = {
thunks: {
cardanoValidatePendingTxOnBlock: mockThunk('validatePendingTxOnBlock'),
- cardanoFetchTrezorPools: mockThunk('fetchTrezorPools'),
+ cardanoFetchTrezorData: mockThunk('fetchTrezorData'),
fetchAndSaveMetadata: mockThunk('fetchAndSaveMetadata'),
initMetadata: mockThunk('initMetadata'),
addAccountMetadata: mockThunk('addAccountMetadata'),
diff --git a/suite-common/wallet-core/src/blockchain/blockchainMiddleware.ts b/suite-common/wallet-core/src/blockchain/blockchainMiddleware.ts
index db493842a48..8d2b7a0a479 100644
--- a/suite-common/wallet-core/src/blockchain/blockchainMiddleware.ts
+++ b/suite-common/wallet-core/src/blockchain/blockchainMiddleware.ts
@@ -16,7 +16,7 @@ export const prepareBlockchainMiddleware = createMiddlewareWithExtraDeps(
// propagate action to reducers
next(action);
- const { cardanoValidatePendingTxOnBlock, cardanoFetchTrezorPools } = extra.thunks;
+ const { cardanoValidatePendingTxOnBlock, cardanoFetchTrezorData } = extra.thunks;
switch (action.type) {
case TREZOR_CONNECT_BLOCKCHAIN_ACTIONS.CONNECT:
@@ -26,7 +26,7 @@ export const prepareBlockchainMiddleware = createMiddlewareWithExtraDeps(
// for cardano staking if applicable
if (['ADA', 'tADA'].includes(action.payload.coin.shortcut)) {
dispatch(
- cardanoFetchTrezorPools(action.payload.coin.shortcut as 'ADA' | 'tADA'),
+ cardanoFetchTrezorData(action.payload.coin.shortcut as 'ADA' | 'tADA'),
);
}
break;
diff --git a/suite-common/wallet-types/src/account.ts b/suite-common/wallet-types/src/account.ts
index 89583e9e8de..eea420bd38f 100644
--- a/suite-common/wallet-types/src/account.ts
+++ b/suite-common/wallet-types/src/account.ts
@@ -42,6 +42,14 @@ type AccountNetworkSpecific =
isActive: boolean;
rewards: string;
poolId: string | null;
+ drep: {
+ drep_id: string;
+ hex: string;
+ amount: string;
+ active: boolean;
+ active_epoch: number | null;
+ has_script: boolean;
+ } | null;
};
};
page: AccountInfo['page'];
diff --git a/suite-common/wallet-types/src/cardanoStaking.ts b/suite-common/wallet-types/src/cardanoStaking.ts
index b59f92c93df..8258ef8c925 100644
--- a/suite-common/wallet-types/src/cardanoStaking.ts
+++ b/suite-common/wallet-types/src/cardanoStaking.ts
@@ -16,6 +16,17 @@ export type PoolsResponse = {
pools: StakePool[];
};
+interface DRep {
+ hex: string;
+ bech32: string;
+}
+
+export interface DRepResponse {
+ [key: string]: DRep;
+}
+
+export type CardanoAction = 'delegate' | 'withdrawal' | 'voteDelegate' | 'voteAbstain';
+
export type ActionAvailability =
| { status: true; reason?: undefined }
| { status: false; reason: 'POOL_ID_FETCH_FAIL' | 'TX_NOT_FINAL' | 'UTXO_BALANCE_INSUFFICIENT' }
@@ -38,10 +49,15 @@ export type CardanoStaking = {
isFetchError: boolean;
isCurrentPoolOversaturated: boolean;
trezorPools: PoolsResponse | undefined;
+ trezorDRep?: DRepResponse;
+ accountDRepHex?: string;
isActive: boolean;
+ alreadyVoted: boolean;
rewards: string;
- delegate(): void;
- withdraw(): void;
- calculateFeeAndDeposit(action: 'delegate' | 'withdrawal'): void;
+ delegate: () => void;
+ withdrawal: () => void;
+ voteDelegate: () => void;
+ voteAbstain: () => void;
+ calculateFeeAndDeposit: (action: CardanoAction) => Promise;
error?: string;
};
diff --git a/suite-common/wallet-utils/src/__fixtures__/cardanoUtils.ts b/suite-common/wallet-utils/src/__fixtures__/cardanoUtils.ts
index 7abfd7a64e3..3ae4a3c515a 100644
--- a/suite-common/wallet-utils/src/__fixtures__/cardanoUtils.ts
+++ b/suite-common/wallet-utils/src/__fixtures__/cardanoUtils.ts
@@ -630,7 +630,7 @@ export const getVotingCertificates = [
stakingPath: 'path',
dRep: {
type: 0, //keyHash
- keyHash: 'hex',
+ hex: 'hex',
},
result: [
{
@@ -648,7 +648,7 @@ export const getVotingCertificates = [
stakingPath: 'path',
dRep: {
type: 1, // scriptHash
- keyHash: 'hex',
+ hex: 'hex',
},
result: [
{
diff --git a/suite-common/wallet-utils/src/accountUtils.ts b/suite-common/wallet-utils/src/accountUtils.ts
index 30d9854d822..d989d7fad02 100644
--- a/suite-common/wallet-utils/src/accountUtils.ts
+++ b/suite-common/wallet-utils/src/accountUtils.ts
@@ -855,6 +855,7 @@ export const getAccountSpecific = (accountInfo: Partial, networkTyp
isActive: misc && misc.staking ? misc.staking.isActive : false,
address: misc && misc.staking ? misc.staking.address : '',
poolId: misc && misc.staking ? misc.staking.poolId : null,
+ drep: misc && misc.staking ? misc.staking.drep : null,
},
},
marker: undefined,
diff --git a/suite-common/wallet-utils/src/cardanoUtils.ts b/suite-common/wallet-utils/src/cardanoUtils.ts
index 30559a27515..cf9e96f0424 100644
--- a/suite-common/wallet-utils/src/cardanoUtils.ts
+++ b/suite-common/wallet-utils/src/cardanoUtils.ts
@@ -41,6 +41,9 @@ export const getAddressType = () => PROTO.CardanoAddressType.BASE;
export const getNetworkId = (accountSymbol: Account['symbol']) =>
accountSymbol === 'ada' ? CARDANO.NETWORK_IDS.mainnet : CARDANO.NETWORK_IDS.testnet;
+export const getNetworkName = (accountSymbol: string): 'preview' | 'mainnet' =>
+ accountSymbol.toLowerCase() === 'ada' ? 'mainnet' : 'preview';
+
export const getUnusedChangeAddress = (account: Pick) => {
if (!account.addresses) return;
@@ -135,16 +138,15 @@ export const getDelegationCertificates = (
export const getVotingCertificates = (
stakingPath: string,
- dRep: { keyHash?: string; type: PROTO.CardanoDRepType },
+ dRep: { hex?: string; type: PROTO.CardanoDRepType },
) => {
const result: CardanoCertificate[] = [
{
type: PROTO.CardanoCertificateType.VOTE_DELEGATION,
path: stakingPath,
dRep: {
- keyHash: dRep.type === PROTO.CardanoDRepType.KEY_HASH ? dRep.keyHash : undefined,
- scriptHash:
- dRep.type === PROTO.CardanoDRepType.SCRIPT_HASH ? dRep.keyHash : undefined,
+ keyHash: dRep.type === PROTO.CardanoDRepType.KEY_HASH ? dRep.hex : undefined,
+ scriptHash: dRep.type === PROTO.CardanoDRepType.SCRIPT_HASH ? dRep.hex : undefined,
type: dRep.type,
},
},
diff --git a/suite-common/wallet-utils/src/index.ts b/suite-common/wallet-utils/src/index.ts
index 3222d029aeb..99629288387 100644
--- a/suite-common/wallet-utils/src/index.ts
+++ b/suite-common/wallet-utils/src/index.ts
@@ -1,7 +1,7 @@
export * from './accountUtils';
+export * from './cardanoUtils';
export * from './backendUtils';
export * from './balanceUtils';
-export * from './cardanoUtils';
export * from './csvParserUtils';
export * from './discreetModeUtils';
export * from './ethUtils';