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 @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Read `priceData.cost` from backend-provided quote metadata and use it to sort quotes ([#10012](https://github.com/MetaMask/core/pull/10012))

## [80.1.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ exports[`Bridge Selectors selectBridgeQuotes should return sorted quotes with me
"usd": "2.099927",
"valueInCurrency": "419.985546",
},
"cost": {
"usd": "8.900073",
"valueInCurrency": "1758.014454",
},
"priceImpact": {
"usd": "7.9",
"valueInCurrency": "1564.2",
Expand Down Expand Up @@ -350,6 +354,10 @@ exports[`Bridge Selectors selectBridgeQuotes should return sorted quotes with me
"usd": "2.099927",
"valueInCurrency": "419.985546",
},
"cost": {
"usd": "8.900073",
"valueInCurrency": "1758.014454",
},
"priceImpact": {
"usd": "8.9",
"valueInCurrency": "1758",
Expand Down
167 changes: 154 additions & 13 deletions packages/bridge-controller/src/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,30 @@ describe('Bridge Selectors', () => {
),
).toStrictEqual({});
});

it('should treat missing conversionRate and usdConversionRate as 0 for EVM tokens', () => {
const result = selectExchangeRateByAssetId(
{
...mockExchangeRateSources,
currencyRates: {
ETH: {},
},
marketData: {
'0x1': {
[MOCK_MUSD_ADDRESS]: {
price: 2,
currency: 'ETH',
},
},
},
} as unknown as BridgeAppState,
formatAddressToAssetId(MOCK_MUSD_ADDRESS.toLowerCase(), '1'),
);
expect(result).toStrictEqual({
exchangeRate: '0',
usdExchangeRate: '0',
});
});
});

describe('selectIsAssetExchangeRateInState', () => {
Expand Down Expand Up @@ -1052,6 +1076,106 @@ describe('Bridge Selectors', () => {
).toBe('-0.02');
});

it('should use priceImpact.valueInCurrency to sort quotes if cost and amount are unavailable (Phase 1.5)', () => {
const mockState = getMockState(1);
const quotes = [
{
...mockState.quotes[0],
quote: {
...mockState.quotes[0].quote,
priceData: { priceImpact: { usd: '10' } },
},
},
{
...mockState.quotes[1],
quote: {
...mockState.quotes[1].quote,
priceData: { priceImpact: { usd: '1' } },
},
},
];
const { recommendedQuote, sortedQuotes } = selectBridgeQuotes(
{
...mockState,
assetExchangeRates: {},
marketData: {},
quotes,
currencyRates: {
ETH: {
conversionRate: 1980,
usdConversionRate: 10,
},
},
},
{
...mockClientParams,
migrationPhase: QuoteMetadataMigrationPhase.V2WithV1Fallback,
},
);

expect(
sortedQuotes.every(
(quote) =>
!quote.cost?.valueInCurrency &&
!quote.quote.priceData?.priceImpact?.amount,
),
).toBe(true);
expect(
sortedQuotes.map(({ quote }) => [
quote.requestId,
quote.priceData?.priceImpact?.usd,
quote.priceData?.priceImpact?.valueInCurrency,
]),
).toStrictEqual([
['456', '1', '198'],
['123', '10', '1980'],
]);
expect(recommendedQuote?.quote.requestId).toBe('456');
});

it('does not derive fiat from usd when the src native rate pair is missing (Phase 1.5)', () => {
const mockState = getMockState(1);
const quotes = mockState.quotes.map((quote) => ({
...quote,
quote: {
...quote.quote,
priceData: { priceImpact: { usd: '10' } },
},
}));
const { sortedQuotes } = selectBridgeQuotes(
{
...mockState,
quotes,
assetExchangeRates: {},
marketData: {},
currencyRates: {},
},
{
...mockClientParams,
migrationPhase: QuoteMetadataMigrationPhase.V2WithV1Fallback,
},
);

expect(
sortedQuotes.map(
(quote) => quote.quote.priceData?.priceImpact?.valueInCurrency,
),
).toStrictEqual([undefined, undefined]);
});

it('calculates metadata when quoteRequest is empty', () => {
const mockState = getMockState(1);
const { sortedQuotes } = selectBridgeQuotes(
{
...mockState,
quoteRequest: [],
},
mockClientParams,
);

expect(sortedQuotes).toHaveLength(mockState.quotes.length);
});

describe('returns swap metadata', () => {
const getMockSwapState = (
srcAsset: Omit<BridgeAssetV2, 'chainId' | 'address'>,
Expand Down Expand Up @@ -2083,36 +2207,36 @@ describe('Bridge Selectors', () => {

expect(totalReceived).toMatchInlineSnapshot(`
{
"amount": "38240503",
"amount": "38423182",
"asset": {
"assetId": "eip155:137/erc20:0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"decimals": 6,
"iconUrl": "https://media.socket.tech/tokens/all/USDC",
"name": "Native USD Coin (POS)",
"symbol": "USDC",
},
"minAmount": "37460000",
"minAmountNormalized": "37.46",
"minAmountUsd": "37.46",
"minAmountValueInCurrency": "7492",
"normalizedAmount": "38.240503",
"usd": "38.240503",
"valueInCurrency": "7648.1006",
"minAmount": "37600000",
"minAmountNormalized": "37.6",
"minAmountUsd": "37.6",
"minAmountValueInCurrency": "7520",
"normalizedAmount": "38.423182",
"usd": "38.423182",
"valueInCurrency": "7684.6364",
}
`);
expect(minimumReceived).toMatchInlineSnapshot(`
{
"amount": "37460000",
"amount": "37600000",
"asset": {
"assetId": "eip155:137/erc20:0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"decimals": 6,
"iconUrl": "https://media.socket.tech/tokens/all/USDC",
"name": "Native USD Coin (POS)",
"symbol": "USDC",
},
"normalizedAmount": "37.46",
"usd": "37.46",
"valueInCurrency": "7492",
"normalizedAmount": "37.6",
"usd": "37.6",
"valueInCurrency": "7520",
}
`);
expect(rest).toMatchInlineSnapshot(`
Expand All @@ -2123,10 +2247,27 @@ describe('Bridge Selectors', () => {
"quotesRefreshCount": 0,
}
`);
expect(recommendedQuotes.map((quote) => quote?.cost?.valueInCurrency))
.toMatchInlineSnapshot(`
[
"-4867.87052548",
"24925.00683656",
]
`);
expect(
recommendedQuotes.map(
(quote) => quote?.quote.priceData?.cost?.valueInCurrency,
),
).toMatchInlineSnapshot(`
[
"-4867.87052548",
"24925.00683656",
]
`);
expect(recommendedQuotes.map((quote) => quote?.quote.requestId))
.toMatchInlineSnapshot(`
[
"4277a368-40d7-4e82-aa67-74f29dc5f98a",
"381c23bc-e3e4-48fe-bc53-257471e388ad",
"90ae8e69-f03a-4cf6-bab7-ed4e3431eb37",
]
`);
Expand Down
17 changes: 15 additions & 2 deletions packages/bridge-controller/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ const selectSortedBridgeQuotes = createBridgeSelector(
selectBridgeQuotesWithMetadata,
(_, { sortOrder }: BridgeQuotesClientParams) => sortOrder,
],
(quotesWithMetadata, sortOrder): (QuoteResponse & QuoteMetadata)[] => {
(quotesWithMetadata, sortOrder): QuoteResponse[] => {
switch (sortOrder) {
case SortOrder.ETA_ASC:
return orderBy(
Expand All @@ -458,6 +458,18 @@ const selectSortedBridgeQuotes = createBridgeSelector(
'asc',
);
default:
if (
quotesWithMetadata.every(
(quote) => quote.quote.priceData?.cost?.valueInCurrency,
)
) {
return orderBy(
quotesWithMetadata,
({ quote: { priceData } }) =>
Number(priceData?.cost?.valueInCurrency),
'asc',
);
}
Comment thread
micaelae marked this conversation as resolved.
if (
quotesWithMetadata.every(
(quote) => quote.quote.priceData?.priceImpact?.amount,
Expand All @@ -469,7 +481,8 @@ const selectSortedBridgeQuotes = createBridgeSelector(
Number(priceData?.priceImpact?.amount),
'asc',
);
} else if (
}
if (
quotesWithMetadata.every(
(quote) => quote.quote.priceData?.priceImpact?.valueInCurrency,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ export const toCurrencyValues = (
quote: { src, dest, feeData, priceData },
} = quote;

const { adjustedReturn, priceImpact } = priceData ?? {};
const { adjustedReturn, priceImpact, cost } = priceData ?? {};

const priceImpactFiat = toCurrency(priceImpact, usdToFiatExchangeRate);
const adjustedReturnFiat = toCurrency(adjustedReturn, usdToFiatExchangeRate);
const costFiat = toCurrency(cost, usdToFiatExchangeRate);

const minAmountValueInCurrency = toCurrency(
{
Expand Down Expand Up @@ -68,14 +69,17 @@ export const toCurrencyValues = (
),
]),
),
...((priceImpactFiat ?? adjustedReturnFiat) && {
...((priceImpactFiat ?? adjustedReturnFiat ?? costFiat) && {
priceData: {
...(priceImpactFiat && {
priceImpact: priceImpactFiat,
}),
...(adjustedReturnFiat && {
adjustedReturn: adjustedReturnFiat,
}),
...(costFiat && {
cost: costFiat,
}),
Comment thread
micaelae marked this conversation as resolved.
},
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ export const toQuoteMetadataV1 = (
}),
...includeIfTruthy(priceData?.priceImpact, {
priceImpact: toTokenAmountValues(priceData?.priceImpact),
// Use priceImpact as cost
cost: toTokenAmountValues(priceData?.priceImpact),
}),
...includeIfTruthy(priceData?.cost, {
cost: toTokenAmountValues(priceData?.cost),
}),
...includeIfTruthy(relayer?.[0], {
relayerFee: toTokenAmountValues(relayer?.[0]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ export const toQuoteMetadataV2 = (
: undefined;
const txFeeAsset = quoteResponseV2?.quote?.feeData?.txFee?.[0]?.asset;

const priceImpactToUse = {
usd: priceImpact?.usd ?? cost?.usd,
valueInCurrency: priceImpact?.valueInCurrency ?? cost?.valueInCurrency,
};
const networkFeeToUse = gasFee?.total ?? totalNetworkFee;

return {
Expand Down Expand Up @@ -101,12 +97,15 @@ export const toQuoteMetadataV2 = (
}),
},
priceData: {
...includeIfTruthy(priceImpactToUse, {
priceImpact: priceImpactToUse,
...includeIfTruthy(priceImpact, {
priceImpact,
}),
...includeIfTruthy(adjustedReturn, {
adjustedReturn,
}),
...includeIfTruthy(cost, {
cost,
}),
swapRate,
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/bridge-controller/src/validators/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const QuoteSchemaV2 = intersection([
'usd',
'valueInCurrency',
]),
cost: pick(AmountsAndAssetSchema, ['usd', 'valueInCurrency']),
}),
),
),
Expand Down