@zerodev/wallet-react 0.0.1-alpha.0 → 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +282 -0
  3. package/dist/_cjs/actions.js +62 -24
  4. package/dist/_cjs/connector.js +86 -52
  5. package/dist/_cjs/constants.js +1 -1
  6. package/dist/_cjs/hooks/useExportPrivateKey.js +18 -0
  7. package/dist/_cjs/hooks/useGetUserEmail.js +19 -0
  8. package/dist/_cjs/index.js +8 -1
  9. package/dist/_cjs/oauth.js +60 -55
  10. package/dist/_cjs/provider.js +5 -2
  11. package/dist/_cjs/store.js +4 -9
  12. package/dist/_cjs/utils/aaUtils.js +2 -2
  13. package/dist/_esm/actions.js +74 -27
  14. package/dist/_esm/connector.js +102 -59
  15. package/dist/_esm/constants.js +1 -1
  16. package/dist/_esm/hooks/useExportPrivateKey.js +18 -0
  17. package/dist/_esm/hooks/useGetUserEmail.js +19 -0
  18. package/dist/_esm/index.js +3 -1
  19. package/dist/_esm/oauth.js +71 -53
  20. package/dist/_esm/provider.js +5 -2
  21. package/dist/_esm/store.js +4 -10
  22. package/dist/_esm/utils/aaUtils.js +2 -2
  23. package/dist/_types/actions.d.ts +35 -6
  24. package/dist/_types/actions.d.ts.map +1 -1
  25. package/dist/_types/connector.d.ts +1 -3
  26. package/dist/_types/connector.d.ts.map +1 -1
  27. package/dist/_types/constants.d.ts +1 -1
  28. package/dist/_types/constants.d.ts.map +1 -1
  29. package/dist/_types/hooks/useExportPrivateKey.d.ts +18 -0
  30. package/dist/_types/hooks/useExportPrivateKey.d.ts.map +1 -0
  31. package/dist/_types/hooks/useGetUserEmail.d.ts +18 -0
  32. package/dist/_types/hooks/useGetUserEmail.d.ts.map +1 -0
  33. package/dist/_types/index.d.ts +4 -2
  34. package/dist/_types/index.d.ts.map +1 -1
  35. package/dist/_types/oauth.d.ts +25 -12
  36. package/dist/_types/oauth.d.ts.map +1 -1
  37. package/dist/_types/provider.d.ts.map +1 -1
  38. package/dist/_types/store.d.ts +11 -7
  39. package/dist/_types/store.d.ts.map +1 -1
  40. package/dist/_types/utils/aaUtils.d.ts +1 -1
  41. package/dist/_types/utils/aaUtils.d.ts.map +1 -1
  42. package/dist/tsconfig.build.tsbuildinfo +1 -1
  43. package/package.json +2 -2
  44. package/src/actions.ts +124 -44
  45. package/src/connector.ts +130 -70
  46. package/src/constants.ts +2 -1
  47. package/src/hooks/useExportPrivateKey.ts +57 -0
  48. package/src/hooks/useGetUserEmail.ts +52 -0
  49. package/src/index.ts +9 -2
  50. package/src/oauth.ts +97 -78
  51. package/src/provider.ts +5 -2
  52. package/src/store.ts +15 -16
  53. package/src/utils/aaUtils.ts +2 -2
  54. package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # @zerodev/wallet-react
2
2
 
