Skip to content

feat(token-prices): migrate getTokenPrices to v2 indexer endpoint#2870

Open
aristidesstaffieri wants to merge 12 commits into
masterfrom
feat/token-prices-v2
Open

feat(token-prices): migrate getTokenPrices to v2 indexer endpoint#2870
aristidesstaffieri wants to merge 12 commits into
masterfrom
feat/token-prices-v2

Conversation

@aristidesstaffieri

@aristidesstaffieri aristidesstaffieri commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates Freighter's token-price lookups from the v1 indexer endpoint to the v2 token-prices endpoint. The v2 endpoint is network-scoped, so getTokenPrices now takes the active network and appends it as a network query 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_v2 Amplitude Experiment boolean flag, so the rollout can be reversed without a release:

  • Default is v2. The flag defaults to true in initialState, so it survives a missing flag, a null Experiment client (e.g. no deployment key in dev), and a failed fetch.
  • Serving the flag a non-"on" value rolls back to v1INDEXER_URL, no network param, no unsupported-network/empty-token skips (the pre-migration behavior). Only on/true/enabled/yes count as on.
  • The flag is read from the Redux store at fetch call time, not a render-captured value, so a freshly-resolved flag is applied even when the price fetch runs inside a long-lived async flow.

⚠️ Operator note: a plain Amplitude on/off toggle serializes "off" as {key:'off'} with no value, which falls back to the true default (v2). To force v1, serve an explicit variant value (e.g. disabled).

What's in this PR

  • @shared/api/internal.tsgetTokenPrices now requires a network arg, targets INDEXER_V2_URL, appends ?network=, and returns {} early on unsupported networks.
  • extension/src/helpers/hooks/useGetTokenPrices.tsxfetchData takes networkDetails and forwards the network to getTokenPrices.
  • Account, Send, Swap, Wallets, DestAsset hooks — thread networkDetails through every fetchTokenPrices caller.
  • 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

  • Unit tests pass locally (yarn test)
  • CI green
  • Manual smoke test: prices load on pubnet + testnet; no request fired (and no Sentry error) on Futurenet

Notes

  • en/pt translation.json each gain two auto-lock-timer strings from the i18n extraction script — incidental, unrelated to the token-prices change.

  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.
@aristidesstaffieri aristidesstaffieri self-assigned this Jun 24, 2026
Copilot AI review requested due to automatic review settings June 24, 2026 17:31
Comment thread extension/src/popup/locales/en/translation.json
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-7f5a86b3c48703d35f84 (SDF collaborators only — install instructions in the release description)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread extension/src/helpers/hooks/useGetTokenPrices.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 networkDetails into token price fetch flows across Wallets/Account/Send/Swap hooks.
  • Update @shared/api/internal.getTokenPrices to call INDEXER_V2_URL/token-prices with a network query 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.

Comment thread extension/src/popup/locales/pt/translation.json Outdated
Comment thread @shared/api/internal.ts
Comment thread @shared/api/internal.ts
aristidesstaffieri and others added 4 commits June 24, 2026 11:44
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread @shared/api/internal.ts Outdated
  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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread extension/src/helpers/hooks/useGetTokenPrices.tsx Outdated
  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.
@aristidesstaffieri aristidesstaffieri requested a review from a team June 25, 2026 18:48
  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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread extension/src/helpers/__tests__/useGetTokenPrices.test.tsx
  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.
@piyalbasu

Copy link
Copy Markdown
Contributor

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?

Comment thread @shared/api/internal.ts Outdated
Comment on lines +623 to +626
const priceNetwork = {
[Networks.PUBLIC]: NETWORKS.PUBLIC,
[Networks.TESTNET]: NETWORKS.TESTNET,
}[networkDetails.networkPassphrase];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

const priceNetwork = {
[Networks.PUBLIC]: NETWORKS.PUBLIC,
[Networks.TESTNET]: NETWORKS.TESTNET,
}[networkDetails.networkPassphrase];

The same module that defines NetworkDetails already hosts a sibling passphrase map, PASSPHRASE_TO_NETWORK_NAME:

export const PASSPHRASE_TO_NETWORK_NAME: Record<string, string> = {
[Networks.PUBLIC]: NETWORK_NAMES.PUBNET,
[Networks.TESTNET]: NETWORK_NAMES.TESTNET,
[FUTURENET_NETWORK_DETAILS.networkPassphrase]: NETWORK_NAMES.FUTURENET,
};

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey good call ty, this is fixed in 41f0811

@aristidesstaffieri

Copy link
Copy Markdown
Contributor Author

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?

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread @shared/api/internal.ts
  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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread extension/src/popup/ducks/remoteConfig.ts
@piyalbasu

Copy link
Copy Markdown
Contributor

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?

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.

Ah gotcha. No need to change functionality. Maybe worth a comment in the code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants