@zerodev/wallet-react 0.0.1-alpha.1 → 0.0.1-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +30 -17
  3. package/dist/_cjs/actions.js +62 -24
  4. package/dist/_cjs/connector.js +82 -48
  5. package/dist/_cjs/hooks/useExportPrivateKey.js +18 -0
  6. package/dist/_cjs/hooks/useGetUserEmail.js +19 -0
  7. package/dist/_cjs/index.js +8 -1
  8. package/dist/_cjs/oauth.js +60 -55
  9. package/dist/_cjs/provider.js +5 -2
  10. package/dist/_cjs/store.js +4 -9
  11. package/dist/_esm/actions.js +74 -27
  12. package/dist/_esm/connector.js +98 -55
  13. package/dist/_esm/hooks/useExportPrivateKey.js +18 -0
  14. package/dist/_esm/hooks/useGetUserEmail.js +19 -0
  15. package/dist/_esm/index.js +3 -1
  16. package/dist/_esm/oauth.js +71 -53
  17. package/dist/_esm/provider.js +5 -2
  18. package/dist/_esm/store.js +4 -10
  19. package/dist/_types/actions.d.ts +35 -6
  20. package/dist/_types/actions.d.ts.map +1 -1
  21. package/dist/_types/connector.d.ts +0 -2
  22. package/dist/_types/connector.d.ts.map +1 -1
  23. package/dist/_types/hooks/useExportPrivateKey.d.ts +18 -0
  24. package/dist/_types/hooks/useExportPrivateKey.d.ts.map +1 -0
  25. package/dist/_types/hooks/useGetUserEmail.d.ts +18 -0
  26. package/dist/_types/hooks/useGetUserEmail.d.ts.map +1 -0
  27. package/dist/_types/index.d.ts +4 -2
  28. package/dist/_types/index.d.ts.map +1 -1
  29. package/dist/_types/oauth.d.ts +25 -12
  30. package/dist/_types/oauth.d.ts.map +1 -1
  31. package/dist/_types/provider.d.ts.map +1 -1
  32. package/dist/_types/store.d.ts +11 -7
  33. package/dist/_types/store.d.ts.map +1 -1
  34. package/dist/tsconfig.build.tsbuildinfo +1 -1
  35. package/package.json +2 -2
  36. package/src/actions.test.ts +895 -0
  37. package/src/actions.ts +124 -44
  38. package/src/connector.ts +117 -65
  39. package/src/hooks/useExportPrivateKey.ts +57 -0
  40. package/src/hooks/useGetUserEmail.ts +52 -0
  41. package/src/index.ts +9 -2
  42. package/src/oauth.test.ts +445 -0
  43. package/src/oauth.ts +97 -78
  44. package/src/provider.ts +5 -2
  45. package/src/store.ts +15 -16
  46. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,42 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OAUTH_PROVIDERS = void 0;
4
- exports.buildOAuthUrl = buildOAuthUrl;
5
4
  exports.openOAuthPopup = openOAuthPopup;
6
- exports.extractOAuthToken = extractOAuthToken;
7
- exports.pollOAuthPopup = pollOAuthPopup;
8
5
  exports.generateOAuthNonce = generateOAuthNonce;
6
+ exports.buildBackendOAuthUrl = buildBackendOAuthUrl;
7
+ exports.listenForOAuthMessage = listenForOAuthMessage;
8
+ exports.handleOAuthCallback = handleOAuthCallback;
9
9
  const viem_1 = require("viem");
10
10
  exports.OAUTH_PROVIDERS = {
11
11
  GOOGLE: 'google',
12
12
  };
13
- const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
14
13
  const POPUP_WIDTH = 500;
15
14
  const POPUP_HEIGHT = 600;
