Skip to content

Feature/519 freighter wallet auth - #661

Open
vincentchimene wants to merge 5 commits into
StellarFlow-Network:mainfrom
vincentchimene:feature/519-freighter-wallet-auth
Open

Feature/519 freighter wallet auth#661
vincentchimene wants to merge 5 commits into
StellarFlow-Network:mainfrom
vincentchimene:feature/519-freighter-wallet-auth

Conversation

@vincentchimene

Copy link
Copy Markdown
Contributor

feat(wallet): Implement Freighter Wallet Connect & Re-authentication Hook

Closes #519


Overview

This PR delivers the complete Freighter wallet integration for the StellarFlow frontend, addressing all three technical requirements outlined in issue #519. It introduces a dedicated context
layer built on @stellar/freighter-api, a primary consumer hook, and a re-authentication hook that gracefully handles session expiry — without disrupting the existing WalletProvider
infrastructure.


Changes

src/context/FreighterWalletContext.tsx (new)

Core context layer providing Freighter-native connection state management:

  • Tracks extension state via isConnected() and getAddress() on mount and after every user interaction
  • Starts a WatchWalletChanges poller (3s interval) to detect external account switches and network changes without requiring a page reload
  • Persists the connected public key and network name in encrypted localStorage using the existing storage utility, enabling session continuity across reloads
  • Raises a sessionExpired flag when the live extension key diverges from the persisted key, signalling downstream hooks to prompt re-authentication
  • Fully SSR-safe — all window access is guarded; the provider renders cleanly during server-side rendering

src/hooks/useWallet.ts (new)

Primary consumer hook exposing a flat, ergonomic API for all wallet-aware components:

  • Surfaces publicKey, network, status, sessionExpired, and error from context state
  • Computed helpers: isConnected, isLoading, shortAddress (G...WXYZ format), networkLabel (e.g. "Testnet", "Mainnet")
  • Stable useCallback action references to prevent unnecessary child re-renders

src/hooks/useReAuth.ts (new)

Re-authentication hook for graceful session expiry handling:

  • requireAuth() resolves immediately when the session is valid (zero overhead on the happy path)
  • When the session has expired due to idle timeout or an external account switch, it transparently triggers Freighter's requestAccess() and resolves with the newly confirmed public key
  • Deduplicates concurrent calls via an in-flight ref — only one Freighter popup is ever opened regardless of how many components call requireAuth() simultaneously
  • Emits toast notifications on success (confirmed, auto-dismisses after 5s) and failure (failed, persists until dismissed)
  • Exposes isReAuthing for disabling UI controls during the re-auth flow

src/app/components/providers/FreighterWalletBoundary.tsx (new)

Thin "use client" boundary component that satisfies Next.js App Router's requirement for Context providers to be Client Components, while keeping layout.tsx a Server Component.

src/app/layout.tsx (modified)

Registers <FreighterWalletBoundary> in the provider tree, nested inside <ToastProvider> to ensure useReAuth has access to addToast.


Testing

18 tests — all passing (npm run test:wallet)

Group Tests
Connection state listener idle→disconnected, idle→connected, WatchWalletChanges lifecycle, account switch detection, connect() via requestAccess, disconnect()
Session persistence localStorage write on connect, hydration on page reload, removal on disconnect
Re-authentication fast-path (valid session), re-auth on expiry, cancel/rejection handling, concurrent call deduplication, clearSessionExpired()
Computed helpers shortAddress format, networkLabel mapping, isLoading state

Usage

// Connection state — any component inside the app tree
const { isConnected, publicKey, shortAddress, networkLabel, connect, disconnect } = useWallet();

// Guard any privileged wallet action
const { requireAuth, isReAuthing } = useReAuth();

const handleSign = async () => {
  try {
    const { publicKey } = await requireAuth();
    await signTransaction(xdr, { address: publicKey });
  } catch {
    // user cancelled re-auth — handle gracefully
  }
};

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Notes

- This implementation is additive  the existing WalletProvider in WalletContext.tsx is untouched and continues to function alongside the new context
- TypeScript strict mode: zero new errors introduced (pre-existing errors in validators/page.tsx and logs/page.tsx are unrelated to this PR)

- Integrates @stellar/freighter-api isConnected, getAddress, requestAccess, getNetwork
- WatchWalletChanges polling (3s) detects account switches and network changes
- Persists public key + network in encrypted localStorage via storage util
- Raises sessionExpired flag on external account switch
- Exposes connect, disconnect, refresh, clearSessionExpired actions
- SSR-safe: all browser APIs guarded with typeof window checks

Closes part of StellarFlow-Network#519
- Wraps FreighterWalletContext state + actions into a flat ergonomic API
- Computed helpers: isConnected, isLoading, shortAddress, networkLabel
- Re-exports stable action references to prevent unnecessary re-renders
- Full TypeScript types via UseWalletReturn interface

Closes part of StellarFlow-Network#519
- requireAuth() fast-path returns immediately when session is valid
- On session expiry or account switch, triggers Freighter requestAccess()
- Deduplicates concurrent calls via inflightRef (single popup guarantee)
- Toast notifications on success (confirmed) and failure (failed)
- Exposes isReAuthing loading flag and clearReAuthError

Closes part of StellarFlow-Network#519
- Add FreighterWalletBoundary client component to satisfy Next.js App
  Router requirement that Context providers be Client Components
- Nest FreighterWalletBoundary inside ToastProvider in layout.tsx so
  useReAuth can access addToast
- Keeps layout.tsx as a Server Component

Closes StellarFlow-Network#519
… auth implementation

- Install jest@29, jest-environment-jsdom, @testing-library/react,
  @testing-library/jest-dom, babel-jest + babel presets
- Add jest.config.js with jsdom env, @/ alias mapping, babel transform
- Add babel.test.config.js (separate from Next.js build config)
- Add npm scripts: test:wallet, test:jest

18 tests covering all 3 requirements from issue StellarFlow-Network#519:
  Requirement 1 — Freighter connection state listener:
    ✓ idle→disconnected when extension not connected
    ✓ idle→connected when extension returns valid address
    ✓ WatchWalletChanges starts on mount, stops on unmount
    ✓ Account switch detected via watcher → sessionExpired raised
    ✓ connect() triggers requestAccess when getAddress returns empty
    ✓ disconnect() clears publicKey, network, storage

  Requirement 2 — Session persistence across reloads:
    ✓ Persists public key to encrypted localStorage on connect
    ✓ Hydrates publicKey from storage on initial render
    ✓ Removes key from storage on disconnect

  Requirement 3 — Graceful re-authentication on session expiry:
    ✓ requireAuth() fast-path when session is valid
    ✓ requireAuth() triggers re-auth on sessionExpired
    ✓ requireAuth() throws + error toast on user cancel
    ✓ Concurrent calls deduplicated (single Freighter popup)
    ✓ clearSessionExpired() clears the flag

  Computed helpers:
    ✓ shortAddress: first4…last4 format
    ✓ networkLabel: TESTNET→Testnet, PUBLIC→Mainnet
    ✓ isLoading true during idle/checking states

Closes StellarFlow-Network#519
@Sadeequ

Sadeequ commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Hello @vincentchimene, Please do attend to these conflicts so I can merge.

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.

👛 Wallet-Auth | Implement Freighter Wallet Connect & Re-authentication Hook

2 participants