Skip to content

Commit

Permalink
feat(ramp): sorts ramp quotes by price (#13339)
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 adds sorting logic to the useQuotes hook to sort quotes by price
based on the order provided by the Ramp API.


## **Related issues**

Fixes:

## **Manual testing steps**

1. Go to ramps experience
2. confirm that quotes are sorted by price
3.

## **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 Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/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
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.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
georgeweiler authored Feb 7, 2025
1 parent a010f9c commit 1ea963f
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 1 deletion.
38 changes: 38 additions & 0 deletions app/components/UI/Ramp/hooks/useQuotes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BaseType> = {
[key in keyof BaseType]?: DeepPartial<BaseType[key]>;
Expand Down Expand Up @@ -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' } },
]);
});
});
10 changes: 9 additions & 1 deletion app/components/UI/Ramp/hooks/useQuotes.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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,
Expand Down
41 changes: 41 additions & 0 deletions app/components/UI/Ramp/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
import {
AggregatorNetwork,
OrderOrderTypeEnum,
Provider,
QuoteSortMetadata,
} from '@consensys/on-ramp-sdk/dist/API';
import {
timeToDescription,
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
26 changes: 26 additions & 0 deletions app/components/UI/Ramp/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
AggregatorNetwork,
OrderOrderTypeEnum,
QuoteSortMetadata,
SellOrder,
} from '@consensys/on-ramp-sdk/dist/API';
import {
Expand All @@ -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;

Expand Down Expand Up @@ -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 = {
Expand Down

0 comments on commit 1ea963f

Please sign in to comment.