@zerodev/wallet-react 0.0.1-alpha.1 → 0.0.1-alpha.10
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.
- package/CHANGELOG.md +67 -0
- package/README.md +30 -17
- package/dist/_cjs/actions.js +62 -24
- package/dist/_cjs/connector.js +82 -48
- package/dist/_cjs/hooks/useExportPrivateKey.js +18 -0
- package/dist/_cjs/hooks/useGetUserEmail.js +19 -0
- package/dist/_cjs/index.js +8 -1
- package/dist/_cjs/oauth.js +60 -55
- package/dist/_cjs/provider.js +5 -2
- package/dist/_cjs/store.js +4 -9
- package/dist/_esm/actions.js +74 -27
- package/dist/_esm/connector.js +98 -55
- package/dist/_esm/hooks/useExportPrivateKey.js +18 -0
- package/dist/_esm/hooks/useGetUserEmail.js +19 -0
- package/dist/_esm/index.js +3 -1
- package/dist/_esm/oauth.js +71 -53
- package/dist/_esm/provider.js +5 -2
- package/dist/_esm/store.js +4 -10
- package/dist/_types/actions.d.ts +35 -6
- package/dist/_types/actions.d.ts.map +1 -1
- package/dist/_types/connector.d.ts +0 -2
- package/dist/_types/connector.d.ts.map +1 -1
- package/dist/_types/hooks/useExportPrivateKey.d.ts +18 -0
- package/dist/_types/hooks/useExportPrivateKey.d.ts.map +1 -0
- package/dist/_types/hooks/useGetUserEmail.d.ts +18 -0
- package/dist/_types/hooks/useGetUserEmail.d.ts.map +1 -0
- package/dist/_types/index.d.ts +4 -2
- package/dist/_types/index.d.ts.map +1 -1
- package/dist/_types/oauth.d.ts +25 -12
- package/dist/_types/oauth.d.ts.map +1 -1
- package/dist/_types/provider.d.ts.map +1 -1
- package/dist/_types/store.d.ts +11 -7
- package/dist/_types/store.d.ts.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/actions.ts +124 -44
- package/src/connector.ts +117 -65
- package/src/hooks/useExportPrivateKey.ts +57 -0
- package/src/hooks/useGetUserEmail.ts +52 -0
- package/src/index.ts +9 -2
- package/src/oauth.ts +97 -78
- package/src/provider.ts +5 -2
- package/src/store.ts +15 -16
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/_cjs/oauth.js
CHANGED
|
@@ -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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
83
|
-
|
|
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
|
}
|
package/dist/_cjs/provider.js
CHANGED
|
@@ -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.
|
|
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().
|
|
157
|
+
store.getState().setActiveChainId(chainId_number);
|
|
155
158
|
emitter.emit('chainChanged', chainId);
|
|
156
159
|
return null;
|
|
157
160
|
}
|
package/dist/_cjs/store.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
activeChainId: null,
|
|
43
38
|
}),
|
|
44
39
|
}), {
|
|
45
40
|
name: 'zerodev-wallet',
|
|
46
41
|
partialize: (state) => ({
|
|
47
42
|
session: state.session,
|
|
48
|
-
|
|
43
|
+
activeChainId: state.activeChainId,
|
|
49
44
|
}),
|
|
50
45
|
})));
|
|
51
46
|
exports.createZeroDevWalletStore = createZeroDevWalletStore;
|
package/dist/_esm/actions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connect as wagmiConnect } from '@wagmi/core/actions';
|
|
2
|
-
import {
|
|
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('
|
|
75
|
+
throw new Error('Wallet not initialized. Please wait for connector setup.');
|
|
74
76
|
}
|
|
75
|
-
// Get
|
|
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
|
-
|
|
92
|
-
//
|
|
93
|
-
const oauthUrl =
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
//
|
|
96
|
+
// Listen for OAuth completion via postMessage
|
|
105
97
|
return new Promise((resolve, reject) => {
|
|
106
|
-
|
|
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
|
-
},
|
|
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
|
+
}
|
package/dist/_esm/connector.js
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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().
|
|
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,
|
|
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 =
|
|
165
|
-
? kernelAccounts.get(
|
|
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().
|
|
213
|
+
return store.getState().activeChainId ?? params.chains[0].id;
|
|
173
214
|
},
|
|
174
215
|
async getProvider() {
|
|
175
|
-
|
|
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().
|
|
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
|
+
}
|
package/dist/_esm/index.js
CHANGED
|
@@ -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';
|