Skip to content

feat(analytics): use the seed-derived auth pubkey as the Amplitude/Sentry user_id#939

Open
piyalbasu wants to merge 2 commits into
mainfrom
feat/analytics-auth-user-id
Open

feat(analytics): use the seed-derived auth pubkey as the Amplitude/Sentry user_id#939
piyalbasu wants to merge 2 commits into
mainfrom
feat/analytics-auth-user-id

Conversation

@piyalbasu

Copy link
Copy Markdown
Contributor

TL;DR

Sets the wallet's seed-derived auth public key as the Amplitude and Sentry user_id, replacing the random per-install id — and migrates existing users to it automatically. The mobile counterpart to the extension change (stellar/freighter): the same stable, cross-platform id the backend uses (sub), byte-identical to the extension's.

Only the public key hex is read/persisted/emitted — the private keypair never leaves getAuthKeypair. No new data-collection surface.

Migration caveat: events already sent under a user's old random id are not retroactively relabeled — they stay under the old id but are device-id-linked to the auth id in the identity graph. The auth pubkey is canonical going forward. (Standard identity-switch behavior.)

Branches off main — independent of the in-flight analytics schema slices (trivial future merge; both touch services/analytics/).

Implementation details (for agents/reviewers)

What changed:

  • src/services/auth/getAuthUserId.tsgetAuthUserId(): Promise<string | null>: the public auth key hex via getAuthKeypair() (keypair.rawPublicKey().toString("hex")), null when locked. The keypair never escapes.
  • src/services/analytics/user.tsgetUserId() now resolves the auth id first (persisting/overwriting the stored id) with the existing random/session path preserved verbatim as the locked-state fallback. The existing identifyUser() (dedup + amplitude.setUserId) reconciles/migrates automatically: an existing user's stored random id → auth id, setUserId fires once.
  • src/components/App.tsx — no change needed; its Sentry setUser already sources id from getUserId(), so it adopts the auth id (self-heals to the persisted id on the launch after first reconcile).

Verification: TDD, per-task spec+quality reviews + a whole-branch final review (verdict: ready to merge, no Critical/Important). getAuthUserId 2/2 + auth suite 55; user.ts 4/4 + full suite 2681; lint:ts clean. A test asserts the migration overwrite and that the random fallback path is unchanged when locked; the "never leaks the keypair" property is verified.

Follow-ups (deferred, non-blocking):

  • Spec's wallet-wipe/re-onboard reset-clearing of the persisted id isn't implemented (public-only, self-heals on next unlock).
  • App.tsx sets Sentry's id once at mount, so a mid-session unlock lags Sentry to the next launch (Amplitude reconciles via identifyUser); pre-existing.
  • Optional: skip the redundant same-value AsyncStorage write when the stored id already equals the auth id.

Depends on the shipped deriveAuthKeypair / getAuthKeypair (src/services/auth). Extension counterpart: stellar/freighter.

piyalbasu and others added 2 commits July 17, 2026 11:15
getAuthUserId() returns the auth keypair's public key hex for
analytics identification, or null when the session is locked. Only
the public hex ever leaves getAuthKeypair — private key material is
never exposed.
…ntry)

getUserId() now prefers the seed-derived auth id (services/auth/getAuthUserId)
when the session is unlocked, persisting/overwriting the stored
METRICS_USER_ID so existing users migrate off the random id. Falls back to
the existing stored/random id when locked. identifyUser()'s dedup and
Amplitude setUserId, plus App.tsx's Sentry.setUser, both already resolve
through getUserId() so they follow the auth id automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 17, 2026 16:45

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

Uses the seed-derived auth public key as the stable analytics and Sentry identity.

Changes:

  • Adds public auth-ID derivation.
  • Persists the auth ID with random-ID fallback.
  • Adds derivation and migration tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/services/auth/getAuthUserId.ts Derives the public auth ID.
src/services/analytics/user.ts Prefers and persists the auth ID.
__tests__/services/auth/getAuthUserId.test.ts Tests auth-ID resolution.
__tests__/services/analytics/user.test.ts Tests persistence and fallback behavior.

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

* 3. Use session-only ID if storage fails
*/
export const getUserId = async (): Promise<string> => {
const authId = await getAuthUserId();
import { useAnalyticsStore } from "ducks/analytics";
import { STORAGE_KEYS, DEBUG_CONFIG } from "services/analytics/constants";
import { isInitialized } from "services/analytics/core";
import { getAuthUserId } from "services/auth/getAuthUserId";
@github-actions

Copy link
Copy Markdown
Contributor

iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-f838568e336849a5d456 (SDF collaborators only — install instructions in the release description)

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.

2 participants