3
+ ## 0.0.1-alpha.10
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: use session.token for getUserEmail auth headers
8
+ - Updated dependencies
9
+ - @zerodev/wallet-core@0.0.1-alpha.9
10
+
11
+ ## 0.0.1-alpha.9
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+ - @zerodev/wallet-core@0.0.1-alpha.8
17
+
18
+ ## 0.0.1-alpha.8
19
+
20
+ ### Patch Changes
21
+
22
+ - fix: use internal state for get user email
23
+
24
+ ## 0.0.1-alpha.7
25
+
26
+ ### Patch Changes
27
+
28
+ - Add OTP authentication via Turnkey Auth Proxy
29
+ - Add OAuth backend PKCE flow
30
+ - Add getUserEmail method and React hook
31
+ - Fix passkey login endpoint
32
+ - Fix signature verification with JSON canonicalization
33
+ - Rename turnkeySession to session in OAuth response
34
+ - Updated dependencies
35
+ - @zerodev/wallet-core@0.0.1-alpha.7
36
+
37
+ ## 0.0.1-alpha.6
38
+
39
+ ### Patch Changes
40
+
41
+ - feat: Added private key export feature
42
+ - Updated dependencies
43
+ - @zerodev/wallet-core@0.0.1-alpha.6
44
+
45
+ ## 0.0.1-alpha.5
46
+
47
+ ### Patch Changes
48
+
49
+ - refactor: Simplify chain state management by replacing chainIds array with activeChainId
50
+
51
+ ## 0.0.1-alpha.4
52
+
53
+ ### Patch Changes
54
+
55
+ - fix: kernel instance reinitialisation
56
+
57
+ ## 0.0.1-alpha.3
58
+
59
+ ### Patch Changes
60
+
61
+ - fix: removed lazy initialization of ZD wallet core
62
+
63
+ ## 0.0.1-alpha.2
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies
68
+ - @zerodev/wallet-core@0.0.1-alpha.5
69
+
70
+ ## 0.0.1-alpha.1
71
+
72
+ ### Patch Changes
73
+
74
+ - feat: changed default aaUrl to staging url
75
+
3
76
  ## 0.0.1-alpha.0
4
77
 
