diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ChangeFee.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ChangeFee.tsx index 3ad6494275a..8ae7ec74120 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ChangeFee.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ChangeFee.tsx @@ -7,7 +7,7 @@ import { spacings } from '@trezor/theme'; import { Translation, FiatValue, FormattedCryptoAmount } from 'src/components/suite'; import { useSelector } from 'src/hooks/suite'; -import { useRbf, RbfContext, UseRbfProps } from 'src/hooks/wallet/useRbfForm'; +import { useRbfContext, UseRbfProps } from 'src/hooks/wallet/useRbfForm'; import { RbfFees } from './RbfFees'; import { AffectedTransactions } from './AffectedTransactions'; @@ -21,53 +21,53 @@ interface ChangeFeeProps extends UseRbfProps { } const ChangeFeeLoaded = (props: ChangeFeeProps) => { - const contextValues = useRbf(props); const { tx, showChained, children } = props; - const { networkType } = contextValues.account; + const { + account: { networkType }, + } = useRbfContext(); + const feeRate = networkType === 'bitcoin' ? `${tx.rbfParams?.feeRate} ${getFeeUnits(networkType)}` : null; const fee = formatNetworkAmount(tx.fee, tx.symbol); return ( - - - - -  ({feeRate}) - - } - typographyStyle="body" - > - - + + +  ({feeRate}) + + } + typographyStyle="body" + > + + + + - - - - - + + + - + - + - - + + - {children} - - + {children} + ); }; diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ReplaceTxButton.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ReplaceTxButton.tsx index af74735de9a..5a3d67b7062 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ReplaceTxButton.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ReplaceTxButton.tsx @@ -1,21 +1,13 @@ import { NewModal } from '@trezor/components'; -import { SelectedAccountLoaded, RbfTransactionParams } from '@suite-common/wallet-types'; import { Translation } from 'src/components/suite'; import { useDevice } from 'src/hooks/suite'; -import { useRbf } from 'src/hooks/wallet/useRbfForm'; +import { useRbfContext } from 'src/hooks/wallet/useRbfForm'; -type ReplaceTxButtonProps = { - rbfParams: RbfTransactionParams; - selectedAccount: SelectedAccountLoaded; -}; - -export const ReplaceTxButton = ({ rbfParams, selectedAccount }: ReplaceTxButtonProps) => { +export const ReplaceTxButton = () => { const { device, isLocked } = useDevice(); - const { isLoading, signTransaction, getValues, composedLevels } = useRbf({ - selectedAccount, - rbfParams, - }); + + const { isLoading, signTransaction, getValues, composedLevels } = useRbfContext(); const values = getValues(); const composedTx = composedLevels ? composedLevels[values.selectedFee || 'normal'] : undefined; diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModal.tsx index 62ad5ade280..f22ca36df8d 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModal.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModal.tsx @@ -1,25 +1,108 @@ import { useState, useMemo } from 'react'; -import { NewModal, Column, Banner } from '@trezor/components'; -import { HELP_CENTER_ZERO_VALUE_ATTACKS } from '@trezor/urls'; +import { NewModal } from '@trezor/components'; import { isPending, findChainedTransactions, getAccountKey } from '@suite-common/wallet-utils'; import { getNetwork } from '@suite-common/wallet-config'; import { selectAccountByKey, - selectTransactionConfirmations, selectAllPendingTransactions, selectIsPhishingTransaction, } from '@suite-common/wallet-core'; -import { spacings } from '@trezor/theme'; +import { + ChainedTransactions, + SelectedAccountLoaded, + WalletAccountTransactionWithRequiredRbfParams, +} from '@suite-common/wallet-types'; import { useSelector } from 'src/hooks/suite'; -import { Translation, TrezorLink } from 'src/components/suite'; +import { Translation } from 'src/components/suite'; import { Account, WalletAccountTransaction } from 'src/types/wallet'; +import { RbfContext, useRbf } from 'src/hooks/wallet/useRbfForm'; -import { BasicTxDetails } from './BasicTxDetails'; import { AdvancedTxDetails, TabID } from './AdvancedTxDetails/AdvancedTxDetails'; import { ChangeFee } from './ChangeFee/ChangeFee'; import { ReplaceTxButton } from './ChangeFee/ReplaceTxButton'; +import { TxDetailModalBase } from './TxDetailModalBase'; + +const hasRbfParams = ( + tx: WalletAccountTransaction, +): tx is WalletAccountTransactionWithRequiredRbfParams => tx.rbfParams !== undefined; + +type DetailModalProps = { + tx: WalletAccountTransaction; + onCancel: () => void; + tab: TabID | undefined; + onChangeFeeClick: () => void; + chainedTxs?: ChainedTransactions; +}; + +const DetailModal = ({ tx, onCancel, tab, onChangeFeeClick, chainedTxs }: DetailModalProps) => { + const accountKey = getAccountKey(tx.descriptor, tx.symbol, tx.deviceState); + const account = useSelector(state => selectAccountByKey(state, accountKey)) as Account; + const network = getNetwork(account.symbol); + const isPhishingTransaction = useSelector(state => + selectIsPhishingTransaction(state, tx.txid, accountKey), + ); + const blockchain = useSelector(state => state.wallet.blockchain[tx.symbol]); + + return ( + } + bottomContent={ + + + + } + onBackClick={undefined} + > + + + ); +}; + +type BumpFeeModalProps = { + tx: WalletAccountTransactionWithRequiredRbfParams; + onCancel: () => void; + onBackClick: () => void; + onShowChained: () => void; + chainedTxs?: ChainedTransactions; + selectedAccount: SelectedAccountLoaded; +}; + +const BumpFeeModal = ({ + tx, + onCancel, + onBackClick, + onShowChained, + chainedTxs, + selectedAccount, +}: BumpFeeModalProps) => { + const contextValues = useRbf({ rbfParams: tx.rbfParams, chainedTxs, selectedAccount }); + + return ( + + } + bottomContent={} + onBackClick={onBackClick} + > + + + + ); +}; type TxDetailModalProps = { tx: WalletAccountTransaction; @@ -28,14 +111,18 @@ type TxDetailModalProps = { }; export const TxDetailModal = ({ tx, rbfForm, onCancel }: TxDetailModalProps) => { - const blockchain = useSelector(state => state.wallet.blockchain[tx.symbol]); - const transactions = useSelector(selectAllPendingTransactions); - const [section, setSection] = useState<'CHANGE_FEE' | 'DETAILS'>( rbfForm ? 'CHANGE_FEE' : 'DETAILS', ); const [tab, setTab] = useState(undefined); + const accountKey = getAccountKey(tx.descriptor, tx.symbol, tx.deviceState); + const account = useSelector(state => selectAccountByKey(state, accountKey)) as Account; + const network = getNetwork(account.symbol); + const networkFeatures = network.accountTypes[account.accountType]?.features ?? network.features; + const selectedAccount = useSelector(state => state.wallet.selectedAccount); + + const transactions = useSelector(selectAllPendingTransactions); // const confirmations = getConfirmations(tx, blockchain.blockHeight); // TODO: replace this part will be refactored after blockbook implementation: // https://github.com/trezor/blockbook/issues/555 @@ -44,114 +131,48 @@ export const TxDetailModal = ({ tx, rbfForm, onCancel }: TxDetailModalProps) => return findChainedTransactions(tx.descriptor, tx.txid, transactions); }, [tx, transactions]); - const accountKey = getAccountKey(tx.descriptor, tx.symbol, tx.deviceState); - const confirmations = useSelector(state => - selectTransactionConfirmations(state, tx.txid, accountKey), - ); - const account = useSelector(state => selectAccountByKey(state, accountKey)) as Account; - const selectedAccount = useSelector(state => state.wallet.selectedAccount); - const network = getNetwork(account.symbol); - const networkFeatures = network.accountTypes[account.accountType]?.features ?? network.features; - - const isPhishingTransaction = useSelector(state => - selectIsPhishingTransaction(state, tx.txid, accountKey), - ); const onBackClick = () => { setSection('DETAILS'); setTab(undefined); }; - const getBottomContent = () => { - if ( - networkFeatures?.includes('rbf') && - tx.rbfParams && - !tx.deadline && - selectedAccount.status === 'loaded' - ) { - if (section === 'CHANGE_FEE') { - return ( - - ); - } else { - return ( - { - setSection('CHANGE_FEE'); - setTab(undefined); - }} - > - - - ); - } - } + const onShowChained = () => { + setSection('DETAILS'); + setTab('chained'); }; + const onChangeFeeClick = () => { + setSection('CHANGE_FEE'); + setTab(undefined); + }; + + if ( + hasRbfParams(tx) && + section === 'CHANGE_FEE' && + networkFeatures?.includes('rbf') && + !tx.deadline && + selectedAccount.status === 'loaded' + ) { + return ( + + ); + } + return ( - - ) : ( - - ) - } - size="large" - bottomContent={getBottomContent()} - onBackClick={section === 'CHANGE_FEE' ? onBackClick : undefined} - > - - - - {isPhishingTransaction && ( - - ( - - {chunks} - - ), - }} - /> - - )} - - {section === 'CHANGE_FEE' ? ( - { - setSection('DETAILS'); - setTab('chained'); - }} - /> - ) : ( - - )} - - + tab={tab} + onChangeFeeClick={onChangeFeeClick} + chainedTxs={chainedTxs} + /> ); }; diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModalBase.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModalBase.tsx new file mode 100644 index 00000000000..1466d3a1c68 --- /dev/null +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/TxDetailModalBase.tsx @@ -0,0 +1,90 @@ +import { ReactNode } from 'react'; + +import { NewModal, Column, Banner } from '@trezor/components'; +import { HELP_CENTER_ZERO_VALUE_ATTACKS } from '@trezor/urls'; +import { getAccountKey } from '@suite-common/wallet-utils'; +import { getNetwork } from '@suite-common/wallet-config'; +import { + selectAccountByKey, + selectTransactionConfirmations, + selectIsPhishingTransaction, +} from '@suite-common/wallet-core'; +import { spacings } from '@trezor/theme'; + +import { useSelector } from 'src/hooks/suite'; +import { Translation, TrezorLink } from 'src/components/suite'; +import { Account, WalletAccountTransaction } from 'src/types/wallet'; + +import { BasicTxDetails } from './BasicTxDetails'; + +type TxDetailModalProps = { + tx: WalletAccountTransaction; + onCancel: () => void; + onBackClick: (() => void) | undefined; + heading: ReactNode; + bottomContent: ReactNode | undefined; + children: ReactNode; +}; + +export const TxDetailModalBase = ({ + tx, + onCancel, + onBackClick, + heading, + bottomContent, + children, +}: TxDetailModalProps) => { + const blockchain = useSelector(state => state.wallet.blockchain[tx.symbol]); + + const accountKey = getAccountKey(tx.descriptor, tx.symbol, tx.deviceState); + const confirmations = useSelector(state => + selectTransactionConfirmations(state, tx.txid, accountKey), + ); + const account = useSelector(state => selectAccountByKey(state, accountKey)) as Account; + const network = getNetwork(account.symbol); + + const isPhishingTransaction = useSelector(state => + selectIsPhishingTransaction(state, tx.txid, accountKey), + ); + + return ( + + + + + {isPhishingTransaction && ( + + ( + + {chunks} + + ), + }} + /> + + )} + + {children} + + + ); +}; diff --git a/packages/suite/src/hooks/wallet/__fixtures__/useRbfForm.ts b/packages/suite/src/hooks/wallet/__fixtures__/useRbfForm.ts index 1d718b927ea..c39c9006352 100644 --- a/packages/suite/src/hooks/wallet/__fixtures__/useRbfForm.ts +++ b/packages/suite/src/hooks/wallet/__fixtures__/useRbfForm.ts @@ -1,3 +1,10 @@ +import { + ChainedTransactions, + WalletAccountTransaction, + WalletAccountTransactionWithRequiredRbfParams, +} from '@suite-common/wallet-types'; +import { AccountUtxo } from '@trezor/connect'; + export { getRootReducer } from './useSendForm'; const ABCD = 'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd'; @@ -71,7 +78,41 @@ const BTC_CJ_ACCOUNT = { // // script_type: 'PAYTOADDRESS', // }, -const PREPARE_TX = (params = {}) => ({ +const txDummyData = { + deviceState: 'A@B:1', + descriptor: '', + type: 'sent', + txid: '', + amount: '', + fee: '', + targets: [], + tokens: [], + internalTransfers: [], + details: { + vin: [], + vout: [], + size: 0, + totalInput: '', + totalOutput: '', + }, +} satisfies Partial; + +// This type-magic here is for 2 reasons: +// +// 1. WalletAccountTransaction has rbfParams as optional, but we want to +// enforce it in this fixture as we test only this case here +// +// 2. We need to add `required` into AccountUtxo because this is then passed +// down into utxo-lib where it is present on ComposeInput. This is not +// ideal and pretty magic, maybe subject of future refactor. +// +type HackedTxType = WalletAccountTransactionWithRequiredRbfParams & { + rbfParams: WalletAccountTransactionWithRequiredRbfParams['rbfParams'] & { + utxo: Array; + }; +}; + +const PREPARE_TX = (params: Partial = {}): HackedTxType => ({ symbol: 'btc', rbfParams: { txid: 'ABCD', @@ -110,9 +151,21 @@ const PREPARE_TX = (params = {}) => ({ baseFee: 175, ...params, }, + ...txDummyData, }); -export const composeAndSign = [ +type ComposeAndSignFixture = { + description: string; + store: any; + tx: WalletAccountTransactionWithRequiredRbfParams; + composedLevels: any; + composeTransactionCalls: number; + chainedTxs?: ChainedTransactions; + signedTx?: any; + decreasedOutputs?: boolean | string; +}; + +export const composeAndSign: ComposeAndSignFixture[] = [ { description: 'change-output reduced by fee. outputs order not affected. change was at the end of original tx.', @@ -152,7 +205,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 2, + composeTransactionCalls: 1, signedTx: { outputs: [ { @@ -178,10 +231,10 @@ export const composeAndSign = [ }, }, chainedTxs: { - own: [{ txid: 'aaaa', fee: '500' }], + own: [{ symbol: 'btc', ...txDummyData, txid: 'aaaa', fee: '500' }], others: [ - { txid: 'bbbb', fee: '500' }, - { txid: 'cccc', fee: '5000' }, + { symbol: 'btc', ...txDummyData, txid: 'bbbb', fee: '500' }, + { symbol: 'btc', ...txDummyData, txid: 'cccc', fee: '5000' }, ], }, tx: PREPARE_TX({ @@ -207,7 +260,7 @@ export const composeAndSign = [ feePerByte: '34.34', // 3.79 (old) + 4 (new) + 26.55 for chainedTxs }, }, - composeTransactionCalls: 2, + composeTransactionCalls: 1, signedTx: { outputs: [ { @@ -218,7 +271,7 @@ export const composeAndSign = [ }, { address_n: [2147483692, 2147483648, 2147483648, 1, 0], - amount: '9239', + amount: '3239', orig_index: 1, orig_hash: 'ABCD', }, @@ -249,7 +302,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 2, + composeTransactionCalls: 1, signedTx: { outputs: [ { @@ -304,7 +357,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 2, + composeTransactionCalls: 1, signedTx: { outputs: [ // change-output is gone @@ -358,7 +411,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 4, // 1. normal fee, 2. custom fee + composeTransactionCalls: 2, // 1. normal fee, 2. custom fee signedTx: { outputs: [ // change-output is gone @@ -432,7 +485,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 2, + composeTransactionCalls: 1, signedTx: { outputs: [ { @@ -503,7 +556,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 4, // 1. normal fee, 2. custom fee + composeTransactionCalls: 2, // 1. normal fee, 2. custom fee signedTx: { inputs: [{ prev_hash: DCBA }, { prev_hash: ABCD }], outputs: [ @@ -578,7 +631,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 4, // 1. normal fee, 2. custom fee + composeTransactionCalls: 2, // 1. normal fee, 2. custom fee signedTx: { inputs: [{ prev_hash: DCBA }, { prev_hash: ABCD }], outputs: [ @@ -648,7 +701,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 6, // 1. normal fee, 2. custom fee, 3. send-max + composeTransactionCalls: 3, // 1. normal fee, 2. custom fee, 3. send-max decreasedOutputs: true, signedTx: { inputs: [{ prev_hash: DCBA }], @@ -695,7 +748,7 @@ export const composeAndSign = [ feeRate: '1.37', changeAddress: undefined, }), - composeTransactionCalls: 2, // 1. immediate send-max + composeTransactionCalls: 1, // 1. immediate send-max decreasedOutputs: true, composedLevels: { normal: { @@ -754,7 +807,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 6, // 1. normal fee, 2. custom fee, 3 send-max + composeTransactionCalls: 3, // 1. normal fee, 2. custom fee, 3 send-max decreasedOutputs: true, signedTx: { inputs: [{ prev_hash: DCBA }], @@ -816,7 +869,7 @@ export const composeAndSign = [ ], }, }, - composeTransactionCalls: 2, + composeTransactionCalls: 1, signedTx: { // outputs are restored outputs: [ @@ -878,7 +931,7 @@ export const composeAndSign = [ error: 'NOT-ENOUGH-FUNDS', }, }, - composeTransactionCalls: 8, // 1. normal fee, 2. custom fee, 3. send-max normal fee, 4. send-max custom fee + composeTransactionCalls: 4, // 1. normal fee, 2. custom fee, 3. send-max normal fee, 4. send-max custom fee // tx is not signed }, { @@ -943,7 +996,7 @@ export const composeAndSign = [ feePerByte: '15.33', // 11.33 (old) + 4 (new) }, }, - composeTransactionCalls: 2, // 1. immediate send-max + composeTransactionCalls: 1, // 1. immediate send-max decreasedOutputs: 'TR_NOT_ENOUGH_ANONYMIZED_FUNDS_RBF_WARNING', signedTx: { inputs: [{ prev_hash: DCBA }], @@ -1029,7 +1082,7 @@ export const composeAndSign = [ feePerByte: '15.33', // 11.33 (old) + 4 (new) }, }, - composeTransactionCalls: 2, // 1. immediate send-max + composeTransactionCalls: 1, // 1. immediate send-max decreasedOutputs: 'TR_UTXO_REGISTERED_IN_COINJOIN_RBF_WARNING', signedTx: { inputs: [{ prev_hash: DCBA }], diff --git a/packages/suite/src/hooks/wallet/__tests__/useRbfForm.test.tsx b/packages/suite/src/hooks/wallet/__tests__/useRbfForm.test.tsx index 657595f8bb9..a09c036d578 100644 --- a/packages/suite/src/hooks/wallet/__tests__/useRbfForm.test.tsx +++ b/packages/suite/src/hooks/wallet/__tests__/useRbfForm.test.tsx @@ -2,7 +2,6 @@ import { screen } from '@testing-library/react'; import TrezorConnect from '@trezor/connect'; import { configureMockStore, initPreloadedState } from '@suite-common/test-utils'; -import { SelectedAccountLoaded, RbfTransactionParams } from '@suite-common/wallet-types'; import { renderWithProviders, @@ -14,7 +13,7 @@ import { ChangeFee } from 'src/components/suite/modals/ReduxModal/UserContextMod import { ReplaceTxButton } from 'src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/ChangeFee/ReplaceTxButton'; import * as fixtures from '../__fixtures__/useRbfForm'; -import { useRbfContext } from '../useRbfForm'; +import { RbfContext, useRbf, useRbfContext } from '../useRbfForm'; // do not mock jest.unmock('@trezor/connect'); @@ -39,22 +38,30 @@ jest.mock('@trezor/blockchain-link', () => ({ default: class BlockchainLink { name = 'jest-mocked-module'; listeners: Record void> = {}; + constructor(args: any) { this.name = args.name; } + on(...args: any[]) { const [type, fn] = args; this.listeners[type] = fn; } + listenerCount() { return 0; } + connect() { return true; } + disconnect() {} + removeAllListeners() {} + dispose() {} + getInfo() { return { url: this, @@ -66,6 +73,7 @@ jest.mock('@trezor/blockchain-link', () => ({ blockHash: 'abcd', }; } + estimateFee(params: { blocks: number[] }) { return params.blocks.map(() => ({ feePerUnit: '-1' })); } @@ -73,6 +81,7 @@ jest.mock('@trezor/blockchain-link', () => ({ })); type RootReducerState = ReturnType>; + interface Args { send?: Partial; fees?: any; @@ -97,6 +106,7 @@ const initStore = ({ send, fees, selectedAccount, coinjoin }: Args = {}) => { interface TestCallback { getContextValues?: () => any; } + // component rendered inside of SendIndex // callback prop is an object passed from single test case // getContextValues returns actual state of SendFormContext @@ -130,17 +140,25 @@ describe('useRbfForm hook', () => { it(`composeAndSign: ${f.description}`, async () => { const store = initStore(f.store); const callback: TestCallback = {}; - const { unmount } = renderWithProviders( - store, - // @ts-expect-error f.tx is not exact - {}}> - - - , - ); + + const TestComponent = () => { + const contextValues = useRbf({ + rbfParams: f.tx.rbfParams, + chainedTxs: f.chainedTxs, + selectedAccount: f.store.selectedAccount, + }); + + return ( + + {}}> + + + + + ); + }; + + const { unmount } = renderWithProviders(store, ); const composeTransactionSpy = jest.spyOn(TrezorConnect, 'composeTransaction'); @@ -165,13 +183,11 @@ describe('useRbfForm hook', () => { expect(composedLevels).toMatchObject(f.composedLevels); // validate number of calls to '@trezor/connect' - if (typeof f.composeTransactionCalls === 'number') { - expect(composeTransactionSpy).toHaveBeenCalledTimes(f.composeTransactionCalls); - } + expect(composeTransactionSpy).toHaveBeenCalledTimes(f.composeTransactionCalls); - if (f.decreasedOutputs) { + if (f.decreasedOutputs !== undefined) { if (typeof f.decreasedOutputs === 'string') { - expect(() => screen.getByText(f.decreasedOutputs)).not.toThrow(); + expect(() => screen.getByText(f.decreasedOutputs as string)).not.toThrow(); } else { expect(() => findByTestId('@send/decreased-outputs')).not.toThrow(); } diff --git a/suite-common/wallet-types/src/transaction.ts b/suite-common/wallet-types/src/transaction.ts index 84e12810b44..b12313b90d0 100644 --- a/suite-common/wallet-types/src/transaction.ts +++ b/suite-common/wallet-types/src/transaction.ts @@ -15,6 +15,7 @@ import { } from '@trezor/connect'; import { Network, NetworkSymbol } from '@suite-common/wallet-config'; import { TranslationKey } from '@suite-common/intl-types'; +import { RequiredKey } from '@trezor/type-utils'; import { Account } from './account'; @@ -185,6 +186,11 @@ export interface WalletAccountTransaction extends AccountTransaction { deadline?: number; } +export type WalletAccountTransactionWithRequiredRbfParams = RequiredKey< + WalletAccountTransaction, + 'rbfParams' +>; + export interface ChainedTransactions { own: WalletAccountTransaction[]; others: WalletAccountTransaction[];