diff --git a/packages/suite/src/actions/wallet/send/sendFormThunks.ts b/packages/suite/src/actions/wallet/send/sendFormThunks.ts index f7d80adc09b..34ad25cdecf 100644 --- a/packages/suite/src/actions/wallet/send/sendFormThunks.ts +++ b/packages/suite/src/actions/wallet/send/sendFormThunks.ts @@ -16,7 +16,7 @@ import { selectSendFormDrafts, signTransactionThunk, sendFormActions, - selectSendFormDraftByKey, + selectPrecomposedSendForm, } from '@suite-common/wallet-core'; import { isCardanoTx, isRbfTransaction } from '@suite-common/wallet-utils'; import { MetadataAddPayload } from '@suite-common/metadata-types'; @@ -36,7 +36,7 @@ import { findLabelsToBeMovedOrDeleted, moveLabelsForRbfAction } from '../moveLab export const MODULE_PREFIX = '@send'; export const saveSendFormDraftThunk = createThunk( - `${MODULE_PREFIX}/saveSendFormDraftThunk`, + `${MODULE_PREFIX}/ `, ({ formState }: { formState: FormState }, { dispatch, getState }) => { const selectedAccountKey = selectSelectedAccountKey(getState()); const isSelectedAccountLoaded = selectIsSelectedAccountLoaded(getState()); @@ -141,15 +141,14 @@ const applySendFormMetadataLabelsThunk = createThunk( if (!metadata.enabled) return; - const formDraft = selectSendFormDraftByKey(getState(), selectedAccount.key); - + const precomposedForm = selectPrecomposedSendForm(getState()); const outputsPermutation = isCardanoTx(selectedAccount, precomposedTransaction) ? precomposedTransaction?.outputs.map((_o, i) => i) // cardano preserves order of outputs : precomposedTransaction?.outputsPermutation; const synchronize = getSynchronize(); - formDraft?.outputs + precomposedForm?.outputs // create array of metadata objects .map((formOutput, index) => { const { label } = formOutput; diff --git a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx index 48a20baf901..c2d96157f4d 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewModalContent.tsx @@ -7,7 +7,7 @@ import { Deferred } from '@trezor/utils'; import { DeviceRootState, selectDevice, - selectSendFormDraftByKey, + selectPrecomposedSendForm, selectSendFormReviewButtonRequestsCount, selectStakePrecomposedForm, StakeState, @@ -76,7 +76,7 @@ export const TransactionReviewModalContent = ({ const precomposedForm = useSelector(state => isStakeState(txInfoState) ? selectStakePrecomposedForm(state) - : selectSendFormDraftByKey(state, account?.key), + : selectPrecomposedSendForm(state), ); const isRbfAction = precomposedTx !== undefined && isRbfTransaction(precomposedTx); diff --git a/packages/suite/src/reducers/wallet/__tests__/sendFormReducer.test.ts b/packages/suite/src/reducers/wallet/__tests__/sendFormReducer.test.ts index 9eb2adeac3e..3e94cae739b 100644 --- a/packages/suite/src/reducers/wallet/__tests__/sendFormReducer.test.ts +++ b/packages/suite/src/reducers/wallet/__tests__/sendFormReducer.test.ts @@ -68,6 +68,7 @@ describe('sendFormReducer', () => { it('SEND.REQUEST_SIGN_TRANSACTION - save', () => { const action: Action = sendFormActions.storePrecomposedTransaction({ + formState: formStateMock, precomposedTransaction: precomposedTxMock, }); @@ -94,12 +95,14 @@ describe('sendFormReducer', () => { { ...initialState, serializedTx: formSignedTxMock, + precomposedForm: formStateMock, precomposedTx: precomposedTxMock, }, action, ); expect(state.serializedTx).toBeUndefined(); expect(state.precomposedTx).toBeUndefined(); + expect(state.precomposedForm).toBeUndefined(); }); it('SEND.SEND_RAW', () => { @@ -120,12 +123,14 @@ describe('sendFormReducer', () => { ...initialState, sendRaw: true, precomposedTx: precomposedTxMock, + precomposedForm: formStateMock, serializedTx: formSignedTxMock, }, action, ); expect(state.sendRaw).toBeUndefined(); expect(state.precomposedTx).toBeUndefined(); + expect(state.precomposedForm).toBeUndefined(); expect(state.serializedTx).toBeUndefined(); }); }); diff --git a/suite-common/wallet-core/src/send/sendFormActions.ts b/suite-common/wallet-core/src/send/sendFormActions.ts index b98274f7dfd..c44e078a67b 100644 --- a/suite-common/wallet-core/src/send/sendFormActions.ts +++ b/suite-common/wallet-core/src/send/sendFormActions.ts @@ -27,7 +27,10 @@ const removeDraft = createAction( const storePrecomposedTransaction = createAction( `${SEND_MODULE_PREFIX}/store-precomposed-transaction`, - (payload: { precomposedTransaction: GeneralPrecomposedTransactionFinal }) => ({ + (payload: { + formState: FormState; + precomposedTransaction: GeneralPrecomposedTransactionFinal; + }) => ({ payload, }), ); diff --git a/suite-common/wallet-core/src/send/sendFormReducer.ts b/suite-common/wallet-core/src/send/sendFormReducer.ts index 52385050b20..7147b284aaf 100644 --- a/suite-common/wallet-core/src/send/sendFormReducer.ts +++ b/suite-common/wallet-core/src/send/sendFormReducer.ts @@ -30,8 +30,9 @@ export type SendState = { }; sendRaw?: boolean; precomposedTx?: GeneralPrecomposedTransactionFinal; + precomposedForm?: FormState; // Used to pass the form state to the review modal. Holds similar data as drafts, but drafts are not used in RBF form. signedTx?: BlockbookTransaction; - serializedTx?: SerializedTx; // hexadecimal representation of signed transaction (payload for TrezorConnect.pushTransaction) + serializedTx?: SerializedTx; // Hexadecimal representation of signed transaction (payload for TrezorConnect.pushTransaction). }; export const initialState: SendState = { @@ -66,8 +67,13 @@ export const prepareSendFormReducer = createReducerWithExtraDeps(initialState, ( }) .addCase( sendFormActions.storePrecomposedTransaction, - (state, { payload: { precomposedTransaction } }) => { + (state, { payload: { precomposedTransaction, formState } }) => { state.precomposedTx = precomposedTransaction; + // Deep-cloning to prevent buggy interaction between react-hook-form and immer, see https://github.com/orgs/react-hook-form/discussions/3715#discussioncomment-2151458 + // Otherwise, whenever the outputs fieldArray is updated after the form draft or precomposedForm is saved, there is na error: + // TypeError: Cannot assign to read only property of object '#' + // This might not be necessary in the future when the dependencies are upgraded. + state.precomposedForm = cloneObject(formState); }, ) .addCase( @@ -79,6 +85,7 @@ export const prepareSendFormReducer = createReducerWithExtraDeps(initialState, ( ) .addCase(sendFormActions.discardTransaction, state => { delete state.precomposedTx; + delete state.precomposedForm; delete state.serializedTx; delete state.signedTx; }) @@ -88,6 +95,7 @@ export const prepareSendFormReducer = createReducerWithExtraDeps(initialState, ( .addCase(sendFormActions.dispose, state => { delete state.sendRaw; delete state.precomposedTx; + delete state.precomposedForm; delete state.serializedTx; delete state.signedTx; }) @@ -103,6 +111,8 @@ export const selectSendPrecomposedTx = (state: SendRootState) => state.wallet.se export const selectSendSerializedTx = (state: SendRootState) => state.wallet.send.serializedTx; export const selectSendSignedTx = (state: SendRootState) => state.wallet.send.signedTx; export const selectSendFormDrafts = (state: SendRootState) => state.wallet.send.drafts; +export const selectPrecomposedSendForm = (state: SendRootState) => + state.wallet.send.precomposedForm; export const selectSendFormDraftByKey = ( state: SendRootState, diff --git a/suite-common/wallet-core/src/send/sendFormThunks.ts b/suite-common/wallet-core/src/send/sendFormThunks.ts index 2c5ed33cfc2..6929350af93 100644 --- a/suite-common/wallet-core/src/send/sendFormThunks.ts +++ b/suite-common/wallet-core/src/send/sendFormThunks.ts @@ -572,12 +572,10 @@ export const enhancePrecomposedTransactionThunk = createThunk< // store formValues and transactionInfo in send reducer to be used by TransactionReviewModal dispatch( sendFormActions.storePrecomposedTransaction({ + formState: formValues, precomposedTransaction: enhancedPrecomposedTransaction, }), ); - dispatch( - sendFormActions.storeDraft({ accountKey: selectedAccount.key, formState: formValues }), - ); return enhancedPrecomposedTransaction; },