Skip to content

Commit

Permalink
Merge branch 'main' into 29974-nft-width
Browse files Browse the repository at this point in the history
  • Loading branch information
darkwing authored Jan 30, 2025
2 parents cd3c5a4 + 538bbc9 commit 75468c2
Show file tree
Hide file tree
Showing 18 changed files with 274 additions and 33 deletions.
2 changes: 1 addition & 1 deletion shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const ETH_USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7';
export const METABRIDGE_ETHEREUM_ADDRESS =
'0x0439e60F02a8900a951603950d8D4527f400C3f1';
export const BRIDGE_QUOTE_MAX_ETA_SECONDS = 60 * 60; // 1 hour
export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.5; // if a quote returns in x times less return than the best quote, ignore it
export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.35; // if a quote returns in x times less return than the best quote, ignore it

export const BRIDGE_PREFERRED_GAS_ESTIMATE = 'high';
export const BRIDGE_DEFAULT_SLIPPAGE = 0.5;
Expand Down
1 change: 1 addition & 0 deletions shared/constants/swaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const SLIPPAGE_LOW_ERROR = 'slippage-low';
export const SLIPPAGE_NEGATIVE_ERROR = 'slippage-negative';

export const MAX_ALLOWED_SLIPPAGE = 15;
export const SWAPS_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.35;

