Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
45323d4
Feature/move history fetch to bg (#2273)
piyalbasu Oct 7, 2025
b61ec65
check for updated appdata before showing password modal (#2300)
piyalbasu Oct 8, 2025
91a49a5
stringify errors rather than using `cause` (#2302)
piyalbasu Oct 9, 2025
cc130fc
Feature/move icons to own hook (#2308)
piyalbasu Oct 15, 2025
c50cbc1
skip blockaid scan on first fetch of account-balances (#2310)
piyalbasu Oct 17, 2025
89b00b0
Merge branch 'master' into release/5.36.0
piyalbasu Oct 23, 2025
47203df
Dropdown menu option to copy wallet address (#2316)
leofelix077 Oct 27, 2025
3da9b6b
scroll on long strings; pretty print json (#2320)
piyalbasu Oct 27, 2025
9218e3f
Merge branch 'master' into release/5.36.0
aristidesstaffieri Oct 29, 2025
077117e
re-searching so should abort any in flight API requests (#2323)
piyalbasu Oct 30, 2025
45f8bd6
[FEATURE] new send/swap navigation flow (#2353)
aristidesstaffieri Nov 5, 2025
1779c51
[FEATURE] adds send and swap buttons to asset detail view (#2351)
aristidesstaffieri Nov 7, 2025
5199754
only fetch asset list data if needed (#2369)
piyalbasu Nov 7, 2025
cdafa95
[BUG] SAC token management improvements (#2374)
aristidesstaffieri Nov 10, 2025
85014fb
Merge branch 'master' into release/5.36.0
piyalbasu Nov 11, 2025
5e591ce
Feature/cache token prices (#2373)
piyalbasu Nov 11, 2025
6268562
load backend settings async on Account view (#2381)
piyalbasu Nov 13, 2025
4f2b5c6
Feature/use ledger key for home domains (#2363)
piyalbasu Nov 14, 2025
9a0dd5e
update version numbers for release
piyalbasu Nov 17, 2025
151770c
Eadd error handling for soroswap tokenlist.json fail state
leofelix077 Nov 18, 2025
4849a32
extract schema into constant
leofelix077 Nov 18, 2025
4859b8e
simplify fulfilled checks
leofelix077 Nov 19, 2025
d213ee8
remove timeout and fix errors returned
leofelix077 Nov 19, 2025
0aed2a4
rm unnecessary calls to make flows even faster (#2391)
piyalbasu Nov 19, 2025
1e8117a
makes send swap buttons stay in the container in full screen mode (#2…
piyalbasu Nov 19, 2025
9525ce1
use cached token list lookup on add asset screen
leofelix077 Nov 21, 2025
6799190
use cached assets lookup
leofelix077 Nov 21, 2025
8d906f5
Merge branch 'master' into bugfix/unblock-asset-list-on-429
leofelix077 Nov 21, 2025
3db8b81
simplify caching logic for lookup assets
leofelix077 Nov 21, 2025
e003ad6
Merge branch 'bugfix/unblock-asset-list-on-429' of github.com:stellar…
leofelix077 Nov 21, 2025
eeaddf5
simplify caching logic for lookup assets
leofelix077 Nov 21, 2025
22b1eec
merge 5.36 into branch
leofelix077 Nov 21, 2025
bd6034d
Merge branch 'master' into release/5.36.0
piyalbasu Nov 21, 2025
b57acbf
Merge branch 'release/5.36.0' of github.com:stellar/freighter into bu…
leofelix077 Nov 21, 2025
48ca0e7
update getTokenFromTokenList
leofelix077 Nov 21, 2025
4927799
update token list fetch parallel
leofelix077 Nov 21, 2025
ed9ca12
update token list fetch parallel
leofelix077 Nov 21, 2025
607bb7c
remove manual definition of verified tokens
leofelix077 Nov 21, 2025
6725607
revert to parallel fetch on getAssets
leofelix077 Nov 21, 2025
a3cc0a7
revert to parallel fetch on getAssets
leofelix077 Nov 21, 2025
dc8184d
revert to parallel fetch on getAssets
leofelix077 Nov 21, 2025
663c8d5
update tests for error handling
leofelix077 Nov 21, 2025
b658a3f
update tests for error handling
leofelix077 Nov 21, 2025
4c9da4f
remove deprecated package
leofelix077 Nov 21, 2025
884726d
Merge branch 'release/5.37.0' of github.com:stellar/freighter into bu…
leofelix077 Nov 26, 2025
1d9880a
add tests for cached list and comments on functions
leofelix077 Nov 26, 2025
de381a6
fix tests
leofelix077 Nov 26, 2025
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
345 changes: 345 additions & 0 deletions @shared/api/helpers/__tests__/token-list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
import { getCombinedAssetListData } from "../token-list";
import {
MAINNET_NETWORK_DETAILS,
TESTNET_NETWORK_DETAILS,
NETWORKS,
} from "@shared/constants/stellar";
import {
DEFAULT_ASSETS_LISTS,
AssetsListItem,
} from "@shared/constants/soroban/asset-list";

// Mock Sentry captureException
jest.mock("@sentry/browser", () => ({
captureException: jest.fn(),
}));

import { captureException } from "@sentry/browser";

describe("getCombinedAssetListData", () => {
beforeEach(() => {
jest.clearAllMocks();
// Mock fetch globally
global.fetch = jest.fn() as jest.Mock;
});

it("should return cached asset lists when provided and not empty", async () => {
const cachedAssetLists = [
{
name: "Cached Asset List",
provider: "Cached Provider",
description: "Cached Description",
version: "1.0.0",
network: "mainnet",
assets: [
{
code: "CACHED",
issuer: "GCACHED123",
contract: "CCACHED456",
domain: "cached.com",
icon: "https://cached.com/icon.png",
decimals: 7,
},
],
},
];

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
cachedAssetLists,
});

// Should return cached lists without fetching
expect(result).toEqual(cachedAssetLists);
expect(result.length).toBe(1);
expect(global.fetch).not.toHaveBeenCalled();
});

it("should fetch asset lists when cachedAssetLists is empty", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [],
};

(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
cachedAssetLists: [],
});

// Should fetch lists when cache is empty
expect(global.fetch).toHaveBeenCalled();
expect(result.length).toBeGreaterThan(0);
});

it("should fetch asset lists when cachedAssetLists is undefined", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [],
};

(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
cachedAssetLists: undefined,
});

// Should fetch lists when cache is undefined
expect(global.fetch).toHaveBeenCalled();
expect(result.length).toBeGreaterThan(0);
});

it("should handle 429 rate limit error gracefully and continue with other lists", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [
{
code: "TEST",
issuer: "GABCDEF123",
contract: "C123456",
domain: "test.com",
icon: "https://test.com/icon.png",
decimals: 7,
},
],
};

// Mock fetch to return 429 for Soroswap URL and success for others
(global.fetch as jest.Mock).mockImplementation((url: string) => {
if (url.includes("soroswap/token-list")) {
return Promise.resolve({
ok: false,
status: 429,
statusText: "Too Many Requests",
});
}
// Return success for other URLs
return Promise.resolve({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
});

// Should return successful lists (excluding the 429 one)
expect(result.length).toBeGreaterThan(0);
expect(result).not.toContain(null);
expect(result).not.toContain(undefined);

// Verify that captureException was called for the 429 error
expect(captureException).toHaveBeenCalledWith(
expect.stringContaining("Failed to load asset list"),
);
expect(captureException).toHaveBeenCalledWith(
expect.stringContaining("(429)"),
);
});

it("should handle network errors gracefully", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [],
};

// Mock fetch to throw network error for Soroswap URL
(global.fetch as jest.Mock).mockImplementation((url: string) => {
if (url.includes("soroswap/token-list")) {
return Promise.reject(new Error("Network error"));
}
return Promise.resolve({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
});

// Should return successful lists (excluding the failed one)
expect(result.length).toBeGreaterThan(0);
expect(result).not.toContain(null);

// Verify that captureException was called for the network error
expect(captureException).toHaveBeenCalledWith(
expect.stringContaining("Failed to load asset list"),
);
});

it("should handle 500 server errors gracefully", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [],
};

// Mock fetch to return 500 for Soroswap URL
(global.fetch as jest.Mock).mockImplementation((url: string) => {
if (url.includes("soroswap/token-list")) {
return Promise.resolve({
ok: false,
status: 500,
statusText: "Internal Server Error",
});
}
return Promise.resolve({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
});

// Should return successful lists (excluding the 500 one)
expect(result.length).toBeGreaterThan(0);
expect(result).not.toContain(null);

// Verify that captureException was called
expect(captureException).toHaveBeenCalledWith(
expect.stringContaining("Failed to load asset list"),
);
expect(captureException).toHaveBeenCalledWith(
expect.stringContaining("(500)"),
);
});

it("should handle JSON parsing errors gracefully", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [],
};

// Mock fetch to return invalid JSON for Soroswap URL
(global.fetch as jest.Mock).mockImplementation((url: string) => {
if (url.includes("soroswap/token-list")) {
return Promise.resolve({
ok: true,
status: 200,
json: async () => {
throw new Error("Invalid JSON");
},
});
}
return Promise.resolve({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
});

// Should return successful lists (excluding the invalid JSON one)
expect(result.length).toBeGreaterThan(0);
expect(result).not.toContain(null);

// Verify that captureException was called for JSON parsing error
expect(captureException).toHaveBeenCalledWith(
expect.stringContaining("Failed to parse asset list JSON"),
);
});

it("should return all successful lists when all requests succeed", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "mainnet",
assets: [],
};

// Mock all fetches to succeed
(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});

const result = await getCombinedAssetListData({
networkDetails: MAINNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
});

// Should return all enabled lists
const enabledLists = DEFAULT_ASSETS_LISTS[NETWORKS.PUBLIC].filter(
(list: AssetsListItem) => list.isEnabled,
);
expect(result.length).toBe(enabledLists.length);
expect(captureException).not.toHaveBeenCalled();
});

it("should handle testnet network correctly", async () => {
const mockSuccessResponse = {
name: "Test Asset List",
provider: "Test Provider",
description: "Test Description",
version: "1.0.0",
network: "testnet",
assets: [],
};

(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockSuccessResponse,
});

const result = await getCombinedAssetListData({
networkDetails: TESTNET_NETWORK_DETAILS,
assetsLists: DEFAULT_ASSETS_LISTS,
});

expect(result.length).toBeGreaterThan(0);
});
});
2 changes: 0 additions & 2 deletions @shared/api/helpers/getIconFromTokenList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
AssetListReponseItem,
AssetListResponse,
} from "@shared/constants/soroban/asset-list";
import { NetworkDetails } from "@shared/constants/stellar";
import { getCanonicalFromAsset } from "@shared/helpers/stellar";

import { sendMessageToBackground } from "./extensionMessaging";
Expand All @@ -14,7 +13,6 @@ export const getIconFromTokenLists = async ({
code,
assetsListsData,
}: {
networkDetails: NetworkDetails;
issuerId?: string;
contractId?: string;
code: string;
Expand Down
Loading