16
- function buildOAuthUrl(params) {
17
- const { provider, clientId, redirectUri, nonce, state } = params;
18
- if (provider !== exports.OAUTH_PROVIDERS.GOOGLE) {
19
- throw new Error(`Unsupported OAuth provider: ${provider}`);
20
- }
21
- const authUrl = new URL(GOOGLE_AUTH_URL);
22
- authUrl.searchParams.set('client_id', clientId);
23
- authUrl.searchParams.set('redirect_uri', redirectUri);
24
- authUrl.searchParams.set('response_type', 'id_token');
25
- authUrl.searchParams.set('scope', 'openid email profile');
26
- authUrl.searchParams.set('nonce', nonce);
27
- authUrl.searchParams.set('prompt', 'select_account');
28
- let stateParam = `provider=${provider}`;
29
- if (state) {
30
- const additionalState = Object.entries(state)
31
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
32
- .join('&');
33
- if (additionalState) {
34
- stateParam += `&${additionalState}`;
35
- }
36
- }
37
- authUrl.searchParams.set('state', stateParam);
38
- return authUrl.toString();
39
- }
40
15
  function openOAuthPopup(url) {
41
16
  const width = POPUP_WIDTH;
42
17
  const height = POPUP_HEIGHT;
@@ -48,37 +23,67 @@ function openOAuthPopup(url) {
48
23
  }
49
24
  return authWindow;
50
25
  }