// An address that the metaswap-api recognizes as the default token for the current network,
// in place of the token address that ERC-20 tokens have
Expand Down
2 changes: 1 addition & 1 deletion ui/components/app/assets/nfts/nft-details/nft-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ export default function NftDetails({ nft }: { nft: Nft }) {
<Box className="nft-details__nft-item">
<NftItem
src={nftItemSrc as string | undefined}
alt={image ? nftImageAlt : ''}
alt={nftImageAlt}
networkName={currentChain.nickname ?? ''}
networkSrc={currentChain.rpcPrefs?.imageUrl}
isIpfsURL={isIpfsURL}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default function NftFullImage() {
<Box>
<NftItem
src={isImageHosted ? image : nftImageURL}
alt={image ? nftImageAlt : ''}
alt={nftImageAlt}
name={name}
tokenId={tokenId}
networkName={currentChain.nickname ?? ''}
Expand Down
16 changes: 14 additions & 2 deletions ui/components/multichain/nft-item/nft-item.stories.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { getNftImageAlt } from '../../../helpers/utils/nfts';
import { NftItem } from '.';

export default {
Expand Down Expand Up @@ -27,7 +28,12 @@ export default {
},
},
args: {
alt: 'Join Archer and his 6,969 frens as they take a trip further down the rabbit hole in search of a world with vibrant art, great vibes, and psychedelic tales.',
alt: getNftImageAlt({
name: 'Monkey Trip #2422',
tokenId: '2422',
description:
'Join Archer and his 6,969 frens as they take a trip further down the rabbit hole in search of a world with vibrant art, great vibes, and psychedelic tales.',
}),
name: 'Monkey Trip #2422',
src: 'https://i.seadn.io/gcs/files/878e670c38e0f02e58bf730c51c30d0c.jpg',
networkName: 'Ethereum Mainnet',
Expand All @@ -37,5 +43,11 @@ export default {
};

export const DefaultStory = (args) => <NftItem {...args} />;

DefaultStory.storyName = 'Default';

export const NoImageStory = (args) => <NftItem {...args} />;
NoImageStory.storyName = 'No Image';
NoImageStory.args = {
...DefaultStory.args,
src: '',
};
8 changes: 4 additions & 4 deletions ui/ducks/bridge/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1183,7 +1183,7 @@ describe('Bridge selectors', () => {
).toStrictEqual(false);
});

it('should return isEstimatedReturnLow=true return value is 50% less than sent funds', () => {
it('should return isEstimatedReturnLow=true return value is less than 65% of sent funds', () => {
const state = createBridgeMockStore({
featureFlagOverrides: {
extensionConfig: {
Expand Down Expand Up @@ -1239,7 +1239,7 @@ describe('Bridge selectors', () => {
expect(result.isEstimatedReturnLow).toStrictEqual(true);
});

it('should return isEstimatedReturnLow=false when return value is more than 50% of sent funds', () => {
it('should return isEstimatedReturnLow=false when return value is more than 65% of sent funds', () => {
const state = createBridgeMockStore({
featureFlagOverrides: {
extensionConfig: {
Expand All @@ -1254,7 +1254,7 @@ describe('Bridge selectors', () => {
fromToken: { address: zeroAddress(), symbol: 'ETH' },
toToken: { address: zeroAddress(), symbol: 'TEST' },
fromTokenExchangeRate: 2524.25,
toTokenExchangeRate: 0.63,
toTokenExchangeRate: 0.95,
fromTokenInputValue: 1,
},
bridgeStateOverrides: {
Expand Down Expand Up @@ -1292,7 +1292,7 @@ describe('Bridge selectors', () => {
expect(
getBridgeQuotes(state as never).activeQuote?.adjustedReturn
.valueInCurrency,
).toStrictEqual(new BigNumber('12.87194306627291988'));
).toStrictEqual(new BigNumber('20.69239170627291988'));
expect(result.isEstimatedReturnLow).toStrictEqual(false);
});

Expand Down
2 changes: 1 addition & 1 deletion ui/ducks/bridge/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ export const getValidationErrors = createDeepEqualSelector(
fromTokenInputValue
? activeQuote.adjustedReturn.valueInCurrency.lt(
new BigNumber(
BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE,
1 - BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE,
).times(activeQuote.sentAmount.valueInCurrency),
)
: false,
Expand Down
54 changes: 54 additions & 0 deletions ui/ducks/swaps/swaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import {
SWAPS_FETCH_ORDER_CONFLICT,
ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS,
Slippage,
SWAPS_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE,
} from '../../../shared/constants/swaps';
import {
IN_PROGRESS_TRANSACTION_STATUSES,
Expand All @@ -100,6 +101,7 @@ import {
import { EtherDenomination } from '../../../shared/constants/common';
import { Numeric } from '../../../shared/modules/Numeric';
import { calculateMaxGasLimit } from '../../../shared/lib/swaps-utils';
import { useTokenFiatAmount } from '../../hooks/useTokenFiatAmount';

const debugLog = createProjectLogger('swaps');

Expand Down Expand Up @@ -1435,3 +1437,55 @@ export function cancelSwapsSmartTransaction(uuid) {
}
};
}

export const getIsEstimatedReturnLow = ({ usedQuote, rawNetworkFees }) => {
const sourceTokenAmount = calcTokenAmount(
usedQuote?.sourceAmount,
usedQuote?.sourceTokenInfo?.decimals,
);
// Disabled because it's not a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const sourceTokenFiatAmount = useTokenFiatAmount(
usedQuote?.sourceTokenInfo?.address,
sourceTokenAmount || 0,
usedQuote?.sourceTokenInfo?.symbol,
{
showFiat: true,
},
true,
null,
false,
);
const destinationTokenAmount = calcTokenAmount(
usedQuote?.destinationAmount,
usedQuote?.destinationTokenInfo?.decimals,
);
// Disabled because it's not a hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const destinationTokenFiatAmount = useTokenFiatAmount(
usedQuote?.destinationTokenInfo?.address,
destinationTokenAmount || 0,
usedQuote?.destinationTokenInfo?.symbol,
{
showFiat: true,
},
true,
null,
false,
);
const adjustedReturnValue =
destinationTokenFiatAmount && rawNetworkFees
? new BigNumber(destinationTokenFiatAmount).minus(
new BigNumber(rawNetworkFees),
)
: null;
const isEstimatedReturnLow =
sourceTokenFiatAmount && adjustedReturnValue
? adjustedReturnValue.lt(
new BigNumber(sourceTokenFiatAmount).times(
1 - SWAPS_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE,
),
)
: false;
return isEstimatedReturnLow;
};
29 changes: 28 additions & 1 deletion ui/helpers/utils/nfts.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
const NFT_ALT_TEXT_MAX_LENGTH = 100;

export const nftTruncateAltText = (text, maxLength) => {
// if the text is shorter than or equal to maxLength, return it
if (text.length <= maxLength) {
return text;
}

const truncated = text.substring(0, maxLength);
const lastSpaceIndex = truncated.lastIndexOf(' ');

// If there's a space within the truncated text, cut at the last space
if (lastSpaceIndex > 0) {
return `${truncated.substring(0, lastSpaceIndex)}...`;
}

// If no space is found, return the truncated text with ellipsis
return `${truncated}...`;
};

export const getNftImageAlt = ({ name, tokenId, description }) => {
return description ?? `${name} ${tokenId}`;
// If there is no name, tokenId, or description, return an empty string
if (!name && !tokenId && !description) {
return '';
}

// if name or tokenId is undefined, don't include them in the alt text
const altText = description ?? `${name ?? ''} ${tokenId ?? ''}`.trim();
return nftTruncateAltText(altText, NFT_ALT_TEXT_MAX_LENGTH);
};
37 changes: 36 additions & 1 deletion ui/helpers/utils/nfts.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNftImageAlt } from './nfts';
import { getNftImageAlt, nftTruncateAltText } from './nfts';

describe('NFTs Utils', () => {
describe('getNftImageAlt', () => {
Expand Down Expand Up @@ -27,5 +27,40 @@ describe('NFTs Utils', () => {
}),
).toBe('Cool NFT 555');
});

it('returns an empty string when no name, tokenId, or description is provided', () => {
expect(
getNftImageAlt({
name: null,
tokenId: null,
description: null,
}),
).toBe('');
expect(getNftImageAlt({})).toBe('');
});
});

describe('nftTruncateAltText', () => {
it('returns the full text if it is shorter than or equal to maxLength', () => {
expect(nftTruncateAltText('Short text', 20)).toBe('Short text');
});

it('truncates the text and adds ellipsis if it is longer than maxLength', () => {
expect(
nftTruncateAltText('This is a long text that needs truncation', 20),
).toBe('This is a long text...');
});

it('truncates at the last space within maxLength if possible', () => {
expect(
nftTruncateAltText('This is a long text that needs truncation', 25),
).toBe('This is a long text that...');
});

it('truncates without cutting words if possible', () => {
expect(
nftTruncateAltText('This is a long text that needs truncation', 10),
).toBe('This is a...');
});
});
});
2 changes: 1 addition & 1 deletion ui/helpers/utils/token-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function getTokenFiatAmount(

currentTokenInFiat = currentTokenInFiat.round(2).toString();
let result;
if (hideCurrencySymbol) {
if (hideCurrencySymbol && formatted) {
result = formatCurrency(currentTokenInFiat, currentCurrency);
} else if (formatted) {
result = `${formatCurrency(
Expand Down
4 changes: 3 additions & 1 deletion ui/hooks/useTokenFiatAmount.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
* @param {boolean} hideCurrencySymbol - Indicates whether the returned formatted amount should include the trailing currency symbol
* @returns {string} The formatted token amount in the user's chosen fiat currency
* @param {string} [chainId] - The chain id
* @param {boolean} formatted - Whether the return value should be formatted or not
*/
export function useTokenFiatAmount(
tokenAddress,
Expand All @@ -36,6 +37,7 @@ export function useTokenFiatAmount(
overrides = {},
hideCurrencySymbol,
chainId = null,
formatted = true,
) {
const allMarketData = useSelector(getMarketData);

Expand Down Expand Up @@ -91,7 +93,7 @@ export function useTokenFiatAmount(
currentCurrency,
tokenAmount,
tokenSymbol,
true,
formatted,
hideCurrencySymbol,
),
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const NFTSendHeading = () => {
);
const imageOriginal = (nft as Nft | undefined)?.imageOriginal;
const image = (nft as Nft | undefined)?.image;
const nftImageAlt = nft && getNftImageAlt(nft);
const nftImageAlt = nft ? getNftImageAlt(nft) : '';
const nftSrcUrl = imageOriginal ?? (image || '');
const isIpfsURL = nftSrcUrl?.startsWith('ipfs:');
const currentChain = networkConfigurations[chainId];
Expand All @@ -56,7 +56,7 @@ const NFTSendHeading = () => {
<Box style={{ width: '48px' }}>
<NftItem
src={tokenImage}
alt={image && nftImageAlt ? nftImageAlt : ''}
alt={nftImageAlt}
name={assetName}
tokenId={assetTokenId || ''}
networkName={currentChain.name ?? ''}
Expand Down
25 changes: 19 additions & 6 deletions ui/pages/permissions-connect/connect-page/connect-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isEvmAccountType } from '@metamask/keyring-api';
import { NetworkConfiguration } from '@metamask/network-controller';
import { getEthAccounts, getPermittedEthChainIds } from '@metamask/multichain';
import { Hex } from '@metamask/utils';
import { isEqualCaseInsensitive } from '@metamask/controller-utils';
import { useI18nContext } from '../../../hooks/useI18nContext';
import {
getSelectedInternalAccount,
Expand Down Expand Up @@ -94,12 +95,18 @@ export const ConnectPage: React.FC<ConnectPageProps> = ({
);

const selectedNetworksList = selectedTestNetwork
? [...nonTestNetworks, selectedTestNetwork]
: nonTestNetworks;
? [...nonTestNetworks, selectedTestNetwork].map(({ chainId }) => chainId)
: nonTestNetworks.map(({ chainId }) => chainId);

const supportedRequestedChainIds = requestedChainIds.filter((chainId) =>
selectedNetworksList.includes(chainId),
);

const defaultSelectedChainIds =
requestedChainIds.length > 0
? requestedChainIds
: selectedNetworksList.map(({ chainId }) => chainId);
supportedRequestedChainIds.length > 0
? supportedRequestedChainIds
: selectedNetworksList;

const [selectedChainIds, setSelectedChainIds] = useState(
defaultSelectedChainIds,
);
Expand All @@ -111,12 +118,18 @@ export const ConnectPage: React.FC<ConnectPageProps> = ({
);
}, [accounts]);

const supportedRequestedAccounts = requestedAccounts.filter((account) =>
evmAccounts.find(({ address }) => isEqualCaseInsensitive(address, account)),
);

const currentAccount = useSelector(getSelectedInternalAccount);
const currentAccountAddress = isEvmAccountType(currentAccount.type)
? [currentAccount.address]
: []; // We do not support non-EVM accounts connections
const defaultAccountsAddresses =
requestedAccounts.length > 0 ? requestedAccounts : currentAccountAddress;
supportedRequestedAccounts.length > 0
? supportedRequestedAccounts
: currentAccountAddress;
const [selectedAccountAddresses, setSelectedAccountAddresses] = useState(
defaultAccountsAddresses,
);
Expand Down
Loading

0 comments on commit 75468c2

Please sign in to comment.