Skip to content

CP-14673: switch Glacier calls to the core-proxy-api Glacier proxy - #4012

Merged
B0Y3R-AVA merged 4 commits into
mainfrom
boyer/cp-14673
Jul 29, 2026
Merged

CP-14673: switch Glacier calls to the core-proxy-api Glacier proxy#4012
B0Y3R-AVA merged 4 commits into
mainfrom
boyer/cp-14673

Conversation

@B0Y3R-AVA

@B0Y3R-AVA B0Y3R-AVA commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Description

Ticket: CP-14673

First slice of the mobile side of the Glacier migration: all Glacier traffic must move to the authenticated smart-routing proxy (core-proxy-api.../v1/proxy/glacier), which requires a Firebase AppCheck token (or a per-client Core API key) on every request. The old glacier-api.avax.network host is losing its EVM endpoints at the end of July.

In this PR:

  • getCoreAuthHeaders — shared per-request auth-header resolver: AppCheck token always, plus x-core-api-key when Config.CORE_API_KEY is set (dev/E2E; key lives in 1Password "front end eng" vault → "Core API keys")
  • GlacierService (legacy glacier-sdk client) now passes the resolver as an async HEADERS resolver — proxy-ready
  • .env.example documents CORE_API_KEY
  • glacierApiClient needs no change (already on appCheckFetch; URL comes from env)

