Skip to content

Commit

Permalink
chore: abort quote requests
Browse files Browse the repository at this point in the history
  • Loading branch information
micaelae committed Oct 23, 2024
1 parent db1d225 commit 650bb36
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 43 deletions.
23 changes: 13 additions & 10 deletions app/scripts/controllers/bridge/bridge-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,17 @@ describe('BridgeController', function () {
jest.advanceTimersByTime(1000);
await flushPromises();
expect(fetchBridgeQuotesSpy).toHaveBeenCalledTimes(1);
expect(fetchBridgeQuotesSpy).toHaveBeenCalledWith({
...quoteRequest,
insufficientBal: true,
});
expect(fetchBridgeQuotesSpy).toHaveBeenCalledWith(
{
...quoteRequest,
insufficientBal: true,
},
expect.any(AbortSignal),
);
expect(bridgeController.state.bridgeState.quotesLastFetched).toStrictEqual(
undefined,
);

const firstFetchTime =
bridgeController.state.bridgeState.quotesLastFetched ?? 0;
expect(firstFetchTime).toBeGreaterThan(0);
expect(bridgeController.state.bridgeState).toEqual(
expect.objectContaining({
quoteRequest: { ...quoteRequest, walletAddress: undefined },
Expand All @@ -325,9 +328,9 @@ describe('BridgeController', function () {
quotesLoadingStatus: 1,
}),
);
expect(bridgeController.state.bridgeState.quotesLastFetched).toStrictEqual(
firstFetchTime,
);
const firstFetchTime =
bridgeController.state.bridgeState.quotesLastFetched ?? 0;
expect(firstFetchTime).toBeGreaterThan(0);

// After 2nd fetch
jest.advanceTimersByTime(50000);
Expand Down
30 changes: 25 additions & 5 deletions app/scripts/controllers/bridge/bridge-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = {
},
};

const RESET_STATE_ABORT_MESSAGE = 'Reset controller state';

export default class BridgeController extends StaticIntervalPollingController<
typeof BRIDGE_CONTROLLER_NAME,
{ bridgeState: BridgeControllerState },
BridgeControllerMessenger
> {
#abortController: AbortController | undefined;

constructor({ messenger }: { messenger: BridgeControllerMessenger }) {
super({
name: BRIDGE_CONTROLLER_NAME,
Expand Down Expand Up @@ -89,6 +93,8 @@ export default class BridgeController extends StaticIntervalPollingController<
paramsToUpdate: Partial<QuoteRequest>,
) => {
this.stopAllPolling();
this.#abortController?.abort('Quote request updated');

const { bridgeState } = this.state;
const updatedQuoteRequest = {
...DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest,
Expand Down Expand Up @@ -123,7 +129,6 @@ export default class BridgeController extends StaticIntervalPollingController<
))
: true;

// this.#pollingTokenForQuotes =
this.startPollingByNetworkClientId(srcChainIdInHex, {
...updatedQuoteRequest,
walletAddress,
Expand All @@ -134,9 +139,10 @@ export default class BridgeController extends StaticIntervalPollingController<

resetState = () => {
this.stopAllPolling();
this.#abortController?.abort(RESET_STATE_ABORT_MESSAGE);

this.update((_state) => {
_state.bridgeState = {
..._state.bridgeState,
...DEFAULT_BRIDGE_CONTROLLER_STATE,
quotes: [],
bridgeFeatureFlags: _state.bridgeState.bridgeFeatureFlags,
Expand Down Expand Up @@ -166,32 +172,46 @@ export default class BridgeController extends StaticIntervalPollingController<
};

#fetchBridgeQuotes = async (request: QuoteRequest) => {
this.#abortController?.abort('New quote request');
this.#abortController = new AbortController();
if (request.srcChainId === request.destChainId) {
return;
}
const { bridgeState } = this.state;
this.update((_state) => {
_state.bridgeState = {
...bridgeState,
quotesLastFetched: Date.now(),
quotesLoadingStatus: RequestStatus.LOADING,
};
});

try {
const quotes = await fetchBridgeQuotes(request);
const quotes = await fetchBridgeQuotes(
request,
this.#abortController.signal,
);
this.update((_state) => {
_state.bridgeState = {
..._state.bridgeState,
quotes,
quotesLastFetched: Date.now(),
quotesLoadingStatus: RequestStatus.FETCHED,
};
});
} catch (error) {
console.log('Failed to fetch bridge quotes', error);
this.update((_state) => {
_state.bridgeState = {
...bridgeState,
quoteRequest:
error === RESET_STATE_ABORT_MESSAGE
? DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest
: _state.bridgeState.quoteRequest,
quotesLoadingStatus: RequestStatus.ERROR,
};
});
if ((error as Error).name !== 'AbortError') {
console.log('Failed to fetch bridge quotes', error);
}
}
};

Expand Down
73 changes: 46 additions & 27 deletions ui/pages/bridge/bridge.util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
jest.mock('../../../shared/lib/fetch-with-cache');

describe('Bridge utils', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('fetchBridgeFeatureFlags', () => {
it('should fetch bridge feature flags successfully', async () => {
const mockResponse = {
Expand Down Expand Up @@ -166,22 +170,27 @@ describe('Bridge utils', () => {
(fetchWithCache as jest.Mock).mockResolvedValue(
mockBridgeQuotesNativeErc20,
);
const { signal } = new AbortController();

const result = await fetchBridgeQuotes({
walletAddress: '0x123',
srcChainId: 1,
destChainId: 10,
srcTokenAddress: zeroAddress(),
destTokenAddress: zeroAddress(),
srcTokenAmount: '20000',
slippage: 0.5,
});
const result = await fetchBridgeQuotes(
{
walletAddress: '0x123',
srcChainId: 1,
destChainId: 10,
srcTokenAddress: zeroAddress(),
destTokenAddress: zeroAddress(),
srcTokenAmount: '20000',
slippage: 0.5,
},
signal,
);

expect(fetchWithCache).toHaveBeenCalledWith({
url: 'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x123&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&slippage=0.5',
fetchOptions: {
method: 'GET',
headers: { 'X-Client-Id': 'extension' },
signal,
},
cacheOptions: { cacheRefreshTime: 0 },
functionName: 'fetchBridgeQuotes',
Expand All @@ -194,22 +203,27 @@ describe('Bridge utils', () => {
(fetchWithCache as jest.Mock).mockResolvedValue(
mockBridgeQuotesErc20Erc20,
);
const { signal } = new AbortController();

const result = await fetchBridgeQuotes({
walletAddress: '0x123',
srcChainId: 1,
destChainId: 10,
srcTokenAddress: zeroAddress(),
destTokenAddress: zeroAddress(),
srcTokenAmount: '20000',
slippage: 0.5,
});
const result = await fetchBridgeQuotes(
{
walletAddress: '0x123',
srcChainId: 1,
destChainId: 10,
srcTokenAddress: zeroAddress(),
destTokenAddress: zeroAddress(),
srcTokenAmount: '20000',
slippage: 0.5,
},
signal,
);

expect(fetchWithCache).toHaveBeenCalledWith({
url: 'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x123&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&slippage=0.5',
fetchOptions: {
method: 'GET',
headers: { 'X-Client-Id': 'extension' },
signal,
},
cacheOptions: { cacheRefreshTime: 0 },
functionName: 'fetchBridgeQuotes',
Expand All @@ -224,22 +238,27 @@ describe('Bridge utils', () => {
({ quote, ...restOfQuote }) => restOfQuote,
),
);
const { signal } = new AbortController();

const result = await fetchBridgeQuotes({
walletAddress: '0x123',
srcChainId: 1,
destChainId: 10,
srcTokenAddress: zeroAddress(),
destTokenAddress: zeroAddress(),
srcTokenAmount: '20000',
slippage: 0.5,
});
const result = await fetchBridgeQuotes(
{
walletAddress: '0x123',
srcChainId: 1,
destChainId: 10,
srcTokenAddress: zeroAddress(),
destTokenAddress: zeroAddress(),
srcTokenAmount: '20000',
slippage: 0.5,
},
signal,
);

expect(fetchWithCache).toHaveBeenCalledWith({
url: 'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x123&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&slippage=0.5',
fetchOptions: {
method: 'GET',
headers: { 'X-Client-Id': 'extension' },
signal,
},
cacheOptions: { cacheRefreshTime: 0 },
functionName: 'fetchBridgeQuotes',
Expand Down
7 changes: 6 additions & 1 deletion ui/pages/bridge/bridge.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,18 @@ export async function fetchBridgeTokens(
// Returns a list of bridge tx quotes
export async function fetchBridgeQuotes(
request: QuoteRequest,
signal: AbortSignal,
): Promise<QuoteResponse[]> {
const url = `${BRIDGE_API_BASE_URL}/getQuote?${Object.entries(request)
.map(([k, v]) => `${k}=${v}`)
.join('&')}`;
const quotes = await fetchWithCache({
url,
fetchOptions: { method: 'GET', headers: CLIENT_ID_HEADER },
fetchOptions: {
method: 'GET',
headers: CLIENT_ID_HEADER,
signal,
},
cacheOptions: { cacheRefreshTime: 0 },
functionName: 'fetchBridgeQuotes',
});
Expand Down

0 comments on commit 650bb36

Please sign in to comment.