Skip to content

Commit

Permalink
Merge branch 'main' into fix/remove-confirmation-decode-reliance
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewwalsh0 committed Dec 19, 2024
2 parents bdf0aeb + 22490c3 commit 738f6d7
Show file tree
Hide file tree
Showing 17 changed files with 233 additions and 67 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/wait-for-circleci-workflow-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ jobs:
OWNER: ${{ github.repository_owner }}
REPOSITORY: ${{ github.event.repository.name }}
BRANCH: ${{ github.head_ref || github.ref_name }}
# For a `push` event, the HEAD commit hash is `github.sha`.
# For a `pull_request` event, `github.sha` is instead the base branch commit hash. The
# HEAD commit hash is `pull_request.head.sha`.
HEAD_COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
pipeline_id=$(curl --silent "https://circleci.com/api/v2/project/gh/$OWNER/$REPOSITORY/pipeline?branch=$BRANCH" | jq -r ".items[0].id")
pipeline_id=$(curl --silent "https://circleci.com/api/v2/project/gh/$OWNER/$REPOSITORY/pipeline?branch=$BRANCH" | jq -r ".items | map(select(.vcs.revision == \"${HEAD_COMMIT_HASH}\" )) | first | .id")
echo "Waiting for pipeline '${pipeline_id}' for commit hash '${HEAD_COMMIT_HASH}'"
workflow_status=$(curl --silent "https://circleci.com/api/v2/pipeline/$pipeline_id/workflow" | jq -r ".items[0].status")
if [ "$workflow_status" == "running" ]; then
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,13 @@
"@metamask/scure-bip39": "^2.0.3",
"@metamask/selected-network-controller": "^19.0.0",
"@metamask/signature-controller": "^23.1.0",
"@metamask/smart-transactions-controller": "^15.0.0",
"@metamask/smart-transactions-controller": "^16.0.0",
"@metamask/snaps-controllers": "^9.15.0",
"@metamask/snaps-execution-environments": "^6.10.0",
"@metamask/snaps-rpc-methods": "^11.7.0",
"@metamask/snaps-sdk": "^6.13.0",
"@metamask/snaps-utils": "^8.6.1",
"@metamask/solana-wallet-snap": "^1.0.3",
"@metamask/solana-wallet-snap": "^1.0.4",
"@metamask/transaction-controller": "^42.0.0",
"@metamask/user-operation-controller": "^21.0.0",
"@metamask/utils": "^10.0.1",
Expand Down
2 changes: 2 additions & 0 deletions privacy-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"accounts.api.cx.metamask.io",
"acl.execution.metamask.io",
"api.blockchair.com",
"api.devnet.solana.com",
"api.lens.dev",
"api.segment.io",
"api.web3modal.com",
Expand Down Expand Up @@ -59,6 +60,7 @@
"sepolia.infura.io",
"signature-insights.api.cx.metamask.io",
"snaps.metamask.io",
"solana.rpc.grove.city",
"sourcify.dev",
"start.metamask.io",
"static.cx.metamask.io",
Expand Down
4 changes: 4 additions & 0 deletions shared/constants/metametrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,9 @@ export enum MetaMetricsEventName {
AppUnlockedFailed = 'App Unlocked Failed',
AppLocked = 'App Locked',
AppWindowExpanded = 'App Window Expanded',
BannerDisplay = 'Banner Display',
BannerCloseAll = 'Banner Close All',
BannerSelect = 'Banner Select',
BridgeLinkClicked = 'Bridge Link Clicked',
BitcoinSupportToggled = 'Bitcoin Support Toggled',
BitcoinTestnetSupportToggled = 'Bitcoin Testnet Support Toggled',
Expand Down Expand Up @@ -912,6 +915,7 @@ export enum MetaMetricsEventCategory {
App = 'App',
Auth = 'Auth',
Background = 'Background',
Banner = 'Banner',
// The TypeScript ESLint rule is incorrectly marking this line.
/* eslint-disable-next-line @typescript-eslint/no-shadow */
Error = 'Error',
Expand Down
1 change: 1 addition & 0 deletions ui/components/app/assets/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {
className=""
actionButtonOnClick={() => setShowDetectedTokens(true)}
margin={4}
marginBottom={1}
/>
) : null}
<AssetListControlBar showTokensLinks={shouldShowTokensLinks} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useContext, useState, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import { isEqual } from 'lodash';
Expand All @@ -16,6 +16,12 @@ import {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import useBridging from '../../../hooks/bridge/useBridging';
///: END:ONLY_INCLUDE_IF
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventName,
MetaMetricsEventCategory,
} from '../../../../shared/constants/metametrics';
import type { CarouselSlide } from '../../../../shared/constants/app-state';
import {
AccountOverviewTabsProps,
AccountOverviewTabs,
Expand All @@ -42,6 +48,8 @@ export const AccountOverviewLayout = ({
const slides = useSelector(getSlides);
const totalBalance = useSelector(getSelectedAccountCachedBalance);
const isLoading = useSelector(getAppIsLoading);
const trackEvent = useContext(MetaMetricsContext);
const [hasRendered, setHasRendered] = useState(false);

///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
Expand Down Expand Up @@ -76,35 +84,66 @@ export const AccountOverviewLayout = ({
dispatch(updateSlides(defaultSlides));
}, [hasZeroBalance]);

///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const handleCarouselClick = (id: string) => {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
if (id === 'bridge') {
openBridgeExperience(
'Carousel',
defaultSwapsToken,
location.pathname.includes('asset') ? '&token=native' : '',
);
}
///: END:ONLY_INCLUDE_IF

trackEvent({
event: MetaMetricsEventName.BannerSelect,
category: MetaMetricsEventCategory.Banner,
properties: {
banner_name: id,
},
});
};
///: END:ONLY_INCLUDE_IF

const handleRemoveSlide = (id: string) => {
const handleRemoveSlide = (isLastSlide: boolean, id: string) => {
if (id === 'fund' && hasZeroBalance) {
return;
}
if (isLastSlide) {
trackEvent({
event: MetaMetricsEventName.BannerCloseAll,
category: MetaMetricsEventCategory.Banner,
});
}
dispatch(removeSlide(id));
};

const handleRenderSlides = useCallback(
(renderedSlides: CarouselSlide[]) => {
if (!hasRendered) {
renderedSlides.forEach((slide) => {
trackEvent({
event: MetaMetricsEventName.BannerDisplay,
category: MetaMetricsEventCategory.Banner,
properties: {
banner_name: slide.id,
},
});
});
setHasRendered(true);
}
},
[hasRendered, trackEvent],
);

return (
<>
<div className="account-overview__balance-wrapper">{children}</div>
<Carousel
slides={slides}
isLoading={isLoading}
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
onClick={handleCarouselClick}
///: END:ONLY_INCLUDE_IF
onClose={handleRemoveSlide}
onRenderSlides={handleRenderSlides}
/>
<AccountOverviewTabs {...tabsProps}></AccountOverviewTabs>
</>
Expand Down
53 changes: 49 additions & 4 deletions ui/components/multichain/carousel/carousel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@ import { render, fireEvent } from '@testing-library/react';
import { Carousel } from './carousel';
import { MARGIN_VALUES, WIDTH_VALUES } from './constants';

jest.mock('react-responsive-carousel', () => ({
Carousel: ({
children,
onChange,
}: {
children: React.ReactNode;
onChange?: (index: number) => void;
}) => (
<div className="mock-carousel">
{children}
<div className="carousel-dots">
<button className="dot" onClick={() => onChange?.(1)} />
<button className="dot" onClick={() => onChange?.(0)} />
</div>
</div>
),
}));

jest.mock('react-redux', () => ({
useSelector: jest.fn(),
useDispatch: () => jest.fn(),
}));

jest.mock('reselect', () => ({
createSelector: jest.fn(),
createDeepEqualSelector: jest.fn(),
createSelectorCreator: jest.fn(() => jest.fn()),
lruMemoize: jest.fn(),
}));

jest.mock('../../../selectors/approvals', () => ({
selectPendingApproval: jest.fn(),
}));

jest.mock('../../../hooks/useI18nContext', () => ({
useI18nContext: () => (key: string) => key,
}));
Expand Down Expand Up @@ -46,13 +80,24 @@ describe('Carousel', () => {
expect(closeButtons).toHaveLength(2);

fireEvent.click(closeButtons[0]);
expect(mockOnClose).toHaveBeenCalledWith('1');
expect(mockOnClose).toHaveBeenCalledWith(false, '1');

const remainingSlides = mockSlides.filter((slide) => slide.id !== '1');
rerender(<Carousel slides={remainingSlides} onClose={mockOnClose} />);

const updatedSlides = container.querySelectorAll('.mm-carousel-slide');
expect(updatedSlides).toHaveLength(1);
const updatedCloseButtons = container.querySelectorAll(
'.mm-carousel-slide__close-button',
);
expect(updatedCloseButtons).toHaveLength(1);

fireEvent.click(updatedCloseButtons[0]);
expect(mockOnClose).toHaveBeenCalledWith(true, '2');

const finalSlides = remainingSlides.filter((slide) => slide.id !== '2');
rerender(<Carousel slides={finalSlides} onClose={mockOnClose} />);

const finalSlideElements = container.querySelectorAll('.mm-carousel-slide');
expect(finalSlideElements).toHaveLength(0);
});

it('should handle slide navigation', () => {
Expand All @@ -65,7 +110,7 @@ describe('Carousel', () => {
fireEvent.click(dots[1]);

const slides = container.querySelectorAll('.mm-carousel-slide');
expect(slides[1].parentElement).toHaveClass('selected');
expect(slides[1].parentElement).toHaveClass('mock-carousel');
});

it('should return null when no slides are present', () => {
Expand Down
16 changes: 14 additions & 2 deletions ui/components/multichain/carousel/carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Carousel as ResponsiveCarousel } from 'react-responsive-carousel';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { Box, BoxProps, BannerBase } from '../../component-library';
Expand All @@ -24,6 +24,7 @@ export const Carousel = React.forwardRef(
isLoading = false,
onClose,
onClick,
onRenderSlides,
...props
}: CarouselProps,
ref: React.Ref<HTMLDivElement>,
Expand All @@ -44,6 +45,17 @@ export const Carousel = React.forwardRef(
})
.slice(0, MAX_SLIDES);

useEffect(() => {
if (
visibleSlides &&
visibleSlides.length > 0 &&
onRenderSlides &&
!isLoading
) {
onRenderSlides(visibleSlides);
}
}, [visibleSlides, onRenderSlides, isLoading]);

const handleClose = (e: React.MouseEvent<HTMLElement>, slideId: string) => {
e.preventDefault();
e.stopPropagation();
Expand All @@ -65,7 +77,7 @@ export const Carousel = React.forwardRef(
setSelectedIndex(newSelectedIndex);

if (onClose) {
onClose(slideId);
onClose(visibleSlides.length === 1, slideId);
}
};

Expand Down
3 changes: 2 additions & 1 deletion ui/components/multichain/carousel/carousel.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CarouselSlide } from '../../../../shared/constants/app-state';
export type CarouselProps = {
slides: CarouselSlide[];
isLoading?: boolean;
onClose?: (id: string) => void;
onClose?: (isLastSlide: boolean, id: string) => void;
onClick?: (id: string) => void;
onRenderSlides?: (slides: CarouselSlide[]) => void;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import { useSelector } from 'react-redux';
import { calcTokenAmount } from '../../../../../../../../shared/lib/transactions-controller-utils';
import { getIntlLocale } from '../../../../../../../ducks/locale/locale';
import { formatAmount } from '../../../../simulation-details/formatAmount';
import { TOKEN_VALUE_UNLIMITED_THRESHOLD } from '../../shared/constants';
import { useTokenTransactionData } from '../../hooks/useTokenTransactionData';
import { useIsNFT } from './use-is-nft';

const UNLIMITED_THRESHOLD = 10 ** 15;

function isSpendingCapUnlimited(decodedSpendingCap: number) {
return decodedSpendingCap >= UNLIMITED_THRESHOLD;
return decodedSpendingCap >= TOKEN_VALUE_UNLIMITED_THRESHOLD;
}

export const useApproveTokenSimulation = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ exports[`NativeTransferInfo renders correctly 1`] = `
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xl mm-avatar-token mm-text--body-lg-medium mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-default mm-box--rounded-full"
>
G
E
</div>
<h2
class="mm-box mm-text mm-text--heading-lg mm-box--margin-top-3 mm-box--color-inherit"
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const HEX_ZERO = '0x0';

export const TOKEN_VALUE_UNLIMITED_THRESHOLD = 10 ** 15;
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { BigNumber } from 'bignumber.js';
import React from 'react';
import { useSelector } from 'react-redux';
import {
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP,
CHAIN_ID_TOKEN_IMAGE_MAP,
TEST_CHAINS,
} from '../../../../../../../../shared/constants/network';
import { calcTokenAmount } from '../../../../../../../../shared/lib/transactions-controller-utils';
import { getNetworkConfigurationsByChainId } from '../../../../../../../../shared/modules/selectors/networks';
import {
AvatarToken,
AvatarTokenSize,
Expand Down Expand Up @@ -62,6 +63,13 @@ const NativeSendHeading = () => {
const multichainNetwork = useSelector(getMultichainNetwork);
const ticker = multichainNetwork?.network?.ticker;

const networkConfigurationsByChainId = useSelector(
getNetworkConfigurationsByChainId,
);

const network = networkConfigurationsByChainId?.[transactionMeta.chainId];
const { nativeCurrency } = network;

const locale = useSelector(getIntlLocale);
const roundedTransferValue = formatAmount(locale, nativeAssetTransferValue);

Expand All @@ -76,12 +84,11 @@ const NativeSendHeading = () => {
const NetworkImage = (
<AvatarToken
src={
multichainNetwork?.network?.rpcPrefs?.imageUrl ||
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[
transactionMeta.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP
CHAIN_ID_TOKEN_IMAGE_MAP[
transactionMeta.chainId as keyof typeof CHAIN_ID_TOKEN_IMAGE_MAP
]
}
name={multichainNetwork?.nickname}
name={nativeCurrency}
size={AvatarTokenSize.Xl}
backgroundColor={BackgroundColor.backgroundDefault}
/>
Expand Down
Loading

0 comments on commit 738f6d7

Please sign in to comment.