diff --git a/app/components/UI/Ramp/hooks/useQuotes.test.ts b/app/components/UI/Ramp/hooks/useQuotes.test.ts index 033b8d9c058..6f7978e9e79 100644 --- a/app/components/UI/Ramp/hooks/useQuotes.test.ts +++ b/app/components/UI/Ramp/hooks/useQuotes.test.ts @@ -2,6 +2,7 @@ import { RampSDK } from '../sdk'; import useSDKMethod from './useSDKMethod'; import { renderHookWithProvider } from '../../../../util/test/renderWithProvider'; import useQuotes from './useQuotes'; +import { QuoteSortBy } from '@consensys/on-ramp-sdk/dist/IOnRampSdk'; type DeepPartial = { [key in keyof BaseType]?: DeepPartial; @@ -185,4 +186,41 @@ describe('useQuotes', () => { rerender(() => useQuotes(200)); expect(result.current.quotes).toEqual([{ id: 'quote-2' }]); }); + + it('sorts quotes by price', () => { + const mockQuery = jest.fn(); + (useSDKMethod as jest.Mock).mockReturnValue([ + { + data: { + quotes: [ + { id: 'quote-2', provider: { id: 'provider-id-2' } }, + { id: 'quote-4', provider: { id: 'provider-id-4' } }, + { id: 'quote-1', provider: { id: 'provider-id-1' } }, + { id: 'quote-3', provider: { id: 'provider-id-3' } }, + ], + sorted: [ + { + sortBy: QuoteSortBy.price, + ids: [ + 'provider-id-1', + 'provider-id-2', + 'provider-id-3', + 'provider-id-4', + ], + }, + ], + }, + error: null, + isFetching: false, + }, + mockQuery, + ]); + const { result } = renderHookWithProvider(() => useQuotes(100)); + expect(result.current.quotes).toEqual([ + { id: 'quote-1', provider: { id: 'provider-id-1' } }, + { id: 'quote-2', provider: { id: 'provider-id-2' } }, + { id: 'quote-3', provider: { id: 'provider-id-3' } }, + { id: 'quote-4', provider: { id: 'provider-id-4' } }, + ]); + }); }); diff --git a/app/components/UI/Ramp/hooks/useQuotes.ts b/app/components/UI/Ramp/hooks/useQuotes.ts index 6a006499b59..43e7a9e9468 100644 --- a/app/components/UI/Ramp/hooks/useQuotes.ts +++ b/app/components/UI/Ramp/hooks/useQuotes.ts @@ -1,5 +1,8 @@ import { useRampSDK } from '../sdk'; import useSDKMethod from './useSDKMethod'; +import { useMemo } from 'react'; +import { QuoteSortBy } from '@consensys/on-ramp-sdk/dist/IOnRampSdk'; +import { sortQuotes } from '../utils'; function useQuotes(amount: number | string) { const { @@ -20,8 +23,13 @@ function useQuotes(amount: number | string) { selectedAddress, ); + const quotes = useMemo( + () => sortQuotes(data?.quotes, data?.sorted, QuoteSortBy.price), + [data], + ); + return { - quotes: data?.quotes, + quotes, sorted: data?.sorted, isFetching, error, diff --git a/app/components/UI/Ramp/utils/index.test.ts b/app/components/UI/Ramp/utils/index.test.ts index 28b66068dfd..5a740e61e63 100644 --- a/app/components/UI/Ramp/utils/index.test.ts +++ b/app/components/UI/Ramp/utils/index.test.ts @@ -6,6 +6,8 @@ import { import { AggregatorNetwork, OrderOrderTypeEnum, + Provider, + QuoteSortMetadata, } from '@consensys/on-ramp-sdk/dist/API'; import { timeToDescription, @@ -23,11 +25,13 @@ import { isSellFiatOrder, getNotificationDetails, stateHasOrder, + sortQuotes, } from '.'; import { FIAT_ORDER_STATES } from '../../../../constants/on-ramp'; import { FiatOrder, RampType } from '../../../../reducers/fiatOrders/types'; import { getOrders } from '../../../../reducers/fiatOrders'; import type { RootState } from '../../../../reducers'; +import { QuoteSortBy } from '@consensys/on-ramp-sdk/dist/IOnRampSdk'; describe('timeToDescription', () => { it('should return a function', () => { @@ -640,3 +644,40 @@ describe('stateHasOrder', () => { ); }); }); + +describe('sortQuotes', () => { + const quotes: QuoteResponse[] = [ + { provider: { id: 'provider-id-2' } as Provider } as QuoteResponse, + { provider: { id: 'provider-id-1' } as Provider } as QuoteResponse, + ]; + + it('should return quotes unsorted if no sortingArray is provided', () => { + expect(sortQuotes(quotes)).toEqual(quotes); + }); + + it('should return quotes unsorted if no sortOrder is found', () => { + const sortingArray: QuoteSortMetadata[] = [ + // @ts-expect-error Testing invalid input on purpose + { sortBy: undefined, ids: ['provider-id-1', 'provider-id-2'] }, + ]; + expect(sortQuotes(quotes, sortingArray, QuoteSortBy.price)).toEqual(quotes); + }); + + it('should sort quotes by price correctly', () => { + const sortingArray: QuoteSortMetadata[] = [ + { sortBy: QuoteSortBy.price, ids: ['provider-id-1', 'provider-id-2'] }, + ]; + expect(sortQuotes(quotes, sortingArray, QuoteSortBy.price)).toEqual([ + { provider: { id: 'provider-id-1' } as Provider }, + { provider: { id: 'provider-id-2' } as Provider }, + ]); + }); + + it('should handle undefined quotes gracefully', () => { + expect(sortQuotes(undefined, [], QuoteSortBy.price)).toBeUndefined(); + }); + + it('should handle undefined sortingArray gracefully', () => { + expect(sortQuotes(quotes, undefined, QuoteSortBy.price)).toEqual(quotes); + }); +}); diff --git a/app/components/UI/Ramp/utils/index.ts b/app/components/UI/Ramp/utils/index.ts index f53d2625c09..9f126c69acb 100644 --- a/app/components/UI/Ramp/utils/index.ts +++ b/app/components/UI/Ramp/utils/index.ts @@ -7,6 +7,7 @@ import { import { AggregatorNetwork, OrderOrderTypeEnum, + QuoteSortMetadata, SellOrder, } from '@consensys/on-ramp-sdk/dist/API'; import { @@ -20,6 +21,7 @@ import { RootState } from '../../../../reducers'; import { FIAT_ORDER_STATES } from '../../../../constants/on-ramp'; import { strings } from '../../../../../locales/i18n'; import { getDecimalChainId } from '../../../../util/networks'; +import { QuoteSortBy } from '@consensys/on-ramp-sdk/dist/IOnRampSdk'; const isOverAnHour = (minutes: number) => minutes > 59; @@ -216,6 +218,30 @@ export function isSellFiatOrder(order: FiatOrder): order is FiatOrder { return order.orderType === OrderOrderTypeEnum.Sell; } +export function sortQuotes( + quotes?: (QuoteResponse | QuoteError | SellQuoteResponse)[] | undefined, + sortingArray?: QuoteSortMetadata[], + quoteSortBy?: QuoteSortBy, +): (QuoteResponse | QuoteError | SellQuoteResponse)[] | undefined { + if (!quotes || !sortingArray) { + return quotes; + } + + const sortOrder = sortingArray.find((s) => s.sortBy === quoteSortBy)?.ids; + + if (!sortOrder) { + return quotes; + } + + const sortOrderMap = new Map(sortOrder.map((id, index) => [id, index])); + + return [...quotes].sort( + (a, b) => + (sortOrderMap.get(a.provider.id) ?? 0) - + (sortOrderMap.get(b.provider.id) ?? 0), + ); +} + const NOTIFICATION_DURATION = 5000; const baseNotificationDetails = {