Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/bridge-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152))

### Fixed

- Check whether `selectedQuote` exists in `selectBridgeQuotes.sortedQuotes` before returning it as the `activeQuote`. Fall back on the `recommendedQuote` if selectedQuote is stale ([#8154](https://github.com/MetaMask/core/pull/8154))

## [69.0.1]

### Changed
Expand Down
29 changes: 27 additions & 2 deletions packages/bridge-controller/src/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1256,12 +1256,37 @@ describe('Bridge Selectors', () => {
});

it('should handle selected quote', () => {
const selectedQuote = {
...mockState.quotes[0],
quote: { ...mockState.quotes[0].quote, requestId: '123' },
} as never;
const result = selectBridgeQuotes(mockState, {
...mockClientParams,
selectedQuote: mockQuote as never,
selectedQuote,
});

expect(result.activeQuote).toStrictEqual(mockQuote);
expect(result.recommendedQuote).toStrictEqual(
expect.objectContaining(mockState.quotes[1]),
);
expect(result.activeQuote).toStrictEqual(
expect.objectContaining(selectedQuote),
);
});

it('should set recommendedQuote as activeQuote when selected quote is not found', () => {
const selectedQuote = {
...mockState.quotes[0],
quote: { ...mockState.quotes[0].quote, requestId: 'abc' },
} as never;
const result = selectBridgeQuotes(mockState, {
...mockClientParams,
selectedQuote,
});

expect(result.recommendedQuote).toStrictEqual(
expect.objectContaining(mockState.quotes[1]),
);
expect(result.activeQuote).toStrictEqual(result.recommendedQuote);
});

it('should handle quote refresh state', () => {
Expand Down
14 changes: 9 additions & 5 deletions packages/bridge-controller/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,15 @@ const selectBridgeQuotesWithMetadata = createBridgeSelector(
minToTokenAmount,
swapRate: calcSwapRate(sentAmount.amount, toTokenAmount.amount),
/**
This is the amount required to submit the transactions
Includes the relayer fee or other native fees
This is the amount required to submit all the transactions.
Includes the relayer fee or other native fees.
Should be used for balance checks and tx submission.
*/
totalNetworkFee: totalEstimatedNetworkFee,
totalMaxNetworkFee,
/**
This contains gas fee estimates for the bridge transaction
Does not include the relayer fee (if needed), just the gasLimit and effectiveGas returned by the bridge API
Does not include the relayer fee (if needed), just the gasLimit and effectiveGas returned by the bridge API.
Should only be used for display purposes.
*/
gasFee,
Expand Down Expand Up @@ -485,9 +485,13 @@ const selectRecommendedQuote = createBridgeSelector(
const selectActiveQuote = createBridgeSelector(
[
selectRecommendedQuote,
(_, { selectedQuote }: BridgeQuotesClientParams) => selectedQuote,
selectSortedBridgeQuotes,
(_, { selectedQuote }) => selectedQuote,
],
(recommendedQuote, selectedQuote) => selectedQuote ?? recommendedQuote,
(recommendedQuote, sortedQuotes, selectedQuote) =>
sortedQuotes.find(
(quote) => quote.quote.requestId === selectedQuote?.quote.requestId,
) ?? recommendedQuote,
);

const selectIsQuoteGoingToRefresh = createBridgeSelector(
Expand Down
27 changes: 21 additions & 6 deletions packages/bridge-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ export type ExchangeRate = { exchangeRate?: string; usdExchangeRate?: string };
*/
export type QuoteMetadata = {
/**
* If gas is included, this is the value of the src or dest token that was used to pay for the gas
* If gas is included, this is the value of the src or dest token that was used to pay for the gas.
* Show this value to indicate transaction fees for gasless quotes.
*/
includedTxFees?: TokenAmountValues | null;
/**
Expand All @@ -125,6 +126,12 @@ export type QuoteMetadata = {
* max is the max gas fee that will be used by the transaction.
*/
gasFee: Record<'effective' | 'total' | 'max', TokenAmountValues>;
/**
* The total network fee required to submit the trade and any approvals. This includes
* the relayer fee or other native fees. Should be used for balance checks and tx submission.
* Note: This is only accurate for non-gasless transactions. Use {@link QuoteMetadata.includedTxFees} to
* get the total network fee for gasless transactions.
*/
totalNetworkFee: TokenAmountValues; // estimatedGasFees + relayerFees
totalMaxNetworkFee: TokenAmountValues; // maxGasFees + relayerFees
/**
Expand All @@ -136,16 +143,24 @@ export type QuoteMetadata = {
*/
minToTokenAmount: TokenAmountValues;
/**
* If gas is included: toTokenAmount
* Otherwise: toTokenAmount - totalNetworkFee
* If gas is included: {@link QuoteMetadata.toTokenAmount} - {@link QuoteMetadata.includedTxFees}.
* Otherwise: {@link QuoteMetadata.toTokenAmount} - {@link QuoteMetadata.totalNetworkFee}.
*/
adjustedReturn: Omit<TokenAmountValues, 'amount'>;
/**
* The amount that the user will send, including fees
* srcTokenAmount + metabridgeFee + txFee
* The amount that the user will send, including fees that are paid in the src token
* {@link Quote.srcTokenAmount} + {@link Quote.feeData[FeeType.METABRIDGE].amount} + {@link Quote.feeData[FeeType.TX_FEE].amount}
*/
sentAmount: TokenAmountValues;
swapRate: string; // destTokenAmount / sentAmount
/**
* The swap rate is the amount that the user will receive per amount sent. Accounts for fees paid in the src or dest token.
* This is calculated as {@link QuoteMetadata.toTokenAmount} / {@link QuoteMetadata.sentAmount}.
*/
swapRate: string;
/**
* The cost of the trade, which is the difference between the amount sent and the adjusted return.
* This is calculated as {@link QuoteMetadata.sentAmount} - {@link QuoteMetadata.adjustedReturn}.
*/
cost: Omit<TokenAmountValues, 'amount'>; // sentAmount - adjustedReturn
};

Expand Down
Loading