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
1 change: 1 addition & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Changed Plasma native token ([#7939](https://github.com/MetaMask/core/pull/7939))
- `searchTokens` now returns an optional `error` field when requests fail, allowing consumers to detect and handle search failures instead of silently receiving empty results ([#7938](https://github.com/MetaMask/core/pull/7938))

## [99.3.2]

Expand Down
56 changes: 48 additions & 8 deletions packages/assets-controllers/src/token-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ describe('Token service', () => {
});
});

it('should return empty array if the fetch fails with a network error', async () => {
it('should return empty array with error if the fetch fails with a network error', async () => {
const searchQuery = 'USD';
nock(TOKEN_END_POINT_API)
.get(
Expand All @@ -648,10 +648,14 @@ describe('Token service', () => {

const result = await searchTokens([sampleCaipChainId], searchQuery);

expect(result).toStrictEqual({ count: 0, data: [] });
expect(result).toStrictEqual({
count: 0,
data: [],
error: expect.stringContaining('Example network error'),
});
});

it('should return empty array if the fetch fails with 400 error', async () => {
it('should return empty array with error if the fetch fails with 400 error', async () => {
const searchQuery = 'USD';
nock(TOKEN_END_POINT_API)
.get(
Expand All @@ -662,10 +666,14 @@ describe('Token service', () => {

const result = await searchTokens([sampleCaipChainId], searchQuery);

expect(result).toStrictEqual({ count: 0, data: [] });
expect(result).toStrictEqual({
count: 0,
data: [],
error: expect.stringContaining("Fetch failed with status '400'"),
});
});

it('should return empty array if the fetch fails with 500 error', async () => {
it('should return empty array with error if the fetch fails with 500 error', async () => {
const searchQuery = 'USD';
nock(TOKEN_END_POINT_API)
.get(
Expand All @@ -676,7 +684,35 @@ describe('Token service', () => {

const result = await searchTokens([sampleCaipChainId], searchQuery);

expect(result).toStrictEqual({ count: 0, data: [] });
expect(result).toStrictEqual({
count: 0,
data: [],
error: expect.stringContaining("Fetch failed with status '500'"),
});
});

it('should return error for malformed API response', async () => {
const searchQuery = 'USD';
const malformedResponse = {
count: 5,
// Missing 'data' array - this is malformed
someOtherField: 'value',
};

nock(TOKEN_END_POINT_API)
.get(
`/tokens/search?networks=${encodeURIComponent(sampleCaipChainId)}&query=${searchQuery}&limit=10&includeMarketData=false&includeRwaData=true`,
)
.reply(200, malformedResponse)
.persist();

const result = await searchTokens([sampleCaipChainId], searchQuery);

expect(result).toStrictEqual({
count: 0,
data: [],
error: 'Unexpected API response format',
});
});

it('should handle empty search results', async () => {
Expand Down Expand Up @@ -731,8 +767,12 @@ describe('Token service', () => {

const result = await searchTokens([sampleCaipChainId], searchQuery);

// Non-array responses should be converted to empty object with count 0
expect(result).toStrictEqual({ count: 0, data: [] });
// Non-array responses should be converted to empty object with count 0 and error message
expect(result).toStrictEqual({
count: 0,
data: [],
error: 'Unexpected API response format',
});
});

it('should handle supported CAIP format chain IDs', async () => {
Expand Down
11 changes: 5 additions & 6 deletions packages/assets-controllers/src/token-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ type SearchTokenOptions = {
* @param options.limit - The maximum number of results to return.
* @param options.includeMarketData - Optional flag to include market data in the results (defaults to false).
* @param options.includeRwaData - Optional flag to include RWA data in the results (defaults to false).
* @returns Object containing count and data array. Returns { count: 0, data: [] } if request fails.
* @returns Object containing count, data array, and an optional error message if the request failed.
*/
export async function searchTokens(
chainIds: CaipChainId[],
Expand All @@ -214,7 +214,7 @@ export async function searchTokens(
includeMarketData = false,
includeRwaData = true,
}: SearchTokenOptions = {},
): Promise<{ count: number; data: TokenSearchItem[] }> {
): Promise<{ count: number; data: TokenSearchItem[]; error?: string }> {
const tokenSearchURL = getTokenSearchURL({
chainIds,
query,
Expand All @@ -236,11 +236,10 @@ export async function searchTokens(
}

// Handle non-expected responses
return { count: 0, data: [] };
return { count: 0, data: [], error: 'Unexpected API response format' };
} catch (error) {
// Handle 400 errors and other failures by returning count 0 and empty array
console.log('Search request failed:', error);
return { count: 0, data: [] };
const errorMessage = error instanceof Error ? error.message : String(error);
return { count: 0, data: [], error: errorMessage };
}
}

Expand Down
Loading