5
78
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # @zerodev/wallet-react
2
+
3
+ React hooks and Wagmi connector for ZeroDev Wallet SDK with EIP-7702 gasless transactions.
4
+
5
+ ## Features
6
+
7
+ - **Wagmi Integration** - Works seamlessly with the Wagmi ecosystem
8
+ - **Multiple Auth Methods** - Passkey (WebAuthn), Email OTP, OAuth (Google)
9
+ - **Gasless Transactions** - All transactions are gasless via EIP-7702
10
+ - **Session Management** - Auto-refresh with configurable thresholds
11
+ - **Multi-Chain** - Lazy kernel account creation per chain
12
+ - **TypeScript** - Full type safety with proper generics
13
+ - **React Query** - Built on TanStack Query for optimal UX
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @zerodev/wallet-react @zerodev/wallet-core wagmi viem
19
+ # or
20
+ yarn add @zerodev/wallet-react @zerodev/wallet-core wagmi viem
21
+ # or
22
+ pnpm add @zerodev/wallet-react @zerodev/wallet-core wagmi viem
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### 1. Configure Wagmi
28
+
29
+ ```typescript
30
+ import { createConfig, http } from 'wagmi'
31
+ import { sepolia } from 'wagmi/chains'
32
+ import { zeroDevWallet } from '@zerodev/wallet-react'
33
+
34
+ const config = createConfig({
35
+ chains: [sepolia],
36
+ connectors: [
37
+ zeroDevWallet({
38
+ projectId: 'YOUR_PROJECT_ID',
39
+ chains: [sepolia],
40
+ })
41
+ ],
42
+ transports: {
43
+ [sepolia.id]: http('YOUR_RPC_URL'),
44
+ },
45
+ })
46
+ ```
47
+
48
+ ### 2. Wrap Your App
49
+
50
+ ```typescript
51
+ import { WagmiProvider } from 'wagmi'
52
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
53
+
54
+ const queryClient = new QueryClient()
55
+
56
+ function App() {
57
+ return (
58
+ <WagmiProvider config={config}>
59
+ <QueryClientProvider client={queryClient}>
60
+ <YourApp />
61
+ </QueryClientProvider>
62
+ </WagmiProvider>
63
+ )
64
+ }
65
+ ```
66
+
67
+ ### 3. Authenticate Users
68
+
69
+ ```typescript
70
+ import { useRegisterPasskey, useAuthenticateOAuth, OAUTH_PROVIDERS } from '@zerodev/wallet-react'
71
+
72
+ function LoginPage() {
73
+ const registerPasskey = useRegisterPasskey()
74
+ const authenticateOAuth = useAuthenticateOAuth()
75
+
76
+ return (
77
+ <>
78
+ <button
79
+ onClick={() => registerPasskey.mutate({ email: 'user@example.com' })}
80
+ disabled={registerPasskey.isPending}
81
+ >
82
+ {registerPasskey.isPending ? 'Registering...' : 'Register with Passkey'}
83
+ </button>
84
+
85
+ <button
86
+ onClick={() => authenticateOAuth.mutate({ provider: OAUTH_PROVIDERS.GOOGLE })}
87
+ disabled={authenticateOAuth.isPending}
88
+ >
89
+ {authenticateOAuth.isPending ? 'Authenticating...' : 'Login with Google'}
90
+ </button>
91
+ </>
92
+ )
93
+ }
94
+ ```
95
+
96
+ ### 4. Use Standard Wagmi Hooks
97
+
98
+ ```typescript
99
+ import { useConnection, useSendTransaction, useDisconnect } from 'wagmi'
100
+ import { parseEther } from 'viem'
101
+
102
+ function Dashboard() {
103
+ const { address } = useConnection()
104
+ const { sendTransaction } = useSendTransaction()
105
+ const { disconnect } = useDisconnect()
106
+
107
+ return (
108
+ <>
109
+ <p>Address: {address}</p>
110
+
111
+ <button onClick={() =>
112
+ sendTransaction({
113
+ to: '0x...',
114
+ value: parseEther('0.01')
115
+ })
116
+ }>
117
+ Send Gasless Transaction
118
+ </button>
119
+
120
+ <button onClick={() => disconnect()}>
121
+ Logout
122
+ </button>
123
+ </>
124
+ )
125
+ }
126
+ ```
127
+
128
+ ## Authentication Methods
129
+
130
+ ### Passkey (WebAuthn)
131
+
132
+ ```typescript
133
+ const registerPasskey = useRegisterPasskey()
134
+ const loginPasskey = useLoginPasskey()
135
+
136
+ // Register new passkey
137
+ await registerPasskey.mutateAsync({ email: 'user@example.com' })
138
+
139
+ // Login with existing passkey
140
+ await loginPasskey.mutateAsync({ email: 'user@example.com' })
141
+ ```
142
+
143
+ ### OAuth (Google)
144
+
145
+ ```typescript
146
+ const authenticateOAuth = useAuthenticateOAuth()
147
+
148
+ // Opens popup, backend handles PKCE and token exchange
149
+ // No callback page or OAuth library needed - SDK handles everything
150
+ await authenticateOAuth.mutateAsync({
151
+ provider: OAUTH_PROVIDERS.GOOGLE
152
+ })
153
+ ```
154
+
155
+ ### Email OTP
156
+
157
+ ```typescript
158
+ const sendOTP = useSendOTP()
159
+ const verifyOTP = useVerifyOTP()
160
+
161
+ // Send OTP code
162
+ const { otpId } = await sendOTP.mutateAsync({
163
+ email: 'user@example.com'
164
+ })
165
+
166
+ // Verify OTP code
167
+ await verifyOTP.mutateAsync({
168
+ code: '123456',
169
+ otpId,
170
+ })
171
+ ```
172
+
173
+ ## Configuration Options
174
+
175
+ ```typescript
176
+ type ZeroDevWalletConnectorParams = {
177
+ projectId: string // Required: Your ZeroDev project ID
178
+ organizationId?: string // Optional: Turnkey organization ID
179
+ proxyBaseUrl?: string // Optional: KMS proxy URL
180
+ chains: readonly Chain[] // Required: Supported chains
181
+ rpId?: string // Optional: WebAuthn RP ID
182
+ sessionStorage?: StorageAdapter // Optional: Custom session storage
183
+ autoRefreshSession?: boolean // Optional: Auto-refresh (default: true)
184
+ sessionWarningThreshold?: number // Optional: Refresh threshold in ms (default: 60000)
185
+ }
186
+ ```
187
+
188
+ ## Advanced Usage
189
+
190
+ ### Custom Callbacks
191
+
192
+ ```typescript
193
+ const registerPasskey = useRegisterPasskey({
194
+ mutation: {
195
+ onSuccess: () => {
196
+ console.log('Registration successful!')
197
+ router.push('/dashboard')
198
+ },
199
+ onError: (error) => {
200
+ console.error('Registration failed:', error)
201
+ analytics.track('auth_failed', { method: 'passkey' })
202
+ }
203
+ }
204
+ })
205
+ ```
206
+
207
+ ### Manual Session Refresh
208
+
209
+ ```typescript
210
+ const refreshSession = useRefreshSession()
211
+
212
+ await refreshSession.mutateAsync({})
213
+ ```
214
+
215
+ ### Export Wallet (Seed Phrase)
216
+
217
+ ```typescript
218
+ const exportWallet = useExportWallet()
219
+
220
+ // Container element must exist: <div id="export-container" />
221
+ await exportWallet.mutateAsync({
222
+ iframeContainerId: 'export-container'
223
+ })
224
+ ```
225
+
226
+ ### Export Private Key
227
+
228
+ ```typescript
229
+ const exportPrivateKey = useExportPrivateKey()
230
+
231
+ // Container element must exist: <div id="export-container" />
232
+ await exportPrivateKey.mutateAsync({
233
+ iframeContainerId: 'export-container'
234
+ })
235
+ ```
236
+
237
+ ### Get User Email
238
+
239
+ ```typescript
240
+ const getUserEmail = useGetUserEmail()
241
+
242
+ // Fetch user's email from the backend
243
+ const { email } = await getUserEmail.mutateAsync({
244
+ organizationId: session.organizationId,
245
+ projectId: 'your-project-id'
246
+ })
247
+ ```
248
+
249
+ ## API Reference
250
+
251
+ ### Hooks
252
+
253
+ All hooks follow the TanStack Query mutation pattern:
254
+
255
+ - `useRegisterPasskey()` - Register with passkey
256
+ - `useLoginPasskey()` - Login with passkey
257
+ - `useAuthenticateOAuth()` - OAuth (Google popup)
258
+ - `useSendOTP()` - Send OTP via email
259
+ - `useVerifyOTP()` - Verify OTP code
260
+ - `useRefreshSession()` - Manually refresh session
261
+ - `useExportWallet()` - Export wallet seed phrase
262
+ - `useExportPrivateKey()` - Export wallet private key
263
+ - `useGetUserEmail()` - Get user's email address
264
+
265
+ ### Connector
266
+
267
+ - `zeroDevWallet(params)` - Wagmi connector factory
268
+
269
+ ### Constants
270
+
271
+ - `OAUTH_PROVIDERS.GOOGLE` - Google OAuth provider constant
272
+
273
+ ### Types
274
+
275
+ - `OAuthProvider` - OAuth provider type
276
+ - `ZeroDevWalletConnectorParams` - Connector parameters
277
+ - `ZeroDevWalletState` - Store state type
278
+ - `ZeroDevProvider` - EIP-1193 provider type
279
+
280
+ ## License
281
+
282
+ MIT
@@ -6,8 +6,11 @@ exports.authenticateOAuth = authenticateOAuth;
6
6
  exports.sendOTP = sendOTP;
