Skip to content

Commit 5d9b423

Browse files
authored
Merge pull request #160 from ChainSafe/fix/update-fix-NU6
fix: lazy load to prevent instant downloads of wasm on page load
2 parents 46b43f0 + da3a3be commit 5d9b423

File tree

4 files changed

+59
-12
lines changed

4 files changed

+59
-12
lines changed

packages/web-wallet/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%231a1a1a'/%3E%3Cg fill='%23f4b728'%3E%3Crect x='7' y='8' width='18' height='3'/%3E%3Cpolygon points='7,23 25,11 25,14 9,26'/%3E%3Crect x='7' y='23' width='18' height='3'/%3E%3C/g%3E%3C/svg%3E" />
67
<link href="src/styles/index.css" rel="stylesheet">
78
<title>WebZjs Web Wallet</title>
89
</head>

packages/web-wallet/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function App() {
1111
const { installedSnap } = useMetaMaskContext();
1212
const { state } = useWebZjsContext();
1313

14-
const interval = installedSnap && !state.syncInProgress ? RESCAN_INTERVAL : null;
14+
const interval = installedSnap && state.initialized && !state.syncInProgress ? RESCAN_INTERVAL : null;
1515

1616
useInterval(() => {
1717
triggerRescan();

packages/web-wallet/src/context/WebzjsContext.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, {
44
useReducer,
55
useEffect,
66
useCallback,
7+
useRef,
78
} from 'react';
89
import { get } from 'idb-keyval';
910

@@ -26,6 +27,7 @@ export interface WebZjsState {
2627
activeAccount?: number | null;
2728
syncInProgress: boolean;
2829
loading: boolean;
30+
initialized: boolean;
2931
}
3032

3133
type Action =
@@ -35,7 +37,8 @@ type Action =
3537
| { type: 'set-chain-height'; payload: bigint }
3638
| { type: 'set-active-account'; payload: number }
3739
| { type: 'set-sync-in-progress'; payload: boolean }
38-
| { type: 'set-loading'; payload: boolean };
40+
| { type: 'set-loading'; payload: boolean }
41+
| { type: 'set-initialized'; payload: boolean };
3942

4043
const initialState: WebZjsState = {
4144
webWallet: null,
@@ -45,7 +48,8 @@ const initialState: WebZjsState = {
4548
chainHeight: undefined,
4649
activeAccount: null,
4750
syncInProgress: false,
48-
loading: true,
51+
loading: false,
52+
initialized: false,
4953
};
5054

5155
function reducer(state: WebZjsState, action: Action): WebZjsState {
@@ -64,6 +68,8 @@ function reducer(state: WebZjsState, action: Action): WebZjsState {
6468
return { ...state, syncInProgress: action.payload };
6569
case 'set-loading':
6670
return { ...state, loading: action.payload };
71+
case 'set-initialized':
72+
return { ...state, initialized: action.payload };
6773
default:
6874
return state;
6975
}
@@ -72,11 +78,13 @@ function reducer(state: WebZjsState, action: Action): WebZjsState {
7278
interface WebZjsContextType {
7379
state: WebZjsState;
7480
dispatch: React.Dispatch<Action>;
81+
initWallet: () => Promise<void>;
7582
}
7683

7784
const WebZjsContext = createContext<WebZjsContextType>({
7885
state: initialState,
7986
dispatch: () => {},
87+
initWallet: async () => {},
8088
});
8189

8290
export function useWebZjsContext(): WebZjsContextType {
@@ -85,6 +93,7 @@ export function useWebZjsContext(): WebZjsContextType {
8593

8694
export const WebZjsProvider = ({ children }: { children: React.ReactNode }) => {
8795
const [state, dispatch] = useReducer(reducer, initialState);
96+
const initializingRef = useRef(false);
8897

8998
const initAll = useCallback(async () => {
9099
try {
@@ -147,16 +156,27 @@ export const WebZjsProvider = ({ children }: { children: React.ReactNode }) => {
147156
}
148157

149158
dispatch({ type: 'set-loading', payload: false });
159+
dispatch({ type: 'set-initialized', payload: true });
150160
} catch (err) {
151161
console.error('Initialization error:', err);
152162
dispatch({ type: 'set-error', payload: Error(String(err)) });
153163
dispatch({ type: 'set-loading', payload: false });
154164
}
155165
}, []);
156166

157-
useEffect(() => {
158-
initAll().catch(console.error);
159-
}, [initAll]);
167+
// Lazy-load WASM: call this when user wants to use wallet features
168+
const initWallet = useCallback(async () => {
169+
if (state.initialized || initializingRef.current) {
170+
return; // Already initialized or in progress
171+
}
172+
initializingRef.current = true;
173+
dispatch({ type: 'set-loading', payload: true });
174+
try {
175+
await initAll();
176+
} finally {
177+
initializingRef.current = false;
178+
}
179+
}, [state.initialized, initAll]);
160180

161181
useEffect(() => {
162182
if (state.error) {
@@ -166,7 +186,7 @@ export const WebZjsProvider = ({ children }: { children: React.ReactNode }) => {
166186

167187

168188
return (
169-
<WebZjsContext.Provider value={{ state, dispatch }}>
189+
<WebZjsContext.Provider value={{ state, dispatch, initWallet }}>
170190
<Toaster />
171191
{children}
172192
</WebZjsContext.Provider>

packages/web-wallet/src/pages/Home.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Loader from '../components/Loader/Loader';
88

99
const Home: React.FC = () => {
1010
const navigate = useNavigate();
11-
const { state, dispatch } = useWebZjsContext();
11+
const { state, dispatch, initWallet } = useWebZjsContext();
1212
const { getAccountData, connectWebZjsSnap, recoverWallet } = useWebZjsActions();
1313
const { installedSnap } = useMetaMask();
1414
const { isPendingRequest } = useMetaMaskContext();
@@ -23,8 +23,19 @@ const Home: React.FC = () => {
2323
e.preventDefault();
2424
connectingRef.current = true;
2525
try {
26+
// Lazy-load WASM on first user interaction
27+
await initWallet();
2628
await connectWebZjsSnap();
2729
navigate('/dashboard/account-summary');
30+
} catch (err: any) {
31+
// Handle user rejection gracefully (code 4001)
32+
if (err?.code === 4001) {
33+
console.log('User rejected MetaMask connection');
34+
return;
35+
}
36+
// Other errors should be shown to user
37+
console.error('Connection failed:', err);
38+
dispatch({ type: 'set-error', payload: err instanceof Error ? err : new Error(String(err)) });
2839
} finally {
2940
connectingRef.current = false;
3041
}
@@ -52,9 +63,24 @@ const Home: React.FC = () => {
5263
return;
5364
}
5465

55-
// Case 2: No account but snap is installed - auto-recover (once only)
56-
// Skip if connect is in progress to avoid duplicate viewingKey prompts
57-
if (!recoveryAttemptedRef.current && !connectingRef.current) {
66+
// Case 2: Wallet not initialized yet - initialize it first
67+
if (!state.initialized && !connectingRef.current) {
68+
try {
69+
setRecovering(true);
70+
await initWallet();
71+
// After init completes, effect will re-run with updated state
72+
} catch (err) {
73+
console.error('Wallet initialization failed:', err);
74+
dispatch({ type: 'set-error', payload: err instanceof Error ? err : new Error(String(err)) });
75+
setShowResetInstructions(true);
76+
} finally {
77+
setRecovering(false);
78+
}
79+
return;
80+
}
81+
82+
// Case 3: Wallet initialized but no account - attempt recovery (once only)
83+
if (state.initialized && !recoveryAttemptedRef.current && !connectingRef.current) {
5884
recoveryAttemptedRef.current = true;
5985
try {
6086
setRecovering(true);
@@ -71,7 +97,7 @@ const Home: React.FC = () => {
7197
};
7298

7399
homeReload();
74-
}, [state.loading, state.activeAccount, installedSnap, navigate, dispatch, getAccountData, recoverWallet]);
100+
}, [state.loading, state.activeAccount, state.initialized, installedSnap, navigate, dispatch, getAccountData, recoverWallet, initWallet]);
75101

76102
return (
77103
<div className="home-page flex items-start md:items-center justify-center px-4 overflow-y-hidden">

0 commit comments

Comments
 (0)