51
- function extractOAuthToken(url) {
52
- const hashParams = new URLSearchParams(url.split('#')[1]);
53
- let idToken = hashParams.get('id_token');
54
- if (!idToken) {
55
- const queryParams = new URLSearchParams(url.split('?')[1]);
56
- idToken = queryParams.get('id_token');
26
+ function generateOAuthNonce(publicKey) {
27
+ return (0, viem_1.sha256)(publicKey).replace(/^0x/, '');
28
+ }
29
+ function buildBackendOAuthUrl(params) {
30
+ const { provider, backendUrl, projectId, publicKey, returnTo } = params;
31
+ if (provider !== exports.OAUTH_PROVIDERS.GOOGLE) {
32
+ throw new Error(`Unsupported OAuth provider: ${provider}`);
57
33
  }
58
- return idToken;
34
+ const oauthUrl = new URL(`${backendUrl}/oauth/google/login`);
35
+ oauthUrl.searchParams.set('project_id', projectId);
36
+ oauthUrl.searchParams.set('pub_key', publicKey.replace(/^0x/, ''));
37
+ oauthUrl.searchParams.set('return_to', returnTo);
38
+ return oauthUrl.toString();
59
39
  }
60
- function pollOAuthPopup(authWindow, originUrl, onSuccess, onError) {
61
- const interval = setInterval(() => {
62
- try {
63
- if (authWindow.closed) {
64
- clearInterval(interval);
65
- onError(new Error('Authentication window was closed'));
66
- return;
67
- }
68
- const url = authWindow.location.href || '';
69
- if (url.startsWith(originUrl)) {
70
- const token = extractOAuthToken(url);
71
- if (token) {
72
- authWindow.close();
73
- clearInterval(interval);
74
- onSuccess(token);
75
- }
76
- }
40
+ function listenForOAuthMessage(authWindow, expectedOrigin, onSuccess, onError) {
41
+ let cleaned = false;
42
+ const handleMessage = (event) => {
43
+ if (event.origin !== expectedOrigin)
44
+ return;
45
+ if (!event.data || typeof event.data !== 'object')
46
+ return;
47
+ if (event.data.type === 'oauth_success') {
48
+ cleanup();
49
+ onSuccess();
77
50
  }
78
- catch {
51
+ else if (event.data.type === 'oauth_error') {
52
+ cleanup();
53
+ onError(new Error(event.data.error || 'OAuth authentication failed'));
54
+ }
55
+ };
56
+ const checkWindowClosed = setInterval(() => {
57
+ if (authWindow.closed) {
58
+ cleanup();
59
+ onError(new Error('Authentication window was closed'));
79
60
  }
80
61
  }, 500);
62
+ const cleanup = () => {
63
+ if (cleaned)
64
+ return;
65
+ cleaned = true;
66
+ window.removeEventListener('message', handleMessage);
67
+ clearInterval(checkWindowClosed);
68
+ };
69
+ window.addEventListener('message', handleMessage);
70
+ return cleanup;
81
71
  }
82
- function generateOAuthNonce(publicKey) {
83
- return (0, viem_1.sha256)(publicKey).replace(/^0x/, '');
72
+ function handleOAuthCallback(successParam = 'oauth_success') {
73
+ const urlParams = new URLSearchParams(window.location.search);
74
+ const isSuccess = urlParams.get(successParam) === 'true';
75
+ const error = urlParams.get('error');
76
+ if (window.opener) {
77
+ if (isSuccess) {
78
+ window.opener.postMessage({ type: 'oauth_success' }, window.location.origin);
79
+ window.close();
80
+ return true;
81
+ }
82
+ if (error) {
83
+ window.opener.postMessage({ type: 'oauth_error', error }, window.location.origin);
84
+ window.close();
85
+ return false;
86
+ }
87
+ }
88
+ return false;
84
89
  }
@@ -77,7 +77,10 @@ function createProvider({ store, config, }) {
77
77
  },
78
78
  async request({ method, params }) {
79
79
  const state = store.getState();
80
- const activeChainId = state.chainIds[0];
80
+ const activeChainId = state.activeChainId;
81
+ if (!activeChainId) {
82
+ throw new Error('No active chain');
83
+ }
81
84
  switch (method) {
82
85
  case 'eth_accounts': {
83
86
  const account = state.kernelAccounts.get(activeChainId);
@@ -151,7 +154,7 @@ function createProvider({ store, config, }) {
151
154
  }
152
155
  const [{ chainId }] = params;
153
156
  const chainId_number = parseInt(chainId, 16);
154
- store.getState().setActiveChain(chainId_number);
157
+ store.getState().setActiveChainId(chainId_number);
155
158
  emitter.emit('chainChanged', chainId);
156
159
  return null;
157
160
  }
@@ -7,7 +7,7 @@ const createZeroDevWalletStore = () => (0, zustand_1.create)()((0, middleware_1.
7
7
  wallet: null,
8
8
  eoaAccount: null,
9
9
  session: null,
10
- chainIds: [],
10
+ activeChainId: null,
11
11
  kernelAccounts: new Map(),
12
12
  kernelClients: new Map(),
13
13
  isExpiring: false,
@@ -25,12 +25,7 @@ const createZeroDevWalletStore = () => (0, zustand_1.create)()((0, middleware_1.
25
25
  set({ kernelClients: clients });
26
26
  },
27
27
  setSession: (session) => set({ session }),
28
- setActiveChain: (chainId) => {
29
- const { chainIds } = get();
30
- set({
31
- chainIds: [chainId, ...chainIds.filter((id) => id !== chainId)],
32
- });
33
- },
28
+ setActiveChainId: (chainId) => set({ activeChainId: chainId }),
34
29
  setIsExpiring: (isExpiring) => set({ isExpiring }),
35
30
  setOAuthConfig: (config) => set({ oauthConfig: config }),
36
31
  clear: () => set({
@@ -39,13 +34,13 @@ const createZeroDevWalletStore = () => (0, zustand_1.create)()((0, middleware_1.
39
34
  kernelAccounts: new Map(),
40
35
  kernelClients: new Map(),
41
36
  isExpiring: false,
42
- chainIds: [],
37
+ activeChainId: null,
43
38
  }),
44
39
  }), {
45
40
  name: 'zerodev-wallet',
46
41
  partialize: (state) => ({
47
42
  session: state.session,
48
- chainIds: state.chainIds,
43
+ activeChainId: state.activeChainId,
49
44
  }),
50
45
  })));
51
46
  exports.createZeroDevWalletStore = createZeroDevWalletStore;
@@ -1,5 +1,6 @@
1
1
  import { connect as wagmiConnect } from '@wagmi/core/actions';
2
- import { buildOAuthUrl, generateOAuthNonce, openOAuthPopup, pollOAuthPopup, } from './oauth.js';
2
+ import { createIframeStamper, exportPrivateKey as exportPrivateKeySdk, exportWallet as exportWalletSdk, } from '@zerodev/wallet-core';
3
+ import { buildBackendOAuthUrl, listenForOAuthMessage, openOAuthPopup, } from './oauth.js';
3
4
  /**
4
5
  * Get ZeroDev connector from config
5
6
  */
@@ -60,6 +61,7 @@ export async function loginPasskey(config, parameters) {
60
61
  }
61
62
  /**
62
63
  * Authenticate with OAuth (opens popup)
64
+ * Uses backend OAuth flow where the backend handles PKCE and token exchange
63
65
  */
64
66
  export async function authenticateOAuth(config, parameters) {
65
67
  const connector = parameters.connector ?? getZeroDevConnector(config);
@@ -70,46 +72,36 @@ export async function authenticateOAuth(config, parameters) {
70
72
  if (!wallet)
71
73
  throw new Error('Wallet not initialized');
72
74
  if (!oauthConfig) {
73
- throw new Error('OAuth is not configured. Please provide oauthConfig to zeroDevWallet connector.');
75
+ throw new Error('Wallet not initialized. Please wait for connector setup.');
74
76
  }
75
- // Get client ID for the provider
76
- let clientId = parameters.clientId;
77
- if (!clientId) {
78
- clientId = oauthConfig.googleClientId;
79
- }
80
- if (!clientId) {
81
- throw new Error(`Client ID not configured for ${parameters.provider}`);
82
- }
83
- if (!oauthConfig.redirectUri) {
84
- throw new Error('OAuth redirect URI is not configured.');
85
- }
86
- // Generate nonce from wallet public key
77
+ // Get wallet public key for the OAuth flow
87
78
  const publicKey = await wallet.getPublicKey();
88
79
  if (!publicKey) {
89
80
  throw new Error('Failed to get wallet public key');
90
81
  }
91
- const nonce = generateOAuthNonce(publicKey);
92
- // Build OAuth URL
93
- const oauthUrl = buildOAuthUrl({
82
+ // Build OAuth URL that redirects to backend
83
+ // Use current origin as redirect - SDK auto-detects callback on any page
84
+ const oauthUrl = buildBackendOAuthUrl({
94
85
  provider: parameters.provider,
95
- clientId,
96
- redirectUri: oauthConfig.redirectUri,
97
- nonce,
86
+ backendUrl: oauthConfig.backendUrl,
87
+ projectId: oauthConfig.projectId,
88
+ publicKey,
89
+ returnTo: `${window.location.origin}?oauth_success=true&oauth_provider=${parameters.provider}`,
98
90
  });
99
91
  // Open popup
100
92
  const authWindow = openOAuthPopup(oauthUrl);
101
93
  if (!authWindow) {
102
94
  throw new Error(`Failed to open ${parameters.provider} login window.`);
103
95
  }
104
- // Poll for OAuth completion
96
+ // Listen for OAuth completion via postMessage
105
97
  return new Promise((resolve, reject) => {
106
- pollOAuthPopup(authWindow, window.location.origin, async (idToken) => {
98
+ const cleanup = listenForOAuthMessage(authWindow, window.location.origin, async () => {
107
99
  try {
108
100
  // Complete OAuth authentication with wallet-core
101
+ // The backend has stored the OAuth session in a cookie
109
102
  await wallet.auth({
110
103
  type: 'oauth',
111
104
  provider: parameters.provider,
112
- credential: idToken,
113
105
  });
114
106
  const [session, eoaAccount] = await Promise.all([
115
107
  wallet.getSession(),
@@ -124,7 +116,10 @@ export async function authenticateOAuth(config, parameters) {
124
116
  catch (err) {
125
117
  reject(err);
126
118
  }
127
- }, reject);
119
+ }, (error) => {
120
+ cleanup();
121
+ reject(error);
122
+ });
128
123
  });
129
124
  }
130
125
  /**
@@ -148,7 +143,6 @@ export async function sendOTP(config, parameters) {
148
143
  });
149
144
  return {
150
145
  otpId: result.otpId,
151
- subOrganizationId: result.subOrganizationId,
152
146
  };
153
147
  }
154
148
  /**
@@ -166,7 +160,6 @@ export async function verifyOTP(config, parameters) {
166
160
  mode: 'verifyOtp',
167
161
  otpId: parameters.otpId,
168
162
  otpCode: parameters.code,
169
- subOrganizationId: parameters.subOrganizationId,
170
163
  });
171
164
  const [session, eoaAccount] = await Promise.all([
172
165
  wallet.getSession(),
@@ -193,6 +186,31 @@ export async function refreshSession(config, parameters = {}) {
193
186
  store.getState().setSession(newSession || null);
194
187
  return newSession;
195
188
  }
189
+ /**
190
+ * Get user email
191
+ */
192
+ export async function getUserEmail(config) {
193
+ const connector = getZeroDevConnector(config);
194
+ // @ts-expect-error - getStore is a custom method
195
+ const store = await connector.getStore();
196
+ const wallet = store.getState().wallet;
197
+ if (!wallet)
198
+ throw new Error('Wallet not initialized');
199
+ const oauthConfig = store.getState().oauthConfig;
200
+ if (!oauthConfig) {
201
+ throw new Error('Wallet not initialized. Please wait for connector setup.');
202
+ }
203
+ const session = store.getState().session;
204
+ if (!session) {
205
+ throw new Error('No active session');
206
+ }
207
+ // Call the core SDK method
208
+ return await wallet.client.getUserEmail({
209
+ organizationId: session.organizationId,
210
+ projectId: oauthConfig.projectId,
211
+ token: session.token,
212
+ });
213
+ }
196
214
  /**
197
215
  * Export wallet
198
216
  */
@@ -203,7 +221,6 @@ export async function exportWallet(config, parameters) {
203
221
  const wallet = store.getState().wallet;
204
222
  if (!wallet)
205
223
  throw new Error('Wallet not initialized');
206
- const { exportWallet: exportWalletSdk, createIframeStamper } = await import('@zerodev/wallet-core');
207
224
  const iframeContainer = document.getElementById(parameters.iframeContainerId);
208
225
  if (!iframeContainer) {
209
226
  throw new Error('Iframe container not found');
@@ -223,3 +240,33 @@ export async function exportWallet(config, parameters) {
223
240
  throw new Error('Failed to inject export bundle');
224
241
  }
225
242
  }
243
+ /**
244
+ * Export private key
245
+ */
246
+ export async function exportPrivateKey(config, parameters) {
247
+ const connector = parameters.connector ?? getZeroDevConnector(config);
248
+ // @ts-expect-error - getStore is a custom method
249
+ const store = await connector.getStore();
250
+ const wallet = store.getState().wallet;
251
+ if (!wallet)
252
+ throw new Error('Wallet not initialized');
253
+ const iframeContainer = document.getElementById(parameters.iframeContainerId);
254
+ if (!iframeContainer) {
255
+ throw new Error('Iframe container not found');
256
+ }
257
+ const iframeStamper = await createIframeStamper({
258
+ iframeUrl: 'https://export.turnkey.com',
259
+ iframeContainer,
260
+ iframeElementId: 'export-private-key-iframe',
261
+ });
262
+ const publicKey = await iframeStamper.init();
263
+ const { exportBundle, organizationId } = await exportPrivateKeySdk({
264
+ wallet,
265
+ targetPublicKey: publicKey,
266
+ ...(parameters.address && { address: parameters.address }),
267
+ });
268
+ const success = await iframeStamper.injectKeyExportBundle(exportBundle, organizationId, parameters.keyFormat ?? 'Hexadecimal');
269
+ if (success !== true) {
270
+ throw new Error('Failed to inject export bundle');
271
+ }
272
+ }
@@ -1,61 +1,105 @@
1
1
  import { createConnector } from '@wagmi/core';
2
2
  import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient, } from '@zerodev/sdk';
3
3
  import { getEntryPoint, KERNEL_V3_3 } from '@zerodev/sdk/constants';
4
- import { createZeroDevWallet } from '@zerodev/wallet-core';
4
+ import { createZeroDevWallet, KMS_SERVER_URL } from '@zerodev/wallet-core';
5
5
  import { createPublicClient, http } from 'viem';
6
+ import { handleOAuthCallback } from './oauth.js';
6
7
  import { createProvider } from './provider.js';
7
8
  import { createZeroDevWalletStore } from './store.js';
8
9
  import { getAAUrl } from './utils/aaUtils.js';
10
+ // OAuth URL parameter used to detect callback
11
+ const OAUTH_SUCCESS_PARAM = 'oauth_success';
12
+ const OAUTH_PROVIDER_PARAM = 'oauth_provider';
13
+ /**
14
+ * Detect OAuth callback from URL params and handle it.
15
+ * - If in popup: sends postMessage to opener and closes
16
+ * - If not in popup: completes auth directly
17
+ */
18
+ async function detectAndHandleOAuthCallback(wallet, store) {
19
+ if (typeof window === 'undefined')
20
+ return false;
21
+ const params = new URLSearchParams(window.location.search);
22
+ const isOAuthCallback = params.get(OAUTH_SUCCESS_PARAM) === 'true';
23
+ if (!isOAuthCallback)
24
+ return false;
25
+ // If in popup, use the existing handler to notify opener
26
+ if (window.opener) {
27
+ handleOAuthCallback(OAUTH_SUCCESS_PARAM);
28
+ return true;
29
+ }
30
+ // Not in popup - complete auth directly (redirect flow)
31
+ console.log('OAuth callback detected, completing authentication...');
32
+ const provider = (params.get(OAUTH_PROVIDER_PARAM) ||
33
+ 'google');
34
+ try {
35
+ await wallet.auth({ type: 'oauth', provider });
36
+ const [session, eoaAccount] = await Promise.all([
37
+ wallet.getSession(),
38
+ wallet.toAccount(),
39
+ ]);
40
+ store.getState().setEoaAccount(eoaAccount);
41
+ store.getState().setSession(session || null);
42
+ // Clean up URL params
43
+ params.delete(OAUTH_SUCCESS_PARAM);
44
+ params.delete(OAUTH_PROVIDER_PARAM);
45
+ const newUrl = params.toString()
46
+ ? `${window.location.pathname}?${params.toString()}`
47
+ : window.location.pathname;
48
+ window.history.replaceState({}, '', newUrl);
49
+ console.log('OAuth authentication completed');
50
+ return true;
51
+ }
52
+ catch (error) {
53
+ console.error('OAuth authentication failed:', error);
54
+ return false;
55
+ }
56
+ }
9
57
  export function zeroDevWallet(params) {
10
58
  return createConnector((wagmiConfig) => {
11
59
  let store;
12
60
  let provider;
13
- let initPromise;
14
61
  // Get transports from Wagmi config (uses user's RPC URLs)
15
62
  const transports = wagmiConfig.transports;
16
63
  // Lazy initialization - only runs on client side
17
64
  const initialize = async () => {
18
- initPromise ??= (async () => {
19
- console.log('Initializing ZeroDevWallet connector...');
20
- // Initialize wallet SDK
21
- const wallet = await createZeroDevWallet({
22
- projectId: params.projectId,
23
- ...(params.organizationId && {
24
- organizationId: params.organizationId,
25
- }),
26
- ...(params.proxyBaseUrl && { proxyBaseUrl: params.proxyBaseUrl }),
27
- ...(params.sessionStorage && {
28
- sessionStorage: params.sessionStorage,
29
- }),
30
- ...(params.rpId && { rpId: params.rpId }),
31
- });
32
- // Create store
33
- store = createZeroDevWalletStore();
34
- store.getState().setWallet(wallet);
35
- // Initialize chainIds
36
- const chainIds = params.chains.map((c) => c.id);
37
- store.setState({ chainIds });
38
- // Store OAuth config if provided
39
- if (params.oauthConfig) {
40
- store.getState().setOAuthConfig(params.oauthConfig);
41
- }
42
- // Create EIP-1193 provider
43
- provider = createProvider({
44
- store,
45
- config: params,
46
- chains: Array.from(params.chains),
47
- });
48
- // Check for existing session (page reload)
49
- const session = await wallet.getSession();
50
- if (session) {
51
- console.log('Found existing session, restoring...');
52
- const eoaAccount = await wallet.toAccount();
53
- store.getState().setEoaAccount(eoaAccount);
54
- store.getState().setSession(session);
55
- }
56
- console.log('ZeroDevWallet connector initialized');
57
- })();
58
- return initPromise;
65
+ console.log('Initializing ZeroDevWallet connector...');
66
+ // Initialize wallet SDK
67
+ const wallet = await createZeroDevWallet({
68
+ projectId: params.projectId,
69
+ ...(params.organizationId && {
70
+ organizationId: params.organizationId,
71
+ }),
72
+ ...(params.proxyBaseUrl && { proxyBaseUrl: params.proxyBaseUrl }),
73
+ ...(params.sessionStorage && {
74
+ sessionStorage: params.sessionStorage,
75
+ }),
76
+ ...(params.rpId && { rpId: params.rpId }),
77
+ });
78
+ // Create store
79
+ store = createZeroDevWalletStore();
80
+ store.getState().setWallet(wallet);
81
+ // Store OAuth config - uses proxyBaseUrl and projectId from params
82
+ store.getState().setOAuthConfig({
83
+ backendUrl: params.proxyBaseUrl || `${KMS_SERVER_URL}/api/v1`,
84
+ projectId: params.projectId,
85
+ });
86
+ // Create EIP-1193 provider
87
+ provider = createProvider({
88
+ store,
89
+ config: params,
90
+ chains: Array.from(params.chains),
91
+ });
92
+ // Check for existing session (page reload)
93
+ const session = await wallet.getSession();
94
+ if (session) {
95
+ console.log('Found existing session, restoring...');
96
+ const eoaAccount = await wallet.toAccount();
97
+ store.getState().setEoaAccount(eoaAccount);
98
+ store.getState().setSession(session);
99
+ }
100
+ // Auto-detect OAuth callback (when popup redirects back with ?oauth_success=true)
101
+ await detectAndHandleOAuthCallback(wallet, store);
102
+ console.log('ZeroDevWallet connector initialized');
59
103
  };
60
104
  return {
61
105
  id: 'zerodev-wallet',
@@ -77,10 +121,7 @@ export function zeroDevWallet(params) {
77
121
  : 'Connecting ZeroDevWallet...');
78
122
  const state = store.getState();
79
123
  // Determine active chain
80
- const activeChainId = chainId || state.chainIds[0];
81
- if (!activeChainId) {
82
- throw new Error('No chain configured');
83
- }
124
+ const activeChainId = chainId ?? state.activeChainId ?? params.chains[0].id;
84
125
  // If reconnecting and already have kernel account, return immediately
85
126
  if (isReconnecting && state.kernelAccounts.has(activeChainId)) {
86
127
  const kernelAccount = state.kernelAccounts.get(activeChainId);
@@ -129,7 +170,7 @@ export function zeroDevWallet(params) {
129
170
  store.getState().setKernelClient(activeChainId, kernelClient);
130
171
  }
131
172
  // Set as active chain
132
- store.getState().setActiveChain(activeChainId);
173
+ store.getState().setActiveChainId(activeChainId);
133
174
  // Get fresh state after updates
134
175
  const freshState = store.getState();
135
176
  const kernelAccount = freshState.kernelAccounts.get(activeChainId);
@@ -155,35 +196,36 @@ export function zeroDevWallet(params) {
155
196
  async getAccounts() {
156
197
  if (!store)
157
198
  return [];
158
- const { eoaAccount, kernelAccounts, chainIds } = store.getState();
199
+ const { eoaAccount, kernelAccounts, activeChainId } = store.getState();
159
200
  // Return EOA address if we have it (EIP-7702: EOA address = kernel address)
160
201
  if (eoaAccount) {
161
202
  return [eoaAccount.address];
162
203
  }
163
204
  // Fallback: check kernel accounts
164
- const activeAccount = chainIds[0]
165
- ? kernelAccounts.get(chainIds[0])
205
+ const activeAccount = activeChainId
206
+ ? kernelAccounts.get(activeChainId)
166
207
  : null;
167
208
  return activeAccount ? [activeAccount.address] : [];
168
209
  },
169
210
  async getChainId() {
170
211
  if (!store)
171
212
  return params.chains[0].id;
172
- return store.getState().chainIds[0] || params.chains[0].id;
213
+ return store.getState().activeChainId ?? params.chains[0].id;
173
214
  },
174
215
  async getProvider() {
175
- await initialize();
216
+ if (!provider) {
217
+ await initialize();
218
+ }
176
219
  return provider;
177
220
  },
178
221
  async switchChain({ chainId }) {
179
- await initialize();
180
222
  console.log(`Switching to chain ${chainId}...`);
181
223
  const state = store.getState();
182
224
  if (!state.eoaAccount) {
183
225
  throw new Error('Not authenticated');
184
226
  }
185
227
  // Update active chain
186
- store.getState().setActiveChain(chainId);
228
+ store.getState().setActiveChainId(chainId);
187
229
  // Create kernel account for new chain if doesn't exist
188
230
  if (!state.kernelAccounts.has(chainId)) {
189
231
  const chain = params.chains.find((c) => c.id === chainId);
@@ -215,6 +257,7 @@ export function zeroDevWallet(params) {
215
257
  });
216
258
  store.getState().setKernelClient(chainId, kernelClient);
217
259
  }
260
+ wagmiConfig.emitter.emit('change', { chainId });
218
261
  return params.chains.find((c) => c.id === chainId);
219
262
  },
220
263
  async isAuthorized() {
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+ import { useMutation, } from '@tanstack/react-query';
3
+ import { useConfig } from 'wagmi';
4
+ import { exportPrivateKey } from '../actions.js';
5
+ /**
6
+ * Hook to export private key
7
+ */
8
+ export function useExportPrivateKey(parameters = {}) {
9
+ const { mutation } = parameters;
10
+ const config = useConfig(parameters);
11
+ return useMutation({
12
+ ...mutation,
13
+ async mutationFn(variables) {
14
+ return exportPrivateKey(config, variables);
15
+ },
16
+ mutationKey: ['exportPrivateKey'],
17
+ });
18
+ }
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { useQuery, } from '@tanstack/react-query';
3
+ import { useConfig } from 'wagmi';
4
+ import { getUserEmail } from '../actions.js';
5
+ /**
6
+ * Hook to fetch user email address
7
+ */
8
+ export function useGetUserEmail(parameters) {
9
+ const { query } = parameters;
10
+ const config = useConfig(parameters);
11
+ return useQuery({
12
+ ...query,
13
+ queryKey: ['getUserEmail'],
14
+ queryFn: async () => {
15
+ return getUserEmail(config);
16
+ },
17
+ enabled: Boolean(config),
18
+ });
19
+ }
@@ -1,10 +1,12 @@
1
1
  export { zeroDevWallet } from './connector.js';
2
2
  export { useAuthenticateOAuth } from './hooks/useAuthenticateOAuth.js';
3
+ export { useExportPrivateKey } from './hooks/useExportPrivateKey.js';
3
4
  export { useExportWallet } from './hooks/useExportWallet.js';
5
+ export { useGetUserEmail } from './hooks/useGetUserEmail.js';
4
6
  export { useLoginPasskey } from './hooks/useLoginPasskey.js';
5
7
  export { useRefreshSession } from './hooks/useRefreshSession.js';
6
8
  export { useRegisterPasskey } from './hooks/useRegisterPasskey.js';
7
9
  export { useSendOTP } from './hooks/useSendOTP.js';
8
10
  export { useVerifyOTP } from './hooks/useVerifyOTP.js';
9
- export { OAUTH_PROVIDERS } from './oauth.js';
11
+ export { buildBackendOAuthUrl, handleOAuthCallback, listenForOAuthMessage, OAUTH_PROVIDERS, } from './oauth.js';
10
12
  export { createZeroDevWalletStore } from './store.js';