Follow-ups (unblocked by core-vm-modules#452 merging — done):

  • Bump @avalabs/{evm,avalanche,bitcoin,svm}-module + vm-module-types to 4.0.0
  • ModuleManager.init: pass getAuthHeaders: getCoreAuthHeaders in runtime (required by the new module constructors)
  • Absorb the 3.10-4.0 Hypercore type additions (HypercoreSpotToken guards in token utils, NetworkVMType.HYPERCORE in publicKeys) — with unit tests for the new fallback paths

Not in this PR (ops, coordinated with BE at release time): GLACIER_URL swap in the AWS canonical env files + Bitrise; prod deploy of core-proxy-api is on-request from Feri.

Screenshots/Videos

N/A — no UI changes. Feature verification (balances, tx history, NFTs, stake flows, P/X-chain send) happens after the module bump lands, against the staging proxy.

Testing

Dev Testing (if applicable)

  • Unit tests cover the resolver (AppCheck header, optional Core API key, per-call token re-read)
  • With GLACIER_URL pointed at https://core-proxy-api.avax-test.network/v1/proxy/glacier in .env.development, Glacier-backed features (watchlist, validator lists, NFT reindex) work on a dev build; AppCheck debug tokens are honored on staging

QA Testing (if applicable)

  • Full Glacier feature pass (balances, activity, NFTs, stake) on iOS + Android once the module bump is in — will update this PR with a Bitrise build

Checklist

Please check all that apply (if applicable)

  • I have performed a self-review of my code
  • I have verified the code works
  • I have included screenshots / videos of android and ios
  • I have added testing steps
  • I have added/updated necessary unit tests
  • I have updated the documentation

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

Coverage report ✅

2/2 packages passed thresholds
Thresholds are shown inline against each package baseline.

🟢 @avalabs/core-mobile

St. Category Percentage Covered / Total
🟢 Statements 27.87% (+9.87% vs 18% ▲) 12338/44268
🟢 Branches 23.52% (+11.52% vs 12% ▲) 6057/25746
🟢 Functions 22.15% (+9.15% vs 13% ▲) 2266/10226
🟢 Lines 27.94% (+9.94% vs 18% ▲) 11814/42271

🟢 @avalabs/k2-alpine

St. Category Percentage Covered / Total
🟢 Statements 8.49% (+6.49% vs 2% ▲) 304/3578
🟢 Branches 7.90% (+6.90% vs 1% ▲) 172/2177
🟢 Functions 4.99% (+3.99% vs 1% ▲) 41/821
🟢 Lines 7.82% (+5.82% vs 2% ▲) 257/3283
Artifacts and threshold sources
  • @avalabs/core-mobile: summary core-mobile/coverage/coverage-summary.json, thresholds core-mobile/coverage-thresholds.json
  • @avalabs/k2-alpine: summary k2-alpine/coverage/coverage-summary.json, thresholds k2-alpine/coverage-thresholds.json

Source run: Mobile PR

@B0Y3R-AVA

Copy link
Copy Markdown
Contributor Author

CP-14673 device verification evidence — Glacier via core-proxy-api

Requests to core-proxy-api (Glacier proxy) — unique endpoints

 171 [proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/address/0x453d6b0b.../chains?rltoken=<redacted> -> 200
 104 [proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/networks/mainnet/blockchains/p-chain/transactions?addresses=P-avax1jsphj9g0unpdj... -> 200
  29 [proxy-proof][nitro] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/networks/mainnet/blockchains/p-chain/transactions?addresses=avax1jsphj9g0unpdjs3... -> 200 (appcheck: true)
  29 [proxy-proof][nitro] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/networks/fuji/blockchains/p-chain/transactions?addresses=fuji1jsphj9g0unpdjs3... -> 200 (appcheck: true)
   1 [proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/chains -> 401
   1 [proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/chains -> 200

Status code distribution (all core-proxy-api traffic this session)

 334 -> 200
   1 -> 401

(the 401s are the ~3-minute window where an unprovisioned Core API key was baked in for testing — removed, documented separately; all AppCheck-authenticated traffic is 200)

All Core APIs — per-host totals

 537 [proxy-proof][fetch] POST https://api.avax.network -> 200
 339 [proxy-proof][fetch] GET https://proxy-api-dev.avax.network -> 200
 336 [proxy-proof][fetch] GET https://cdn.jsdelivr.net -> 200
 276 [proxy-proof][fetch] GET https://core-proxy-api.avax-test.network -> 200
 176 [proxy-proof][fetch] POST https://proxy-api-dev.avax.network -> 200
  97 [proxy-proof][nitro] POST https://core-notification-sender-api.avax-test.network -> 200
  91 [proxy-proof][nitro] GET https://core-token-aggregator.avax-test.network -> 200
  58 [proxy-proof][nitro] GET https://core-proxy-api.avax-test.network -> 200
  56 [proxy-proof][nitro] POST https://core-profile-api.avax-test.network -> 200
  37 [proxy-proof][fetch] POST https://app.posthog.com -> 200
  18 [proxy-proof][nitro] GET https://core-balance-api.avax-test.network -> 200
  15 [proxy-proof][fetch] GET https://proxy-api-dev.avax.network -> 400
  14 [proxy-proof][fetch] GET https://ac-gateway-production-us-east-1.avacloud.io -> 200
   2 [proxy-proof][fetch] GET https://warden-mainnet.19bc5bfd-25de-4c8f-8c4c-5887a9e59ee7.net -> 200
   2 [proxy-proof][fetch] GET https://warden-avax-storage.s3.amazonaws.com -> 200
   2 [proxy-proof][fetch] GET https://em-warden-mainnet.enclave-warden.com -> 200
   2 [proxy-proof][fetch] GET https://chainstack-ava-warden-mainnet.s3.us-west-2.amazonaws.com -> 200
   2 [proxy-proof][fetch] GET https://blob-storage-mainnet.warden-avascan.info -> 200
   2 [proxy-proof][fetch] GET https://avawarden-prod.s3.amazonaws.com -> 200
   2 [proxy-proof][fetch] GET https://assets.warden-mainnet.avalanche.protofire.io -> 200
   1 [proxy-proof][fetch] GET https://raw.githubusercontent.com -> 200
   1 [proxy-proof][fetch] GET https://play.google.com -> 200
   1 [proxy-proof][fetch] GET https://core-proxy-api.avax-test.network -> 401

Verbatim raw log samples (logcat, ReactNativeJS)

07-27 13:27:41.971 20460 21071 I ReactNativeJS: '%s %s', '[13:27:41.97]', '[proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/address/0x453d6b0bB38B3b1789a80961fA9eb9AC2e00297a/chains?rltoken=<redacted> -> 200'
07-27 13:28:12.182 20460 21071 I ReactNativeJS: '%s %s', '[13:28:12.18]', '[proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/address/0x453d6b0bB38B3b1789a80961fA9eb9AC2e00297a/chains?rltoken=<redacted> -> 200'
07-27 13:28:42.488 20460 21071 I ReactNativeJS: '%s %s', '[13:28:42.48]', '[proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/address/0x453d6b0bB38B3b1789a80961fA9eb9AC2e00297a/chains?rltoken=<redacted> -> 200'
...
07-27 14:56:05.548 27190 27362 I ReactNativeJS: '%s %s', '[14:56:05.54]', '[proxy-proof][nitro] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/networks/mainnet/blockchains/p-chain/transactions?addresses=avax1jsphj9g0unpdjs354gxsdmkylducq36v3f2mrt%2Cavax1p7j4neelpc9t9nl3s7kk0mkkjjjp4dfq6q2w4d%2Cavax1yg949t2ay0ut9euq5pens80dp329fya7qx5chs%2Cavax1men3cucr6pf9gw3k5n6dsyzvt67qqmy5zcyvua%2Cavax1ssn6y6ah0y6n6v9ment574ydtqfah5cuyqa5ry%2Cavax1f64jn4xyuzt4mrcg4g0seq28n799e8uz7rlddu%2Cavax1v0mka99ck84mqgsav7dx03sanu7z75yyltzj25%2Cavax1js8efhkf580spp7saqpj0h7fskf2k9qtej97hr%2Cavax1lu2qfvjyx5uqy9hshjtcq2h4hh8sh6w0ls8cty&pageSize=100&sortOrder=desc&startTimestamp=1753642565&txTypes=AddPermissionlessDelegatorTx,AddDelegatorTx -> 200 (appcheck: true)'
07-27 14:56:05.700 27190 27362 I ReactNativeJS: '%s %s', '[14:56:05.69]', '[proxy-proof][nitro] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/networks/fuji/blockchains/p-chain/transactions?addresses=fuji1jsphj9g0unpdjs354gxsdmkylducq36vamwy05%2Cfuji1p7j4neelpc9t9nl3s7kk0mkkjjjp4dfqkjw3ej%2Cfuji1yg949t2ay0ut9euq5pens80dp329fya7v5s8m0%2Cavax1men3cucr6pf9gw3k5n6dsyzvt67qqmy5zcyvua%2Cavax1ssn6y6ah0y6n6v9ment574ydtqfah5cuyqa5ry%2Cavax1f64jn4xyuzt4mrcg4g0seq28n799e8uz7rlddu%2Cavax1v0mka99ck84mqgsav7dx03sanu7z75yyltzj25%2Cavax1js8efhkf580spp7saqpj0h7fskf2k9qtej97hr%2Cavax1lu2qfvjyx5uqy9hshjtcq2h4hh8sh6w0ls8cty&pageSize=100&sortOrder=desc&startTimestamp=1753642565&txTypes=AddPermissionlessDelegatorTx,AddDelegatorTx -> 200 (appcheck: true)'
07-27 14:56:13.695 27190 27362 I ReactNativeJS: '%s %s', '[14:56:13.69]', '[proxy-proof][fetch] GET https://core-proxy-api.avax-test.network/v1/proxy/glacier/v1/networks/mainnet/blockchains/p-chain/transactions?addresses=P-avax1jsphj9g0unpdjs354gxsdmkylducq36v3f2mrt&pageSize=100&sortOrder=desc -> 200'

@B0Y3R-AVA
B0Y3R-AVA marked this pull request as ready for review July 27, 2026 20:17
Copilot AI review requested due to automatic review settings July 27, 2026 20:17
@B0Y3R-AVA
B0Y3R-AVA requested a review from a team as a code owner July 27, 2026 20:17

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

Migrates Core Mobile’s Glacier traffic to be compatible with the authenticated core-proxy-api Glacier proxy by introducing a shared auth-header resolver (Firebase AppCheck + optional Core API key) and wiring it into both the legacy glacier-sdk client and the upgraded vm-modules runtime.

Changes:

  • Add getCoreAuthHeaders and integrate it into GlacierService and ModuleManager runtime (getAuthHeaders) for proxy-required per-request auth headers.
  • Bump @avalabs/{evm,avalanche,bitcoin,svm}-module and @avalabs/vm-module-types to 4.0.0, updating codepaths/tests for newly introduced Hypercore token/VM types.
  • Document CORE_API_KEY in .env.example and export APPCHECK_HEADER for reuse.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
yarn.lock Updates lockfile for vm-modules + types bump to 4.0.0.
packages/core-mobile/package.json Bumps vm-modules + vm-module-types dependencies to 4.0.0.
packages/core-mobile/app/vmModule/ModuleManager.ts Passes runtime.getAuthHeaders so vm-modules can auth Glacier proxy calls.
packages/core-mobile/app/utils/publicKeys.ts Adds NetworkVMType.HYPERCORE to emptyAddresses() mapping.
packages/core-mobile/app/utils/api/common/getCoreAuthHeaders.ts New per-request auth header resolver (AppCheck + optional x-core-api-key).
packages/core-mobile/app/utils/api/common/getCoreAuthHeaders.test.ts Unit tests for resolver behavior (AppCheck header, optional API key, per-call token re-read).
packages/core-mobile/app/utils/api/common/appCheckFetch.ts Exports APPCHECK_HEADER for shared usage.
packages/core-mobile/app/services/glacier/GlacierService.ts Uses async HEADERS resolver to attach proxy auth headers per request.
packages/core-mobile/app/services/balance/utils/getLocalTokenId.ts Adds guard for tokens without address (Hypercore spot token compatibility).
packages/core-mobile/app/services/balance/utils/getLocalTokenid.test.ts Adds test coverage for Hypercore spot token fallback behavior.
packages/core-mobile/app/new/features/swap/utils/getTokenAddress.ts Avoids accessing missing address for non-swappable Hypercore spot tokens.
packages/core-mobile/app/new/features/swap/utils/getTokenAddress.test.ts Adds test for Hypercore spot token returning empty address.
packages/core-mobile/app/new/features/swap/utils/buildLocalToken.ts Avoids reading balanceData.address when absent (Hypercore spot compatibility).
packages/core-mobile/app/hooks/networks/utils/getNetworkContractTokens.ts Filters out Hypercore spot tokens from contract token pipeline.
packages/core-mobile/app/hooks/browser/injectedProvider/approvalMethods.test.ts Updates enum snapshot to include newly-added RPC method.
packages/core-mobile/.env.example Documents CORE_API_KEY for dev/E2E proxy auth.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to 19
if (!('address' in token) || !token.address) {
Logger.error('Token address is missing', { token })
return fallbackTokenId
}

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.

Good catch — fixed in 1069ee1. getLocalTokenId now returns the ${type}-${symbol} fallback for TokenType.HYPERCORE_SPOT before the missing-address check, so expected addressless hypercore tokens no longer log an error; unexpected missing addresses still do. Added a test asserting Logger.error is not called for the hypercore case.

Copilot AI review requested due to automatic review settings July 28, 2026 18:49

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

Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

packages/core-mobile/app/hooks/networks/utils/getNetworkContractTokens.ts:23

  • The filter predicate only excludes TokenType.HYPERCORE_SPOT, but it still type-asserts the remaining values as NetworkContractToken. If module.getTokens() ever returns other entries without an address (or an empty address), this will violate the function’s return type and can break callers that assume contract tokens always have an address. Filter by address presence as well (and still exclude Hypercore spot tokens).
  return (tokens ?? []).filter(
    (token): token is NetworkContractToken =>
      token.type !== TokenType.HYPERCORE_SPOT
  )

Copilot AI review requested due to automatic review settings July 28, 2026 18:54

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

Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

packages/core-mobile/app/utils/api/common/getCoreAuthHeaders.test.ts:31

  • Use the shared APPCHECK_HEADER constant in the expectation instead of repeating the header string literal.
    await expect(getCoreAuthHeaders()).resolves.toEqual({
      'X-Firebase-AppCheck': 'appcheck-token'
    })

packages/core-mobile/app/utils/api/common/getCoreAuthHeaders.test.ts:43

  • Use the shared APPCHECK_HEADER constant in the expectation instead of repeating the header string literal.
    await expect(getCoreAuthHeaders()).resolves.toEqual({
      'X-Firebase-AppCheck': 'appcheck-token',
      'x-core-api-key': 'core-api-key'
    })

packages/core-mobile/app/utils/api/common/getCoreAuthHeaders.test.ts:60

  • Use the shared APPCHECK_HEADER constant in the expectation instead of repeating the header string literal.
    await expect(getCoreAuthHeaders()).resolves.toEqual({
      'X-Firebase-AppCheck': 'token-1'
    })
    await expect(getCoreAuthHeaders()).resolves.toEqual({
      'X-Firebase-AppCheck': 'token-2'
    })

packages/core-mobile/app/utils/api/common/getCoreAuthHeaders.test.ts:2

  • The test hardcodes the AppCheck header name string. Since the production code uses the exported APPCHECK_HEADER constant, importing and using it here avoids duplication and keeps the test aligned if the header name ever changes.

This issue also appears in the following locations of the same file:

  • line 29
  • line 40
  • line 55
import AppCheckService from 'services/fcm/AppCheckService'
import { getCoreAuthHeaders } from './getCoreAuthHeaders'

GLACIER_URL=
GLACIER_API_KEY=
# Core API key for core-proxy-api (dev/E2E: authorizes + bypasses rate limits; 1Password "front end eng" vault -> "Core API keys")
CORE_API_KEY=

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we call it CORE_PROXY_API_KEY? There are multiple Core Apis we use

Suggested change
CORE_API_KEY=
CORE_PROXY_API_KEY=

@B0Y3R-AVA
B0Y3R-AVA merged commit fcf0fef into main Jul 29, 2026
7 checks passed
@B0Y3R-AVA
B0Y3R-AVA deleted the boyer/cp-14673 branch July 29, 2026 13:27
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.

4 participants