Skip to content

Commit

Permalink
feat: cross chain swaps - tx status - UI (#28657)
Browse files Browse the repository at this point in the history
<!--
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**

<!--
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?
-->

This PR is a collection of all the UI related code from
#27740 (no UI
changes). It has been split up in order to make it easier to review.

The main addition is the Bridge Transaction Details and its supporting
code.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28657?quickstart=1)

## **Related issues**

Related to #27740, #28636

## **Manual testing steps**

Add `BRIDGE_USE_DEV_APIS=1` to `.metamaskrc` to enable Bridge

Refer to to #27740

## **Screenshots/Recordings**

Refer to to #27740

## **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/develop/.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/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] 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.
  • Loading branch information
infiniteflower authored Dec 2, 2024
1 parent c5df9dc commit 19d5637
Show file tree
Hide file tree
Showing 49 changed files with 3,741 additions and 398 deletions.
10 changes: 3 additions & 7 deletions .storybook/test-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const state = {
url: 'https://metamask.github.io/test-dapp/',
},
metamask: {
bridgeStatusState: {
txHistory: {},
},
announcements: {
22: {
id: 22,
Expand Down Expand Up @@ -525,13 +528,6 @@ const state = {
decimals: 18,
},
],
tokenBalances: {
'0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': {
'0x1': {
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': '0x25e4bc',
},
},
},
allDetectedTokens: {
'0xaa36a7': {
'0x9d0ba4ddac06032527b140912ec808ab9451b788': [
Expand Down
71 changes: 71 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/images/hollow-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions shared/types/bridge-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ export type StartPollingForBridgeTxStatusArgs = {
targetContractAddress?: BridgeHistoryItem['targetContractAddress'];
};

export type SourceChainTxHash = string;
export type SourceChainTxMetaId = string;

export type BridgeStatusControllerState = {
txHistory: Record<SourceChainTxHash, BridgeHistoryItem>;
txHistory: Record<SourceChainTxMetaId, BridgeHistoryItem>;
};
22 changes: 18 additions & 4 deletions test/jest/mock-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { KeyringType } from '../../shared/constants/keyring';
import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods';
import { mockNetworkState } from '../stub/networks';
import { DEFAULT_BRIDGE_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge/constants';
import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge-status/constants';
import { BRIDGE_PREFERRED_GAS_ESTIMATE } from '../../shared/constants/bridge';

export const createGetSmartTransactionFeesApiResponse = () => {
Expand Down Expand Up @@ -702,10 +703,19 @@ export const createSwapsMockStore = () => {
};

export const createBridgeMockStore = (
featureFlagOverrides = {},
bridgeSliceOverrides = {},
bridgeStateOverrides = {},
metamaskStateOverrides = {},
{
featureFlagOverrides = {},
bridgeSliceOverrides = {},
bridgeStateOverrides = {},
bridgeStatusStateOverrides = {},
metamaskStateOverrides = {},
} = {
featureFlagOverrides: {},
bridgeSliceOverrides: {},
bridgeStateOverrides: {},
bridgeStatusStateOverrides: {},
metamaskStateOverrides: {},
},
) => {
const swapsStore = createSwapsMockStore();
return {
Expand Down Expand Up @@ -744,6 +754,10 @@ export const createBridgeMockStore = (
quoteRequest: DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest,
...bridgeStateOverrides,
},
bridgeStatusState: {
...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE,
...bridgeStatusStateOverrides,
},
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jest.mock('../../../selectors', () => {
const mockGetSelectedAccount = jest.fn(() => mockSelectedAccount);

return {
getSelectedAddress: jest.fn(() => '0xselectedaddress'),
getAccountType: mockGetAccountType,
getSelectedInternalAccount: mockGetSelectedAccount,
getCurrentChainId: jest.fn(() => '0x5'),
Expand Down
140 changes: 140 additions & 0 deletions ui/components/app/transaction-breakdown/transaction-breakdown-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
TransactionMeta,
TransactionType,
} from '@metamask/transaction-controller';
import { getShouldShowFiat } from '../../../selectors';
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util';
import { isEIP1559Transaction } from '../../../../shared/modules/transaction.utils';

import {
subtractHexes,
sumHexes,
} from '../../../../shared/modules/conversion.utils';
import {
calcTokenAmount,
getSwapsTokensReceivedFromTxMeta,
} from '../../../../shared/lib/transactions-controller-utils';
import { CONFIRMED_STATUS } from '../transaction-activity-log/transaction-activity-log.constants';
import { MetaMaskReduxState } from '../../../store/store';

export const getTransactionBreakdownData = ({
state,
transaction,
isTokenApprove,
}: {
state: MetaMaskReduxState;
transaction: TransactionMeta;
isTokenApprove: boolean;
}) => {
const {
txParams: { gas, gasPrice, maxFeePerGas, value } = {},
txReceipt: { gasUsed, effectiveGasPrice, l1Fee: l1HexGasTotal } = {},
baseFeePerGas,
sourceTokenAmount: rawSourceTokenAmount,
sourceTokenDecimals,
sourceTokenSymbol,
destinationTokenAddress,
destinationTokenAmount: rawDestinationTokenAmountEstimate,
destinationTokenDecimals,
destinationTokenSymbol,
status,
type,
} = transaction;

const sourceTokenAmount =
rawSourceTokenAmount && sourceTokenDecimals
? calcTokenAmount(rawSourceTokenAmount, sourceTokenDecimals).toFixed()
: undefined;
let destinationTokenAmount;

if (
type === TransactionType.swapAndSend &&
// ensure fallback values are available
rawDestinationTokenAmountEstimate &&
destinationTokenDecimals &&
destinationTokenSymbol
) {
try {
// try to get the actual destination token amount from the on-chain events
destinationTokenAmount = getSwapsTokensReceivedFromTxMeta(
destinationTokenSymbol,
transaction,
destinationTokenAddress,
undefined,
destinationTokenDecimals,
undefined,
undefined,
// @ts-expect-error TODO: fix this, ported directly from original code
null,
);

// if no amount is found, throw
if (!destinationTokenAmount) {
throw new Error('Actual destination token amount not found');
}
} catch (error) {
// if actual destination token amount is not found, use the estimated amount from the quote
destinationTokenAmount =
rawDestinationTokenAmountEstimate && destinationTokenDecimals
? calcTokenAmount(
rawDestinationTokenAmountEstimate,
destinationTokenDecimals,
).toFixed()
: undefined;
}
}

const sourceAmountFormatted =
sourceTokenAmount && sourceTokenDecimals && sourceTokenSymbol
? `${sourceTokenAmount} ${sourceTokenSymbol}`
: undefined;
const destinationAmountFormatted =
destinationTokenAmount && status === CONFIRMED_STATUS
? `${destinationTokenAmount} ${destinationTokenSymbol}`
: undefined;

const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas;

const priorityFee =
effectiveGasPrice &&
baseFeePerGas &&
subtractHexes(effectiveGasPrice, baseFeePerGas);

// To calculate the total cost of the transaction, we use gasPrice if it is in the txParam,
// which will only be the case on non-EIP1559 networks. If it is not in the params, we can
// use the effectiveGasPrice from the receipt, which will ultimately represent to true cost
// of the transaction. Either of these are used the same way with gasLimit to calculate total
// cost. effectiveGasPrice will be available on the txReciept for all EIP1559 networks
const usedGasPrice = gasPrice || effectiveGasPrice;
const hexGasTotal =
(gasLimit &&
usedGasPrice &&
getHexGasTotal({ gasLimit, gasPrice: usedGasPrice })) ||
'0x0';

const totalInHex = sumHexes(
hexGasTotal,
// @ts-expect-error TODO: fix this, ported directly from original code
value,
l1HexGasTotal ?? 0,
);

return {
nativeCurrency: getNativeCurrency(state),
showFiat: getShouldShowFiat(state),
totalInHex,
gas,
gasPrice,
maxFeePerGas,
gasUsed,
isTokenApprove,
hexGasTotal,
priorityFee,
baseFee: baseFeePerGas,
isEIP1559Transaction: isEIP1559Transaction(transaction),
l1HexGasTotal,
sourceAmountFormatted,
destinationAmountFormatted,
};
};
Loading

0 comments on commit 19d5637

Please sign in to comment.