Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/native/review and send #12968

Merged
merged 2 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { useCallback, useEffect, useRef } from 'react';
import { createDeferred } from '@trezor/utils';
import type { Timeout } from '@trezor/type-utils';

type AsyncFunction = (...args: any) => Promise<any>;
type SyncFunction = (...args: any) => any;

// composeTransaction should be debounced from both sides
// `timeout` prevents from calling '@trezor/connect' method to many times (inputs mad-clicking)
// TODO: maybe it should be converted to regular module, could be useful elsewhere
export const useAsyncDebounce = () => {
export const useDebounce = () => {
const timeout = useRef<Timeout | null>(null);

const debounce = useCallback(
async <F extends (...args: any) => Promise<any>>(fn: F): Promise<ReturnType<F>> => {
async <F extends AsyncFunction | SyncFunction>(fn: F): Promise<ReturnType<F>> => {
// clear previous timeout
if (timeout.current) clearTimeout(timeout.current);
// set new timeout
Expand Down
2 changes: 1 addition & 1 deletion packages/react-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { useAsyncDebounce } from './hooks/useAsyncDebounce';
export { useDebounce } from './hooks/useDebounce';
export { useDidUpdate } from './hooks/useDidUpdate';
export { useKeyPress } from './hooks/useKeyPress';
export { useOnce } from './hooks/useOnce';
Expand Down
9 changes: 3 additions & 6 deletions packages/suite/src/actions/wallet/moveLabelsForRbfActions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { selectLabelingDataForAccount } from '../../reducers/suite/metadataReducer';
import { selectLabelingDataForAccount } from 'src/reducers/suite/metadataReducer';
import { findChainedTransactions, findTransactions } from '@suite-common/wallet-utils';
import { Dispatch, GetState } from 'src/types/suite';
import * as metadataLabelingActions from 'src/actions/suite/metadataLabelingActions';
import { AccountLabels, AccountOutputLabels } from '@suite-common/metadata-types';
import {
AccountKey,
RbfLabelsToBeUpdated,
WalletAccountTransaction,
} from '@suite-common/wallet-types';
import { AccountKey, WalletAccountTransaction } from '@suite-common/wallet-types';
import { RbfLabelsToBeUpdated } from 'src/types/wallet/sendForm';

type DeleteAllOutputLabelsParams = {
labels: AccountLabels['outputLabels']['labels'];
Expand Down
2 changes: 1 addition & 1 deletion packages/suite/src/actions/wallet/send/sendFormThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
FormState,
GeneralPrecomposedTransactionFinal,
PrecomposedTransactionFinal,
RbfLabelsToBeUpdated,
} from '@suite-common/wallet-types';
import {
enhancePrecomposedTransactionThunk,
Expand All @@ -32,6 +31,7 @@ import { findLabelsToBeMovedOrDeleted, moveLabelsForRbfAction } from '../moveLab
import { selectMetadata } from 'src/reducers/suite/metadataReducer';
import * as metadataLabelingActions from 'src/actions/suite/metadataLabelingActions';
import * as modalActions from 'src/actions/suite/modalActions';
import { RbfLabelsToBeUpdated } from 'src/types/wallet/sendForm';

export const MODULE_PREFIX = '@send';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '@suite-common/wallet-utils';
import {
StakeFormState,
ComposeActionContext,
PrecomposedLevels,
PrecomposedTransaction,
PrecomposedTransactionFinal,
Expand All @@ -36,6 +35,7 @@ import {
import { Ethereum } from '@everstake/wallet-sdk';
import { MIN_ETH_FOR_WITHDRAWALS } from 'src/constants/suite/ethStaking';
import { NetworkSymbol } from '@suite-common/wallet-config';
import { ComposeActionContext } from '@suite-common/wallet-core';

const calculate = (
availableBalance: string,
Expand Down
8 changes: 2 additions & 6 deletions packages/suite/src/actions/wallet/stakeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ import {
replaceTransactionThunk,
syncAccountsWithBlockchainThunk,
stakeActions,
ComposeActionContext,
} from '@suite-common/wallet-core';
import { notificationsActions } from '@suite-common/toast-notifications';
import { formatNetworkAmount, tryGetAccountIdentity } from '@suite-common/wallet-utils';

import {
StakeFormState,
PrecomposedTransactionFinal,
ComposeActionContext,
StakeType,
} from '@suite-common/wallet-types';
import { StakeFormState, PrecomposedTransactionFinal, StakeType } from '@suite-common/wallet-types';

import * as modalActions from '../suite/modalActions';
import { Dispatch, GetState } from 'src/types/suite';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@ import { useState } from 'react';
import styled from 'styled-components';
import { ConfirmOnDevice, variables } from '@trezor/components';
import { Deferred } from '@trezor/utils';
import { selectDevice, StakeState } from '@suite-common/wallet-core';
import {
DeviceRootState,
selectDevice,
selectSendFormDraftByAccountKey,
selectSendFormReviewButtonRequestsCount,
selectStakePrecomposedForm,
StakeState,
} from '@suite-common/wallet-core';
import { FormState, StakeFormState } from '@suite-common/wallet-types';
import { isCardanoTx } from '@suite-common/wallet-utils';
import { constructTransactionReviewOutputs } from '@suite-common/wallet-utils';
import { SendState } from '@suite-common/wallet-core';
import { useSelector } from 'src/hooks/suite';
import { selectIsActionAbortable } from 'src/reducers/suite/suiteReducer';
import { constructOutputs } from 'src/utils/wallet/reviewTransactionUtils';
import { getTransactionReviewModalActionText } from 'src/utils/suite/transactionReview';
import { Modal, Translation } from 'src/components/suite';
import { TransactionReviewSummary } from './TransactionReviewSummary';
import { TransactionReviewOutputList } from './TransactionReviewOutputList/TransactionReviewOutputList';
import { TransactionReviewEvmExplanation } from './TransactionReviewEvmExplanation';
import { DeviceModelInternal } from '@trezor/connect';
import { spacings } from '@trezor/theme';
import { getTxStakeNameByDataHex } from '@suite-common/suite-utils';

Expand Down Expand Up @@ -61,27 +66,32 @@ export const TransactionReviewModalContent = ({
const { account } = selectedAccount;
const { precomposedTx, serializedTx } = txInfoState;

const precomposedForm = useSelector(state =>
isStakeState(txInfoState)
? selectStakePrecomposedForm(state)
: selectSendFormDraftByAccountKey(state, account?.key),
);

const decreaseOutputId = precomposedTx?.useNativeRbf
? precomposedForm?.setMaxOutputId
: undefined;

const buttonRequestsCount = useSelector((state: DeviceRootState) =>
selectSendFormReviewButtonRequestsCount(state, account?.symbol, decreaseOutputId),
);

if (!account) {
return null;
}

const precomposedForm: FormState | StakeFormState | undefined = isStakeState(txInfoState)
? txInfoState.precomposedForm
: txInfoState.drafts[account.key];

if (selectedAccount.status !== 'loaded' || !device || !precomposedTx || !precomposedForm) {
return null;
}

const { networkType } = account;
const isCardano = isCardanoTx(account, precomposedTx);
const isEthereum = networkType === 'ethereum';
const isRbfAction = !!precomposedTx.prevTxid;
const decreaseOutputId = precomposedTx.useNativeRbf
? precomposedForm.setMaxOutputId
: undefined;

const outputs = constructOutputs({
const outputs = constructTransactionReviewOutputs({
account,
decreaseOutputId,
device,
Expand All @@ -94,25 +104,6 @@ export const TransactionReviewModalContent = ({
? precomposedForm.ethereumStakeType
: getTxStakeNameByDataHex(outputs[0]?.value);

// omit other button requests (like passphrase)
const buttonRequests = device.buttonRequests.filter(
({ code }) =>
code === 'ButtonRequest_ConfirmOutput' ||
code === 'ButtonRequest_SignTx' ||
(code === 'ButtonRequest_Other' && (isCardano || isEthereum)), // Cardano and Ethereum are using ButtonRequest_Other
);

// NOTE: T1B1 edge-case
// while confirming decrease amount 'ButtonRequest_ConfirmOutput' is called twice (confirm decrease address, confirm decrease amount)
// remove 1 additional element to keep it consistent with T2T1 where this step is swipeable with one button request
if (
typeof decreaseOutputId === 'number' &&
deviceModelInternal === DeviceModelInternal.T1B1 &&
buttonRequests.filter(r => r.code === 'ButtonRequest_ConfirmOutput').length > 1
) {
buttonRequests.splice(-1, 1);
}

// get estimate mining time
let estimateTime;
const symbolFees = fees[selectedAccount.account.symbol];
Expand All @@ -124,8 +115,6 @@ export const TransactionReviewModalContent = ({
estimateTime = symbolFees.blockTime * matchedFeeLevel.blocks * 60;
}

const buttonRequestsCount = isCardano ? buttonRequests.length - 1 : buttonRequests.length;

const onCancel =
isActionAbortable || serializedTx
? () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Translation } from 'src/components/suite';
import { formatNetworkAmount, formatAmount, isTestnet } from '@suite-common/wallet-utils';
import { BTC_LOCKTIME_VALUE } from '@suite-common/wallet-constants';
import { Network, NetworkSymbol } from 'src/types/wallet';
import { ReviewOutput } from 'src/types/wallet/transaction';
import { ReviewOutput } from '@suite-common/wallet-types';
import {
TransactionReviewStepIndicator,
TransactionReviewStepIndicatorProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { TransactionReviewDetails } from './TransactionReviewDetails';
import { TransactionReviewOutput } from './TransactionReviewOutput';
import type { Account } from 'src/types/wallet';
import type { FormState, GeneralPrecomposedTransactionFinal } from '@suite-common/wallet-types';
import { getOutputState } from 'src/utils/wallet/reviewTransactionUtils';
import { getTransactionReviewOutputState } from '@suite-common/wallet-utils';
import { TransactionReviewTotalOutput } from './TransactionReviewTotalOutput';
import { ReviewOutput } from 'src/types/wallet/transaction';
import { ReviewOutput } from '@suite-common/wallet-types';
import { spacingsPx } from '@trezor/theme';
import { StakeFormState, StakeType } from '@suite-common/wallet-types';

Expand Down Expand Up @@ -184,13 +184,15 @@ export const TransactionReviewOutputList = ({
const totalRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const isLastStep = getOutputState(outputs.length, buttonRequestsCount) === 'active';
const isLastStep =
getTransactionReviewOutputState(outputs.length, buttonRequestsCount) === 'active';

if (isLastStep) {
totalRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
const activeIndex = outputs.findIndex(
(_, index) => getOutputState(index, buttonRequestsCount) === 'active',
(_, index) =>
getTransactionReviewOutputState(index, buttonRequestsCount) === 'active',
);

outputRefs.current[activeIndex]?.scrollIntoView({ behavior: 'smooth', block: 'start' });
Expand All @@ -208,7 +210,7 @@ export const TransactionReviewOutputList = ({
{outputs.map((output, index) => {
const state = signedTx
? 'success'
: getOutputState(index, buttonRequestsCount);
: getTransactionReviewOutputState(index, buttonRequestsCount);

return (
<TransactionReviewOutput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { TrezorDevice } from 'src/types/suite';
import { Translation } from 'src/components/suite/Translation';
import { useSelector } from 'src/hooks/suite/useSelector';
import {
getOutputState,
getTransactionReviewOutputState,
getIsUpdatedSendFlow,
getIsUpdatedEthereumSendFlow,
} from 'src/utils/wallet/reviewTransactionUtils';
} from '@suite-common/wallet-utils';
import { TransactionReviewStepIndicator } from './TransactionReviewStepIndicator';
import {
TransactionReviewOutputElement,
Expand All @@ -24,7 +24,9 @@ type StepIndicatorProps = Pick<
>;

const StepIndicator = ({ signedTx, outputs, buttonRequestsCount }: StepIndicatorProps) => {
const state = signedTx ? 'success' : getOutputState(outputs.length, buttonRequestsCount);
const state = signedTx
? 'success'
: getTransactionReviewOutputState(outputs.length, buttonRequestsCount);

return <TransactionReviewStepIndicator state={state} size={16} />;
};
Expand Down
3 changes: 1 addition & 2 deletions packages/suite/src/hooks/suite/useDisplayMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { AddressDisplayOptions } from '@suite-common/wallet-types';
import { selectAddressDisplayType } from 'src/reducers/suite/suiteReducer';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
import { useSelector } from './useSelector';
import { StakeType } from '@suite-common/wallet-types';
import { StakeType, ReviewOutput } from '@suite-common/wallet-types';
import { DisplayMode } from 'src/types/suite';
import { ReviewOutput } from '../../types/wallet/transaction';

type UseDisplayModeProps = {
type: ReviewOutput['type'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
UserAction,
actionSequence,
} from 'src/support/tests/hooksHelper';
import { FormState, SendContextValues } from '@suite-common/wallet-types';
import { FormState } from '@suite-common/wallet-types';
import SendIndex from 'src/views/wallet/send';

import * as fixtures from '../__fixtures__/useSendForm';
import { useSendFormContext } from '../useSendForm';
import { act, waitFor } from '@testing-library/react';
import { SendContextValues } from 'src/types/wallet/sendForm';

const TEST_TIMEOUT = 30000;

Expand Down
10 changes: 4 additions & 6 deletions packages/suite/src/hooks/wallet/form/useCompose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@ import { useEffect, useRef, useCallback, useState } from 'react';
import { FieldPath, UseFormReturn } from 'react-hook-form';

import { FeeLevel } from '@trezor/connect';
import { useAsyncDebounce } from '@trezor/react-utils';
import { useDebounce } from '@trezor/react-utils';
import { useDispatch, useSelector, useTranslation } from 'src/hooks/suite';
import { signAndPushSendFormTransactionThunk } from 'src/actions/wallet/send/sendFormThunks';
import { composeSendFormTransactionThunk } from '@suite-common/wallet-core';
import { ComposeActionContext, composeSendFormTransactionThunk } from '@suite-common/wallet-core';
import { findComposeErrors } from '@suite-common/wallet-utils';
import {
FormState,
UseSendFormState,
ComposeActionContext,
SendContextValues,
PrecomposedTransaction,
PrecomposedTransactionCardano,
PrecomposedLevels,
PrecomposedLevelsCardano,
} from '@suite-common/wallet-types';
import { COMPOSE_ERROR_TYPES } from '@suite-common/wallet-constants';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
import { SendContextValues, UseSendFormState } from '../../../types/wallet/sendForm';
PeKne marked this conversation as resolved.
Show resolved Hide resolved

const DEFAULT_FIELD = 'outputs.0.amount';

Expand Down Expand Up @@ -51,7 +49,7 @@ export const useCompose = <TFieldValues extends FormState>({
const dispatch = useDispatch();

// actions
const debounce = useAsyncDebounce();
const debounce = useDebounce();

// Type assertion allowing to make the hook reusable, see https://stackoverflow.com/a/73624072
// This allows the hook to set values and errors for fields shared among multiple forms without passing them as arguments.
Expand Down
2 changes: 1 addition & 1 deletion packages/suite/src/hooks/wallet/form/useFees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
FormState,
PrecomposedLevels,
PrecomposedLevelsCardano,
SendContextValues,
} from '@suite-common/wallet-types';
import { SendContextValues } from '../../../types/wallet/sendForm';

interface Props<TFieldValues extends FormState> extends UseFormReturn<TFieldValues> {
defaultValue?: FeeLevel['label'];
Expand Down
7 changes: 3 additions & 4 deletions packages/suite/src/hooks/wallet/form/useStakeCompose.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { useEffect, useRef, useCallback, useState } from 'react';
import { FieldPath, UseFormReturn } from 'react-hook-form';

import { useAsyncDebounce } from '@trezor/react-utils';
import { useDebounce } from '@trezor/react-utils';
import { useDispatch, useTranslation } from 'src/hooks/suite';
import { composeTransaction } from 'src/actions/wallet/stakeActions';
import { findComposeErrors } from '@suite-common/wallet-utils';
import {
StakeFormState,
StakeContextValues,
ComposeActionContext,
PrecomposedTransaction,
PrecomposedLevels,
} from '@suite-common/wallet-types';
import { COMPOSE_ERROR_TYPES } from '@suite-common/wallet-constants';
import { FeeLevel } from '@trezor/connect';
import { ComposeActionContext, StakeContextValues } from '@suite-common/wallet-core';

const DEFAULT_FIELD = 'outputs.0.amount';

Expand Down Expand Up @@ -41,7 +40,7 @@ export const useStakeCompose = <TFieldValues extends StakeFormState>({
const dispatch = useDispatch();

// actions
const debounce = useAsyncDebounce();
const debounce = useDebounce();

// Type assertion allowing to make the hook reusable, see https://stackoverflow.com/a/73624072
// This allows the hook to set values and errors for fields shared among multiple forms without passing them as arguments.
Expand Down
13 changes: 6 additions & 7 deletions packages/suite/src/hooks/wallet/form/useUtxoSelection.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useEffect, useMemo } from 'react';
import { UseFormReturn } from 'react-hook-form';

import {
UseSendFormState,
ExcludedUtxos,
UtxoSelectionContext,
SendContextValues,
FormState,
} from '@suite-common/wallet-types';
import { ExcludedUtxos, FormState } from '@suite-common/wallet-types';
import type { AccountUtxo, PROTO } from '@trezor/connect';
import { getUtxoOutpoint, isSameUtxo } from '@suite-common/wallet-utils';
import { useCoinjoinRegisteredUtxos } from './useCoinjoinRegisteredUtxos';
import {
SendContextValues,
UseSendFormState,
UtxoSelectionContext,
} from '../../../types/wallet/sendForm';

interface UtxoSelectionContextProps
extends UseFormReturn<FormState>,
Expand Down
Loading
Loading