From 43d7462834daf7803784566a16a221649d343db2 Mon Sep 17 00:00:00 2001 From: libby-coder Date: Sat, 22 Aug 2026 18:10:11 +0100 Subject: [PATCH 1/2] feat: add wallet network-switch guard with persistent session restore (Closes #25) Extends useWallet() with session restore that verifies Freighter's isAllowed() grant (not just a localStorage flag, so a revoked/stale session self-heals) and a live WatchWalletChanges-backed guard that detects the wrong Stellar network and guides the user to switch inside the extension, since Freighter has no API to switch networks on a dApp's behalf. NetworkGuard surfaces this app-wide, and EscrowFundingWizard now blocks progress past wallet-connect on the wrong network. Also wires WalletButton into the navbar, where it was defined but never mounted. --- .env.example | 5 + README.md | 45 ++- src/app/layout.tsx | 2 + src/components/Navbar.tsx | 3 + src/components/WalletButton.tsx | 29 +- src/components/escrow/EscrowFundingWizard.tsx | 10 +- .../escrow/steps/ConnectWalletStep.tsx | 21 +- src/components/wallet/NetworkGuard.tsx | 49 +++ src/components/wallet/index.ts | 1 + src/lib/config.ts | 10 + src/lib/test/wallet.test.tsx | 307 ++++++++++++++++++ src/lib/wallet.ts | 167 ++++++++-- 12 files changed, 605 insertions(+), 44 deletions(-) create mode 100644 src/components/wallet/NetworkGuard.tsx create mode 100644 src/components/wallet/index.ts create mode 100644 src/lib/test/wallet.test.tsx diff --git a/.env.example b/.env.example index ee0eed5..8e6538b 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,8 @@ # Base URL of the GuildWorkman API. Defaults to the hosted instance if unset. NEXT_PUBLIC_API_BASE_URL=http://localhost:8080 + +# Stellar network the wallet-connection layer expects Freighter to be on. +# One of PUBLIC | TESTNET | FUTURENET. Defaults to TESTNET if unset, matching +# the Soroban contracts' current deployment target. +NEXT_PUBLIC_STELLAR_NETWORK=TESTNET diff --git a/README.md b/README.md index dc2477c..2f7e276 100644 --- a/README.md +++ b/README.md @@ -199,14 +199,39 @@ Java backend that doesn't exist today. Rather than pretend that integration is further along than it is, the frontend currently does two honest things: -1. **A real wallet connection.** The navbar's "Connect Wallet" button - (`src/components/WalletButton.tsx`, `src/lib/wallet.ts`) uses +1. **A real, resilient wallet connection.** The navbar's "Connect Wallet" + button (`src/components/WalletButton.tsx`, `src/lib/wallet.ts`) uses `@stellar/freighter-api` to connect an actual Freighter wallet, showing a - truncated address, network (Testnet/Mainnet), and a disconnect option. If - the Freighter extension isn't installed, it shows an "Install Freighter" - hint instead of failing silently. This makes **no contract calls** — - booking, payment, and review logic are all unchanged and still go through - the backend API/Paystack. + truncated address, network (Testnet/Mainnet/Futurenet), and a disconnect + option. If the Freighter extension isn't installed, it shows an "Install + Freighter" hint instead of failing silently. This makes **no contract + calls** — booking, payment, and review logic are all unchanged and still + go through the backend API/Paystack. + + On top of the base connection, `useWallet()` (`src/lib/wallet.ts`) adds: + - **Session restore across reloads** — a localStorage flag is only a fast + hint; the actual restore verifies Freighter's own `isAllowed()` grant + before trusting it, so a session revoked inside the extension (or a + browser profile that never had it) self-heals instead of showing a + stale "connected" UI. + - **A live network-switch guard** — Freighter has no API to switch its + own network on a dApp's behalf (a deliberate security boundary), so + `useWallet` runs Freighter's `WatchWalletChanges` poller for the life + of a session and exposes `isWrongNetwork` / `expectedNetwork`. + `NetworkGuard` (`src/components/wallet/NetworkGuard.tsx`, mounted + app-wide in `src/app/layout.tsx`) shows a banner guiding the user to + switch inside Freighter and clears itself automatically once the + watcher detects the change — no reload or manual recheck required, + though a "check again" button short-circuits the wait. + `EscrowFundingWizard`'s connect-wallet step additionally **blocks + progress** past that step while on the wrong network, since funding + escrow there isn't recoverable after the fact. + - The expected network is configurable via `NEXT_PUBLIC_STELLAR_NETWORK` + (defaults to `TESTNET` — see `.env.example`). + + No new dependencies were added — `WatchWalletChanges` and `isAllowed()` + are both part of the `@stellar/freighter-api` version already in + `package.json`. 2. **An informational trust layer.** The homepage sections (`Hero`, `HowItWorks`, `StatsBand`) plus "Escrow protected" badges on worker cards and the booking flow explain in plain language what the contracts *will* do @@ -222,13 +247,15 @@ src/ components/ ui/ # design-system primitives (Button, Input, Select, Card, Badge) brand/ # the marks as components (Logo, NorthStar, AdinkraPattern) + wallet/ # NetworkGuard — app-wide wrong-network banner *.tsx # page-level and shared client components lib/ api.ts # typed API client — every backend call goes through here - config.ts # API_BASE_URL resolution + config.ts # API_BASE_URL / EXPECTED_STELLAR_NETWORK resolution types.ts # shared request/response types constants.ts # category list and skill-detail seed data - wallet.ts # useWallet() hook wrapping @stellar/freighter-api + wallet.ts # useWallet() hook — connect/session-restore/network-guard + # on top of @stellar/freighter-api public/ assets/ # images used across the app brand/ # exported logo marks (SVG masters + 4x PNGs) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e3661a2..80c1163 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,6 +7,7 @@ import Footer from "@/components/Footer"; import { ThemeProvider } from "@/components/theme"; import { NotificationProvider } from "@/components/notifications/useNotifications"; import NotificationToast from "@/components/notifications/NotificationToast"; +import { NetworkGuard } from "@/components/wallet"; import { themeScript } from "@/lib/theme"; const inter = Inter({ @@ -42,6 +43,7 @@ export default function RootLayout({ +
{children}