Skip to content

Commit

Permalink
Merge branch 'develop' into gh-project-check
Browse files Browse the repository at this point in the history
  • Loading branch information
HajekOndrej authored Dec 17, 2024
2 parents f168a2e + 0427f46 commit 88bc6a3
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 73 deletions.
2 changes: 1 addition & 1 deletion packages/components/src/components/form/BottomText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const BottomText = ({
(iconName && (
<Icon name={iconName} size="medium" variant={variant as IconVariant} />
))}
<Text variant={variant as TextVariant} typographyStyle="hint" as="div">
<Text variant={variant as TextVariant} typographyStyle="hint" as="div" flex="auto">
{children}
</Text>
</Row>
Expand Down
2 changes: 1 addition & 1 deletion packages/suite/src/components/wallet/Fees/CustomFee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const CustomFee = <TFieldValues extends FormState>({
feeLimitError?.message ? (
<InputError
message={feeLimitError?.message}
button={validationButtonProps}
buttonProps={validationButtonProps}
/>
) : null
}
Expand Down
24 changes: 12 additions & 12 deletions packages/suite/src/components/wallet/InputError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import { spacings } from '@trezor/theme';
import { LearnMoreButton } from '../suite/LearnMoreButton';

type ButtonProps = { onClick: MouseEventHandler<HTMLButtonElement>; text: string };
type LinkProps = { url: Url };

export type InputErrorProps = {
button?: ButtonProps | LinkProps;
buttonProps?: ButtonProps;
learnMoreUrl?: Url;
message?: string;
};

export const InputError = ({ button, message }: InputErrorProps) => (
export const InputError = ({ buttonProps, learnMoreUrl, message }: InputErrorProps) => (
<Row gap={spacings.xs} justifyContent="space-between" flex="1">
<Paragraph>{message}</Paragraph>
{button &&
('url' in button ? (
<LearnMoreButton url={button.url} />
) : (
<Button size="tiny" variant="tertiary" onClick={button.onClick} textWrap={false}>
{button.text}
</Button>
))}
<Row gap={spacings.xs}>
<Paragraph>{message}</Paragraph>
{learnMoreUrl && <LearnMoreButton url={learnMoreUrl} />}
</Row>
{buttonProps?.text && (
<Button size="tiny" variant="tertiary" onClick={buttonProps.onClick}>
{buttonProps.text}
</Button>
)}
</Row>
);
8 changes: 8 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2036,6 +2036,14 @@ export default defineMessages({
defaultMessage: 'Unable to verify address history. Check that the address is correct.',
id: 'TR_ETH_ADDRESS_CANT_VERIFY_HISTORY',
},
TR_EVM_ADDRESS_IS_CONTRACT: {
defaultMessage: 'You are sending funds to a contract address.',
id: 'TR_EVM_ADDRESS_IS_CONTRACT',
},
TR_I_UNDERSTAND_THE_RISK: {
defaultMessage: 'I understand',
id: 'TR_I_UNDERSTAND_THE_RISK',
},
TR_NEEDS_ATTENTION_BOOTLOADER: {
defaultMessage: 'Trezor is in Bootloader mode.',
id: 'TR_NEEDS_ATTENTION_BOOTLOADER',
Expand Down
152 changes: 93 additions & 59 deletions packages/suite/src/views/wallet/send/Outputs/Address.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';

import { checkAddressCheckSum, toChecksumAddress } from 'web3-utils';
import styled from 'styled-components';
Expand All @@ -16,11 +16,15 @@ import {
isTaprootAddress,
isBech32AddressUppercase,
getInputState,
checkIsAddressNotUsedNotChecksummed,
} from '@suite-common/wallet-utils';
import { getNetworkSymbolForProtocol } from '@suite-common/suite-utils';
import { HELP_CENTER_EVM_ADDRESS_CHECKSUM } from '@trezor/urls';
import { spacings } from '@trezor/theme';
import { CoinLogo } from '@trezor/product-components';
import {
HELP_CENTER_EVM_ADDRESS_CHECKSUM,
HELP_CENTER_EVM_SEND_TO_CONTRACT_URL,
} from '@trezor/urls';

import { scanOrRequestSendFormThunk } from 'src/actions/wallet/send/sendFormThunks';
import { useSendFormContext } from 'src/hooks/wallet';
Expand Down Expand Up @@ -59,6 +63,7 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
const [addressDeprecatedUrl, setAddressDeprecatedUrl] =
useState<ReturnType<typeof isAddressDeprecated>>(undefined);
const [hasAddressChecksummed, setHasAddressChecksummed] = useState<boolean | undefined>();
const contractAddressWarningDismissed = useRef(false);
const dispatch = useDispatch();
const { device } = useDevice();
const {
Expand All @@ -72,6 +77,7 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
metadataEnabled,
watch,
setDraftSaveRequest,
trigger,
} = useSendFormContext();
const { translationString } = useTranslation();
const { descriptor, networkType, symbol } = account;
Expand All @@ -89,9 +95,14 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
const options = getDefaultValue('options', []);
const broadcastEnabled = options.includes('broadcast');
const isOnline = useSelector(state => state.suite.online);

useEffect(() => {
contractAddressWarningDismissed.current = false;
}, [address]);

const getInputErrorState = () => {
if (hasAddressChecksummed) {
return 'primary';
if (hasAddressChecksummed && !addressError) {
return 'default';
}
if (addressError) {
return getInputState(addressError);
Expand Down Expand Up @@ -158,39 +169,56 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
}
}, [amountInputName, composeTransaction, dispatch, inputName, setValue, symbol]);

const getValidationButtonProps = (): InputErrorProps['button'] => {
const getInputErrorProps = (): {
learnMoreUrl?: InputErrorProps['learnMoreUrl'];
buttonProps?: InputErrorProps['buttonProps'];
} => {
switch (addressError?.type) {
case 'deprecated':
if (addressDeprecatedUrl) {
return {
learnMoreUrl: addressDeprecatedUrl ? URLS[addressDeprecatedUrl] : undefined,
};
case 'evmchecks':
if (!checkAddressCheckSum(address)) {
return {
url: URLS[addressDeprecatedUrl],
buttonProps: {
onClick: () => {
setValue(inputName, toChecksumAddress(address), {
shouldValidate: true,
});

setHasAddressChecksummed(true);
},
text: translationString('TR_CONVERT_TO_CHECKSUM_ADDRESS'),
},
};
}
if (!contractAddressWarningDismissed.current) {
return {
buttonProps: {
onClick: () => {
contractAddressWarningDismissed.current = true;
trigger(inputName);
},
text: translationString('TR_I_UNDERSTAND_THE_RISK'),
},
learnMoreUrl: HELP_CENTER_EVM_SEND_TO_CONTRACT_URL,
};
}

return undefined;

case 'checksum':
return {
onClick: () => {
setValue(inputName, toChecksumAddress(address), {
shouldValidate: true,
});

setHasAddressChecksummed(true);
},
text: translationString('TR_CONVERT_TO_CHECKSUM_ADDRESS'),
};

return {};
case 'uppercase':
return {
onClick: () =>
setValue(inputName, address.toLowerCase(), {
shouldValidate: true,
}),
text: translationString('TR_CONVERT_TO_LOWERCASE'),
buttonProps: {
onClick: () =>
setValue(inputName, address.toLowerCase(), {
shouldValidate: true,
}),
text: translationString('TR_CONVERT_TO_LOWERCASE'),
},
};
default:
return undefined;
return {};
}
};

Expand Down Expand Up @@ -230,38 +258,46 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
return translationString('RECIPIENT_IS_NOT_VALID');
}
},
// Eth addresses are valid without checksum but Trezor displays them as checksummed.
checksum: async (address: string) => {
if (networkType === 'ethereum' && !checkAddressCheckSum(address)) {
if (isOnline) {
const params = {
descriptor: address,
coin: symbol,
};
// 1. If the address is used but unchecksummed, then Suite will automatically
// convert the address to the correct checksummed form and inform the user as described in the OP.
const result = await TrezorConnect.getAccountInfo(params);

if (result.success) {
const hasHistory = result.payload.history.total !== 0;
if (hasHistory) {
setValue(inputName, toChecksumAddress(address), {
shouldValidate: true,
});
setHasAddressChecksummed(true);
evmchecks: async (address: string) => {
if (networkType === 'ethereum') {
if (!isOnline) {
return translationString('TR_ETH_ADDRESS_CANT_VERIFY_HISTORY');
}
const params = {
descriptor: address,
coin: symbol,
};
const result = await TrezorConnect.getAccountInfo(params);

return;
}
if (!result.success) {
return translationString('TR_ETH_ADDRESS_CANT_VERIFY_HISTORY');
}

// 2. If the address is not checksummed at all and not found in blockbook
// offer to checksum it with a button.
if (!hasHistory && address === address.toLowerCase()) {
return translationString('TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED');
}
const { payload } = result;

// 1. Validate address checksum.
// Eth addresses are valid without checksum but Trezor displays them as checksummed.
if (!checkAddressCheckSum(address)) {
const checksumAndUsageValidationResult =
checkIsAddressNotUsedNotChecksummed(
address,
payload.history,
inputName,
setValue,
setHasAddressChecksummed,
);
if (checksumAndUsageValidationResult) {
return translationString('TR_ETH_ADDRESS_NOT_USED_NOT_CHECKSUMMED');
}
}

return translationString('TR_ETH_ADDRESS_CANT_VERIFY_HISTORY');
//2. Check if address is a contract address
if (!contractAddressWarningDismissed.current && symbol === 'eth') {
const isContract = payload.misc?.contractInfo;
if (isContract) {
return translationString('TR_EVM_ADDRESS_IS_CONTRACT');
}
}
}
},
rippleToSelf: (value: string) => {
Expand All @@ -284,7 +320,7 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
const addressBottomText = isAddressWithLabel ? addressLabelComponent : null;

const getBottomText = () => {
if (hasAddressChecksummed) {
if (hasAddressChecksummed && !addressError) {
return (
<Row width="100%" justifyContent="flex-start" gap={spacings.xs}>
<Translation
Expand All @@ -306,16 +342,14 @@ export const Address = ({ output, outputId, outputsCount }: AddressProps) => {
);
}
if (addressError) {
return (
<InputError message={addressError.message} button={getValidationButtonProps()} />
);
return <InputError message={addressError.message} {...getInputErrorProps()} />;
}

return addressBottomText;
};

const getBottomTextIconComponent = () => {
if (hasAddressChecksummed) {
if (hasAddressChecksummed && !addressError) {
return <Icon name="check" size="medium" variant="disabled" />;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/urls/src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export const HELP_CENTER_TRANSACTION_FEES_URL: Url =
'https://trezor.io/learn/a/transaction-fees-in-trezor-suite';
export const HELP_CENTER_EVM_ADDRESS_CHECKSUM: Url =
'https://trezor.io/learn/a/evm-address-checksum-in-trezor-suite';
export const HELP_CENTER_EVM_SEND_TO_CONTRACT_URL =
'https://trezor.io/support/a/where-is-my-ethereum';
export const HELP_CENTER_FIRMWARE_REVISION_CHECK: Url =
'https://trezor.io/learn/a/trezor-firmware-revision-check';
export const HELP_CENTER_REPLACE_BY_FEE: Url =
Expand Down
28 changes: 28 additions & 0 deletions suite-common/wallet-utils/src/validationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { UseFormSetValue } from 'react-hook-form';

import { toChecksumAddress } from 'web3-utils';

import addressValidator from '@trezor/address-validator';
import { getTestnetSymbols } from '@suite-common/wallet-config';
import { Account } from '@suite-common/wallet-types';
import { AccountInfo } from '@trezor/blockchain-link-types';

const getNetworkType = (symbol: Account['symbol']) => {
if (symbol === 'regtest') return symbol;
Expand Down Expand Up @@ -87,3 +92,26 @@ export const isHexValid = (value: string, prefix?: string) => {

return true;
};

export const checkIsAddressNotUsedNotChecksummed = (
address: string,
history: AccountInfo['history'],
inputName: string,
setValue: UseFormSetValue<any>,
setHasAddressChecksummed: (value: boolean) => void,
) => {
const hasHistory = history.total !== 0;

if (hasHistory) {
setValue(inputName, toChecksumAddress(address), { shouldValidate: true });
setHasAddressChecksummed(true);

return false;
}

if (!hasHistory && address === address.toLowerCase()) {
return true;
}

return false;
};

0 comments on commit 88bc6a3

Please sign in to comment.