Skip to content

Commit 405e1ea

Browse files
authored
feat: (MMS-2106) supports copying block explorer link in bridge page (#31498)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Adds support for copying the token block explorer link in the address text under the token icon of the bridge page. Also displays a toast to let the user know this link has been copied. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/31498?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-2106 ## **Manual testing steps** 1. Go to the bridge page. 2. Select a toToken that is not a native token. 3. Click the address under the token icon. 4. Observe the toast and verify the link is correct. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent 1158dca commit 405e1ea

File tree

6 files changed

+543
-370
lines changed

6 files changed

+543
-370
lines changed

app/_locales/en/messages.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/_locales/en_GB/messages.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/pages/bridge/__snapshots__/index.test.tsx.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ exports[`Bridge renders the component with initial props 1`] = `
141141
</div>
142142
<p
143143
class="mm-box mm-text mm-text--body-md mm-box--display-flex mm-box--gap-1 mm-box--color-text-alternative-soft"
144+
style="cursor: default; text-decoration: none;"
144145
/>
145146
</div>
146147
</div>
@@ -209,6 +210,7 @@ exports[`Bridge renders the component with initial props 1`] = `
209210
</div>
210211
<a
211212
class="mm-box mm-text mm-text--body-md mm-box--display-flex mm-box--gap-1 mm-box--color-text-alternative-soft"
213+
style="cursor: pointer; text-decoration: underline;"
212214
/>
213215
</div>
214216
</div>

ui/pages/bridge/prepare/__snapshots__/prepare-bridge-page.test.tsx.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ exports[`PrepareBridgePage should render the component, with initial state 1`] =
9090
</div>
9191
<p
9292
class="mm-box mm-text mm-text--body-md mm-box--display-flex mm-box--gap-1 mm-box--color-text-alternative-soft"
93+
style="cursor: default; text-decoration: none;"
9394
/>
9495
</div>
9596
</div>
@@ -158,6 +159,7 @@ exports[`PrepareBridgePage should render the component, with initial state 1`] =
158159
</div>
159160
<a
160161
class="mm-box mm-text mm-text--body-md mm-box--display-flex mm-box--gap-1 mm-box--color-text-alternative-soft"
162+
style="cursor: pointer; text-decoration: underline;"
161163
/>
162164
</div>
163165
</div>
@@ -281,6 +283,7 @@ exports[`PrepareBridgePage should render the component, with inputs set 1`] = `
281283
</div>
282284
<p
283285
class="mm-box mm-text mm-text--body-md mm-box--display-flex mm-box--gap-1 mm-box--color-text-alternative-soft"
286+
style="cursor: default; text-decoration: none;"
284287
/>
285288
</div>
286289
</div>
@@ -349,6 +352,7 @@ exports[`PrepareBridgePage should render the component, with inputs set 1`] = `
349352
</div>
350353
<a
351354
class="mm-box mm-text mm-text--body-md mm-box--display-flex mm-box--gap-1 mm-box--color-text-alternative-soft"
355+
style="cursor: pointer; text-decoration: underline;"
352356
/>
353357
</div>
354358
</div>

ui/pages/bridge/prepare/bridge-input-group.tsx

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import React, { useEffect, useRef } from 'react';
22
import { useSelector } from 'react-redux';
33
import { BigNumber } from 'bignumber.js';
4-
import { isNativeAddress } from '@metamask/bridge-controller';
4+
import {
5+
formatChainIdToCaip,
6+
isNativeAddress,
7+
} from '@metamask/bridge-controller';
58
import type { BridgeToken } from '@metamask/bridge-controller';
9+
import { getAccountLink } from '@metamask/etherscan-link';
610
import {
711
Text,
812
TextField,
@@ -35,6 +39,11 @@ import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
3539
import { MINUTE } from '../../../../shared/constants/time';
3640
import { getIntlLocale } from '../../../ducks/locale/locale';
3741
import { useIsMultichainSwap } from '../hooks/useIsMultichainSwap';
42+
import {
43+
MULTICHAIN_NETWORK_BLOCK_EXPLORER_FORMAT_URLS_MAP,
44+
MultichainNetworks,
45+
} from '../../../../shared/constants/multichain/networks';
46+
import { formatBlockExplorerAddressUrl } from '../../../../shared/lib/multichain/networks';
3847
import { BridgeAssetPickerButton } from './components/bridge-asset-picker-button';
3948

4049
const sanitizeAmountInput = (textToSanitize: string) => {
@@ -60,6 +69,7 @@ export const BridgeInputGroup = ({
6069
amountInFiat,
6170
onMaxButtonClick,
6271
isMultiselectEnabled,
72+
onBlockExplorerClick,
6373
buttonProps,
6474
}: {
6575
amountInFiat?: BigNumber;
@@ -71,6 +81,7 @@ export const BridgeInputGroup = ({
7181
'testId' | 'autoFocus' | 'value' | 'readOnly' | 'disabled' | 'className'
7282
>;
7383
onMaxButtonClick?: (value: string) => void;
84+
onBlockExplorerClick?: (token: BridgeToken) => void;
7485
} & Pick<
7586
React.ComponentProps<typeof AssetPicker>,
7687
| 'networkProps'
@@ -110,6 +121,45 @@ export const BridgeInputGroup = ({
110121

111122
const isSwap = useIsMultichainSwap();
112123

124+
const handleAddressClick = () => {
125+
if (token && selectedChainId) {
126+
const caipChainId = formatChainIdToCaip(selectedChainId);
127+
const isSolana = caipChainId === MultichainNetworks.SOLANA;
128+
129+
let blockExplorerUrl = '';
130+
if (isSolana) {
131+
const blockExplorerUrls =
132+
MULTICHAIN_NETWORK_BLOCK_EXPLORER_FORMAT_URLS_MAP[caipChainId];
133+
if (blockExplorerUrls) {
134+
blockExplorerUrl = formatBlockExplorerAddressUrl(
135+
blockExplorerUrls,
136+
token.address,
137+
);
138+
}
139+
} else {
140+
const explorerUrl =
141+
networkProps?.network?.blockExplorerUrls?.[
142+
networkProps?.network?.defaultBlockExplorerUrlIndex ?? 0
143+
];
144+
if (explorerUrl) {
145+
blockExplorerUrl = getAccountLink(
146+
token.address,
147+
selectedChainId,
148+
{
149+
blockExplorerUrl: explorerUrl,
150+
},
151+
undefined,
152+
);
153+
}
154+
}
155+
156+
if (blockExplorerUrl) {
157+
handleCopy(blockExplorerUrl);
158+
onBlockExplorerClick?.(token);
159+
}
160+
}
161+
};
162+
113163
return (
114164
<Column paddingInline={6} gap={1}>
115165
<Row gap={4}>
@@ -239,10 +289,16 @@ export const BridgeInputGroup = ({
239289
}
240290
onClick={() => {
241291
if (isAmountReadOnly && token && selectedChainId) {
292+
handleAddressClick();
293+
} else if (token && selectedChainId) {
242294
handleCopy(token.address);
243295
}
244296
}}
245297
as={isAmountReadOnly ? 'a' : 'p'}
298+
style={{
299+
cursor: isAmountReadOnly ? 'pointer' : 'default',
300+
textDecoration: isAmountReadOnly ? 'underline' : 'none',
301+
}}
246302
>
247303
{isAmountReadOnly &&
248304
token &&

0 commit comments

Comments
 (0)