7
7
  exports.verifyOTP = verifyOTP;
8
8
  exports.refreshSession = refreshSession;
9
+ exports.getUserEmail = getUserEmail;
9
10
  exports.exportWallet = exportWallet;
11
+ exports.exportPrivateKey = exportPrivateKey;
10
12
  const actions_1 = require("@wagmi/core/actions");
13
+ const wallet_core_1 = require("@zerodev/wallet-core");
11
14
  const oauth_js_1 = require("./oauth.js");
12
15
  function getZeroDevConnector(config) {
13
16
  const connector = config.connectors.find((c) => c.id === 'zerodev-wallet');
@@ -62,40 +65,29 @@ async function authenticateOAuth(config, parameters) {
62
65
  if (!wallet)
63
66
  throw new Error('Wallet not initialized');
64
67
  if (!oauthConfig) {
65
- throw new Error('OAuth is not configured. Please provide oauthConfig to zeroDevWallet connector.');
66
- }
67
- let clientId = parameters.clientId;
68
- if (!clientId) {
69
- clientId = oauthConfig.googleClientId;
70
- }
71
- if (!clientId) {
72
- throw new Error(`Client ID not configured for ${parameters.provider}`);
73
- }
74
- if (!oauthConfig.redirectUri) {
75
- throw new Error('OAuth redirect URI is not configured.');
68
+ throw new Error('Wallet not initialized. Please wait for connector setup.');
76
69
  }
77
70
  const publicKey = await wallet.getPublicKey();
78
71
  if (!publicKey) {
79
72
  throw new Error('Failed to get wallet public key');
80
73
  }
81
- const nonce = (0, oauth_js_1.generateOAuthNonce)(publicKey);
82
- const oauthUrl = (0, oauth_js_1.buildOAuthUrl)({
74
+ const oauthUrl = (0, oauth_js_1.buildBackendOAuthUrl)({
83
75
  provider: parameters.provider,
84
- clientId,
85
- redirectUri: oauthConfig.redirectUri,
86
- nonce,
76
+ backendUrl: oauthConfig.backendUrl,
77
+ projectId: oauthConfig.projectId,
78
+ publicKey,
79
+ returnTo: `${window.location.origin}?oauth_success=true&oauth_provider=${parameters.provider}`,
87
80
  });
88
81
  const authWindow = (0, oauth_js_1.openOAuthPopup)(oauthUrl);
89
82
  if (!authWindow) {
90
83
  throw new Error(`Failed to open ${parameters.provider} login window.`);
91
84
  }
92
85
  return new Promise((resolve, reject) => {
93
- (0, oauth_js_1.pollOAuthPopup)(authWindow, window.location.origin, async (idToken) => {
86
+ const cleanup = (0, oauth_js_1.listenForOAuthMessage)(authWindow, window.location.origin, async () => {
94
87
  try {
95
88
  await wallet.auth({
96
89
  type: 'oauth',
97
90
  provider: parameters.provider,
98
- credential: idToken,
99
91
  });
100
92
  const [session, eoaAccount] = await Promise.all([
101
93
  wallet.getSession(),
@@ -109,7 +101,10 @@ async function authenticateOAuth(config, parameters) {
109
101
  catch (err) {
110
102
  reject(err);
111
103
  }
112
- }, reject);
104
+ }, (error) => {
105
+ cleanup();
106
+ reject(error);
107
+ });
113
108
  });
114
109
  }
115
110
  async function sendOTP(config, parameters) {
@@ -129,7 +124,6 @@ async function sendOTP(config, parameters) {
129
124
  });
130
125
  return {
131
126
  otpId: result.otpId,
132
- subOrganizationId: result.subOrganizationId,
133
127
  };
134
128
  }
135
129
  async function verifyOTP(config, parameters) {
@@ -143,7 +137,6 @@ async function verifyOTP(config, parameters) {
143
137
  mode: 'verifyOtp',
144
138
  otpId: parameters.otpId,
145
139
  otpCode: parameters.code,
146
- subOrganizationId: parameters.subOrganizationId,
147
140
  });
148
141
  const [session, eoaAccount] = await Promise.all([
149
142
  wallet.getSession(),
@@ -165,24 +158,43 @@ async function refreshSession(config, parameters = {}) {
165
158
  store.getState().setSession(newSession || null);
166
159
  return newSession;
167
160
  }
161
+ async function getUserEmail(config) {
162
+ const connector = getZeroDevConnector(config);
163
+ const store = await connector.getStore();
164
+ const wallet = store.getState().wallet;
165
+ if (!wallet)
166
+ throw new Error('Wallet not initialized');
167
+ const oauthConfig = store.getState().oauthConfig;
168
+ if (!oauthConfig) {
169
+ throw new Error('Wallet not initialized. Please wait for connector setup.');
170
+ }
171
+ const session = store.getState().session;
172
+ if (!session) {
173
+ throw new Error('No active session');
174
+ }
175
+ return await wallet.client.getUserEmail({
176
+ organizationId: session.organizationId,
177
+ projectId: oauthConfig.projectId,
178
+ token: session.token,
179
+ });
180
+ }
168
181
  async function exportWallet(config, parameters) {
169
182
  const connector = parameters.connector ?? getZeroDevConnector(config);
170
183
  const store = await connector.getStore();
171
184
  const wallet = store.getState().wallet;
172
185
  if (!wallet)
173
186
  throw new Error('Wallet not initialized');
174
- const { exportWallet: exportWalletSdk, createIframeStamper } = await Promise.resolve().then(() => require('@zerodev/wallet-core'));
175
187
  const iframeContainer = document.getElementById(parameters.iframeContainerId);
176
188
  if (!iframeContainer) {
177
189
  throw new Error('Iframe container not found');
178
190
  }
179
- const iframeStamper = await createIframeStamper({
191
+ const iframeStamper = await (0, wallet_core_1.createIframeStamper)({
180
192
  iframeUrl: 'https://export.turnkey.com',
181
193
  iframeContainer,
182
194
  iframeElementId: 'export-wallet-iframe',
183
195
  });
184
196
  const publicKey = await iframeStamper.init();
185
- const { exportBundle, organizationId } = await exportWalletSdk({
197
+ const { exportBundle, organizationId } = await (0, wallet_core_1.exportWallet)({
186
198
  wallet,
187
199
  targetPublicKey: publicKey,
188
200
  });
@@ -191,3 +203,29 @@ async function exportWallet(config, parameters) {
191
203
  throw new Error('Failed to inject export bundle');
192
204
  }
193
205
  }
206
+ async function exportPrivateKey(config, parameters) {
207
+ const connector = parameters.connector ?? getZeroDevConnector(config);
208
+ const store = await connector.getStore();
209
+ const wallet = store.getState().wallet;
210
+ if (!wallet)
211
+ throw new Error('Wallet not initialized');
212
+ const iframeContainer = document.getElementById(parameters.iframeContainerId);
213
+ if (!iframeContainer) {
214
+ throw new Error('Iframe container not found');
215
+ }
216
+ const iframeStamper = await (0, wallet_core_1.createIframeStamper)({
217
+ iframeUrl: 'https://export.turnkey.com',
218
+ iframeContainer,
219
+ iframeElementId: 'export-private-key-iframe',
220
+ });
221
+ const publicKey = await iframeStamper.init();
222
+ const { exportBundle, organizationId } = await (0, wallet_core_1.exportPrivateKey)({
223
+ wallet,
224
+ targetPublicKey: publicKey,
225
+ ...(parameters.address && { address: parameters.address }),
226
+ });
227
+ const success = await iframeStamper.injectKeyExportBundle(exportBundle, organizationId, parameters.keyFormat ?? 'Hexadecimal');
228
+ if (success !== true) {
229
+ throw new Error('Failed to inject export bundle');
230
+ }
231
+ }