feat(token-prices): migrate getTokenPrices to v2 indexer endpoint#2870
feat(token-prices): migrate getTokenPrices to v2 indexer endpoint#2870aristidesstaffieri wants to merge 12 commits into
Conversation
Point getTokenPrices at INDEXER_V2_URL and pass the active network as a
query param. The v2 endpoint only supports pubnet and testnet, so skip the
request on any other network (Futurenet, custom) and return {} to avoid a
guaranteed error and Sentry noise.
Thread networkDetails through every fetchTokenPrices caller (account, send,
swap, wallets, dest asset) and update the useGetTokenPrices hook signature
accordingly. Widen the e2e token-prices route stub to match the new query
string, and update tests to assert the network argument and the
unsupported-network skip behavior.
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-7f5a86b3c48703d35f84 (SDF collaborators only — install instructions in the release description) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db91844cca
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR migrates token price fetching to the Indexer v2 token-prices endpoint by threading network context through the popup hooks and updating the shared internal API call, along with associated unit and e2e test updates.
Changes:
- Pass
networkDetailsinto token price fetch flows across Wallets/Account/Send/Swap hooks. - Update
@shared/api/internal.getTokenPricesto callINDEXER_V2_URL/token-priceswith anetworkquery param and skip unsupported networks. - Update Jest expectations and Playwright routing stubs to match the new request shape (query params).
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| extension/src/popup/views/Wallets/hooks/useGetWalletsData.tsx | Passes networkDetails into token price fetching for wallets aggregation. |
| extension/src/popup/views/Wallets/hooks/tests/useGetWalletsData.test.tsx | Updates token price spy assertions to include the network argument. |
| extension/src/popup/views/Account/hooks/useGetAccountData.tsx | Threads networkDetails into token price fetch/refresh paths. |
| extension/src/popup/locales/pt/translation.json | Adds i18n keys for auto-lock timer (currently untranslated in pt). |
| extension/src/popup/locales/en/translation.json | Adds i18n keys for auto-lock timer in English. |
| extension/src/popup/components/swap/SwapAsset/hooks/useSwapFromData.tsx | Passes networkDetails into token price fetching for swap-from asset list. |
| extension/src/popup/components/swap/SwapAmount/hooks/useGetSwapAmountData.tsx | Passes networkDetails into token price fetching for swap amount flow. |
| extension/src/popup/components/send/SendDestinationAsset/hooks/useGetDestAssetData.tsx | Passes networkDetails into token price fetching for destination asset selection. |
| extension/src/popup/components/send/SendAmount/hooks/useSendAmountData.tsx | Passes networkDetails into token price fetching for send amount flow. |
| extension/src/helpers/hooks/useGetTokenPrices.tsx | Extends token price hook API to require networkDetails and forwards network to getTokenPrices. |
| extension/src/helpers/tests/useGetTokenPrices.test.tsx | Updates hook tests to pass networkDetails and assert network-aware price requests. |
| extension/e2e-tests/helpers/stubs.ts | Updates Playwright route matching to handle token-prices query params. |
| @shared/api/internal.ts | Migrates getTokenPrices to v2 endpoint + adds network gating. |
| @shared/api/tests/internal.test.ts | Updates/extends tests for v2 URL + network query param + unsupported network short-circuit. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The token-price request is now scoped by network, but the cache still read and wrote entries keyed by publicKey alone. Reusing the same account across networks (e.g. Futurenet/Testnet then Mainnet) could serve the other network's cached prices — or the empty result cached for an unsupported network — for up to the 3-minute TTL, leaving USD values missing or wrong. Nest tokenPrices under [network][publicKey], matching the existing pattern used by balanceData, historyData, homeDomains, and collections. Thread networkDetails through saveTokenPrices and clearBalancesForAccount, and update both cache readers (useGetTokenPrices and AssetDetail) to look up by network. Add a regression test asserting a populated PUBLIC cache is not reused for a TESTNET fetch on the same account.
getTokenPrices was typed to accept only NETWORKS, but the implementation deliberately accepts any network string and short-circuits anything other than pubnet/testnet. Typing the parameter as NetworkDetails["network"] reflects that behavior and removes the unsafe `as NETWORKS` cast at the useGetTokenPrices call site (and its now-unused import).
getTokenPrices filters out LP IDs and contract-ID issuers before calling
the indexer. When that leaves nothing, it was still POSTing {tokens: []},
an unnecessary call that risks a 4xx the hook would surface as an error.
Return {} early when filteredTokens is empty.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1723ea671
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
A custom network sharing the pubnet/testnet passphrase is stored with
network "STANDALONE", but isMainnet() (passphrase-based) still routes it
into price loading. The previous guard keyed off networkDetails.network and
returned {} for STANDALONE, regressing USD prices on custom Mainnet/Testnet
configs that previously fetched them.
Pass networkDetails into getTokenPrices and derive the price network from
networkPassphrase (pubnet -> PUBLIC, testnet -> TESTNET, else skip), so
custom networks resolve to the correct supported network and query the
endpoint with the right network param.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5cb7d2e992
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The request network is derived from the passphrase, but the cache still
keyed entries by networkDetails.network — the shared "STANDALONE" value for
every custom network. Within the cache TTL, switching the same account
between two custom networks (pubnet/testnet/unsupported) made useCache:true
reuse the stale {} or prices and skip the correctly parameterized v2 request,
so USD values went missing or came from the wrong network.
Key tokenPrices by networkPassphrase, which is exactly what determines the
price response. Networks sharing a passphrase share a cache entry (correct);
custom pubnet vs custom testnet no longer collide; switching from an
unsupported custom network to a supported one is now a cache miss and fetches.
Add the `use_token_prices_v2` Amplitude Experiment flag to toggle the
token-prices endpoint between v1 (`INDEXER_URL`) and v2
(`INDEXER_V2_URL`) at runtime, so v2 can be rolled back without a
release.
- Register `use_token_prices_v2` as a boolean flag in the remoteConfig
duck, defaulting to `true` (v2). It survives a missing flag, a null
Experiment client, and a rejected fetch. Add `tokenPricesV2Selector`.
- Branch `getTokenPrices` on a `useV2` param (default `true`): v2 keeps
the passphrase-derived network, unsupported-network/empty-token skips,
and `?network=` query param; v1 restores the original endpoint with no
network param and no skips. Token filtering and fetch/error handling
stay shared.
- Read the flag in `useGetTokenPrices` from the store at call time
(`store.getState()`) rather than a render-captured selector value, so a
freshly resolved flag isn't missed when the fetch runs inside a
long-lived async flow that closed over the stale default.
- Update the maintenanceMode preloaded state for the new required key;
add v1-endpoint and flag-resolution test coverage.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cc3040d5d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The useGetTokenPrices hook now calls getTokenPrices with a third `useV2` argument, resolved from the remoteConfig store (default `true` via makeDummyStore). Since toHaveBeenCalledWith matches the full argument list, the existing two-argument expectations failed even though the hook behaved correctly. Add the explicit `true` to the affected assertions in useGetTokenPrices.test.tsx (MAINNET, TESTNET, and customTestnet cases). Using the literal value rather than a matcher also documents that the default store resolves to the v2 endpoint.
|
Maybe I'm just misunderstanding, but I expected there to be token prices when I switch to Testnet. I don't see token prices and I'm actually not seeing a call to the token-prices endpoint. Mainnet works as expected. Is this intended? |
| const priceNetwork = { | ||
| [Networks.PUBLIC]: NETWORKS.PUBLIC, | ||
| [Networks.TESTNET]: NETWORKS.TESTNET, | ||
| }[networkDetails.networkPassphrase]; |
There was a problem hiding this comment.
Passphrase → price-network map could live with the other passphrase maps
Suggestion — not a merge blocker. Purely a maintainability/cleanup nit; behavior is correct as written.
TL;DR: The logic that decides which network a price request targets is written inline here, but the codebase already has a dedicated home for "given a network passphrase, what is it" mappings. Pulling this one alongside the others gives the set of price-supported networks a single, discoverable source of truth — so the next person adding or removing a supported network changes it in one place instead of hunting for an anonymous literal buried in the API layer.
Detailed explanation (for agents)
Root cause: the passphrase→network lookup is an anonymous object literal built (and re-allocated) on every call, indexed once by passphrase:
freighter/@shared/api/internal.ts
Lines 623 to 626 in cbe24c1
The same module that defines NetworkDetails already hosts a sibling passphrase map, PASSPHRASE_TO_NETWORK_NAME:
freighter/@shared/constants/stellar.ts
Lines 76 to 80 in cbe24c1
It's not a drop-in reuse — PASSPHRASE_TO_NETWORK_NAME maps to NETWORK_NAMES ("Main Net") whereas the price path needs the NETWORKS enum value ("PUBLIC"/"TESTNET") for the query param. But the two are the same kind of map, and the set of price-supported networks is exactly the policy most likely to change (mainnet-only rollout, adding a third network). Today that policy is split: the supported set lives in this inline literal, and there's no compiler link tying it to the other map.
Suggested fix: lift it to a module-level constant in @shared/constants/stellar.ts, next to PASSPHRASE_TO_NETWORK_NAME:
// Networks the v2 token-prices endpoint can serve, keyed by passphrase.
export const PASSPHRASE_TO_PRICE_NETWORK: Partial<Record<string, NETWORKS>> = {
[Networks.PUBLIC]: NETWORKS.PUBLIC,
[Networks.TESTNET]: NETWORKS.TESTNET,
};then in getTokenPrices:
const priceNetwork = PASSPHRASE_TO_PRICE_NETWORK[networkDetails.networkPassphrase];
if (!priceNetwork) {
return {};
}This also drops the per-call object allocation on a path that runs on every balance refresh and on each Send/Swap amount keystroke (useSendAmountData / useGetSwapAmountData), and puts the "which networks have prices" decision next to the rest of the network metadata.
There was a problem hiding this comment.
hey good call ty, this is fixed in 41f0811
even though the API supports it and I wired the action to allow for it, it retained this gate for main net only. https://github.com/stellar/freighter/blob/master/extension/src/popup/views/Account/hooks/useGetAccountData.tsx#L118 Since it already worked that way I didn't change but we could remove this gate now if we want to start supporting testnet prices. |
…d constants The passphrase→NETWORKS map that getTokenPrices uses to derive the price-request network was an inline anonymous literal in the API layer. It was the codebase's only passphrase→NETWORKS mapping, so the set of price-supported networks had no discoverable home. Move it to @shared/constants/stellar.ts as PASSPHRASE_TO_PRICE_NETWORK, beside the existing PASSPHRASE_TO_NETWORK_NAME map, giving the supported price networks a single source of truth. Behavior is unchanged.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c83f22971a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
With v2 enabled, getTokenPrices requests /token-prices?network=<network> instead of a bare /token-prices. Playwright globs must match the entire URL, so the "**/token-prices" route in stubSendTokenPrices no longer intercepted the request — the two Mainnet send-workflow tests could hit the real indexer or time out instead of using deterministic stub prices. Update the glob to "**/token-prices*", matching the v2 query-bearing URL and the existing stubTokenPrices convention in helpers/stubs.ts.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c70dbdd58
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Ah gotcha. No need to change functionality. Maybe worth a comment in the code |
Summary
Migrates Freighter's token-price lookups from the v1 indexer endpoint to the v2
token-pricesendpoint. The v2 endpoint is network-scoped, sogetTokenPricesnow takes the active network and appends it as anetworkquery param.The v2 endpoint only supports pubnet and testnet — Futurenet and custom networks are rejected upstream. Rather than fire a request that's guaranteed to fail, we short-circuit on unsupported networks and return an empty result, avoiding both the error and the Sentry noise it generated.
Feature flag
The v1↔v2 switch is gated behind the
use_token_prices_v2Amplitude Experiment boolean flag, so the rollout can be reversed without a release:trueininitialState, so it survives a missing flag, a null Experiment client (e.g. no deployment key in dev), and a failed fetch.INDEXER_URL, nonetworkparam, no unsupported-network/empty-token skips (the pre-migration behavior). Onlyon/true/enabled/yescount as on.{key:'off'}with novalue, which falls back to thetruedefault (v2). To force v1, serve an explicit variant value (e.g.disabled).What's in this PR
@shared/api/internal.ts—getTokenPricesnow requires anetworkarg, targetsINDEXER_V2_URL, appends?network=, and returns{}early on unsupported networks.extension/src/helpers/hooks/useGetTokenPrices.tsx—fetchDatatakesnetworkDetailsand forwards the network togetTokenPrices.Account,Send,Swap,Wallets,DestAssethooks — threadnetworkDetailsthrough everyfetchTokenPricescaller.extension/e2e-tests/helpers/stubs.ts— widen the token-prices route stub to**/token-prices*so it matches the new query string.@shared/api/__tests__/internal.test.ts— new tests asserting the v2 endpoint/network param and the unsupported-network skip behavior.useGetTokenPrices.test.tsx,useGetWalletsData.test.tsx— updated to pass/assert the network argument.Test plan
yarn test)Notes
en/pttranslation.jsoneach gain two auto-lock-timer strings from the i18n extraction script — incidental, unrelated to the token-prices change.