Skip to content

Commit e85e19b

Browse files
committed
feat(wallet): persist last-used wallet for silent reconnect (#222)
1 parent e29a4a6 commit e85e19b

1 file changed

Lines changed: 52 additions & 15 deletions

File tree

frontend/src/features/wallet/context/WalletContext.tsx

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ const WalletContext = createContext<WalletContextValue | null>(null)
4747

4848
const LS_WALLET_KEY = 'niffyinsure:lastWalletId'
4949
const LS_NETWORK_KEY = 'niffyinsure:appNetwork'
50+
const LS_WALLET_SESSION = 'niffyinsur-wallet-session-v1'
51+
52+
interface WalletSession {
53+
walletId: WalletId;
54+
publicKey: string;
55+
}
5056

5157
function kitNetworkFor(app: AppNetwork): Networks {
5258
if (app === 'mainnet') return Networks.PUBLIC
@@ -94,6 +100,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
94100
setActiveWalletId(null)
95101
setWalletNetwork(null)
96102
setWalletNetworkResolution({ status: 'idle' })
103+
localStorage.removeItem(LS_WALLET_SESSION)
97104
}
98105
} catch {
99106
setAddress(null)
@@ -106,28 +113,48 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
106113
setActiveWalletId(null)
107114
setWalletNetwork(null)
108115
setWalletNetworkResolution({ status: 'idle' })
116+
localStorage.removeItem(LS_WALLET_SESSION)
109117
})
110118

111-
// Auto-reconnect last wallet
112-
const lastWallet = localStorage.getItem(LS_WALLET_KEY) as WalletId | null
113-
if (lastWallet) {
114-
reconnect(lastWallet)
119+
// Auto-reconnect last wallet (Silent reconnect on app mount)
120+
const sessionRaw = localStorage.getItem(LS_WALLET_SESSION)
121+
if (sessionRaw) {
122+
try {
123+
const session = JSON.parse(sessionRaw) as WalletSession
124+
reconnect(session)
125+
} catch {
126+
localStorage.removeItem(LS_WALLET_SESSION)
127+
}
115128
}
116129
// eslint-disable-next-line react-hooks/exhaustive-deps
117130
}, [])
118131

119-
async function reconnect(walletId: WalletId) {
132+
async function reconnect(session: WalletSession) {
120133
try {
121-
StellarWalletsKit.setWallet(walletId)
134+
StellarWalletsKit.setWallet(session.walletId)
122135
const { address: addr } = await StellarWalletsKit.getAddress()
136+
123137
if (addr) {
138+
// Validate reconnected public key matches stored value (Requirement: clear if mismatched)
139+
if (addr !== session.publicKey) {
140+
console.warn('Wallet address mismatch during reconnect. Clearing session.')
141+
localStorage.removeItem(LS_WALLET_SESSION)
142+
return
143+
}
144+
124145
setAddress(addr)
125-
setActiveWalletId(walletId)
146+
setActiveWalletId(session.walletId)
126147
setConnectionStatus('connected')
127148
await refreshWalletNetwork()
128149
}
129-
} catch {
130-
// Silent — wallet may not be unlocked yet
150+
} catch (err) {
151+
// Failed to reconnect (extension locked or unavailable)
152+
// Requirement: show a non-blocking banner if it fails.
153+
toast({
154+
title: 'Reconnect failed',
155+
description: 'Unable to auto-reconnect to your wallet. Please unlock your extension or connect manually.',
156+
variant: 'default', // non-blocking (not 'destructive' if we want it subtle)
157+
})
131158
}
132159
}
133160

@@ -148,11 +175,21 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
148175
try {
149176
StellarWalletsKit.setWallet(walletId)
150177
const { address: addr } = await StellarWalletsKit.getAddress()
151-
setAddress(addr ?? null)
152-
setActiveWalletId(walletId)
153-
setConnectionStatus('connected')
154-
localStorage.setItem(LS_WALLET_KEY, walletId)
155-
await refreshWalletNetwork()
178+
179+
if (addr) {
180+
setAddress(addr)
181+
setActiveWalletId(walletId)
182+
setConnectionStatus('connected')
183+
184+
// Save session data (Requirement: {walletType, publicKey})
185+
// SECURITY NOTE: We only store the public key. Never store private keys or seed phrases in localStorage.
186+
localStorage.setItem(LS_WALLET_SESSION, JSON.stringify({
187+
walletId,
188+
publicKey: addr
189+
}))
190+
191+
await refreshWalletNetwork()
192+
}
156193
} catch (err: unknown) {
157194
setWalletNetworkResolution({ status: 'idle' })
158195
setConnectionStatus('error')
@@ -173,7 +210,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
173210
setActiveWalletId(null)
174211
setWalletNetwork(null)
175212
setWalletNetworkResolution({ status: 'idle' })
176-
localStorage.removeItem(LS_WALLET_KEY)
213+
localStorage.removeItem(LS_WALLET_SESSION)
177214
}, [])
178215

179216
const signTransaction = useCallback(async (xdr: string): Promise<string> => {

0 commit comments

Comments
 (0)