@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.
- package/CHANGELOG.md +73 -0
- package/README.md +282 -0
- package/dist/_cjs/actions.js +62 -24
- package/dist/_cjs/connector.js +86 -52
- package/dist/_cjs/constants.js +1 -1
- 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/_cjs/utils/aaUtils.js +2 -2
- package/dist/_esm/actions.js +74 -27
- package/dist/_esm/connector.js +102 -59
- package/dist/_esm/constants.js +1 -1
- 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/_esm/utils/aaUtils.js +2 -2
- package/dist/_types/actions.d.ts +35 -6
- package/dist/_types/actions.d.ts.map +1 -1
- package/dist/_types/connector.d.ts +1 -3
- package/dist/_types/connector.d.ts.map +1 -1
- package/dist/_types/constants.d.ts +1 -1
- package/dist/_types/constants.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/_types/utils/aaUtils.d.ts +1 -1
- package/dist/_types/utils/aaUtils.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 +130 -70
- package/src/constants.ts +2 -1
- 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/src/utils/aaUtils.ts +2 -2
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type UseQueryOptions,
|
|
5
|
+
type UseQueryResult,
|
|
6
|
+
useQuery,
|
|
7
|
+
} from '@tanstack/react-query'
|
|
8
|
+
import { type Config, type ResolvedRegister, useConfig } from 'wagmi'
|
|
9
|
+
import { getUserEmail } from '../actions.js'
|
|
10
|
+
|
|
11
|
+
type ConfigParameter<config extends Config = Config> = {
|
|
12
|
+
config?: Config | config | undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook to fetch user email address
|
|
17
|
+
*/
|
|
18
|
+
export function useGetUserEmail<
|
|
19
|
+
config extends Config = ResolvedRegister['config'],
|
|
20
|
+
>(parameters: useGetUserEmail.Parameters<config>): useGetUserEmail.ReturnType {
|
|
21
|
+
const { query } = parameters
|
|
22
|
+
const config = useConfig(parameters)
|
|
23
|
+
|
|
24
|
+
return useQuery({
|
|
25
|
+
...query,
|
|
26
|
+
queryKey: ['getUserEmail'],
|
|
27
|
+
queryFn: async () => {
|
|
28
|
+
return getUserEmail(config)
|
|
29
|
+
},
|
|
30
|
+
enabled: Boolean(config),
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare namespace useGetUserEmail {
|
|
35
|
+
type Parameters<config extends Config = Config> = ConfigParameter<config> & {
|
|
36
|
+
query?:
|
|
37
|
+
| Omit<
|
|
38
|
+
UseQueryOptions<
|
|
39
|
+
getUserEmail.ReturnType,
|
|
40
|
+
getUserEmail.ErrorType,
|
|
41
|
+
getUserEmail.ReturnType
|
|
42
|
+
>,
|
|
43
|
+
'queryKey' | 'queryFn'
|
|
44
|
+
>
|
|
45
|
+
| undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type ReturnType = UseQueryResult<
|
|
49
|
+
getUserEmail.ReturnType,
|
|
50
|
+
getUserEmail.ErrorType
|
|
51
|
+
>
|
|
52
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
export type { ZeroDevWalletConnectorParams } from './connector.js'
|
|
2
2
|
export { zeroDevWallet } from './connector.js'
|
|
3
3
|
export { useAuthenticateOAuth } from './hooks/useAuthenticateOAuth.js'
|
|
4
|
+
export { useExportPrivateKey } from './hooks/useExportPrivateKey.js'
|
|
4
5
|
export { useExportWallet } from './hooks/useExportWallet.js'
|
|
6
|
+
export { useGetUserEmail } from './hooks/useGetUserEmail.js'
|
|
5
7
|
export { useLoginPasskey } from './hooks/useLoginPasskey.js'
|
|
6
8
|
export { useRefreshSession } from './hooks/useRefreshSession.js'
|
|
7
9
|
export { useRegisterPasskey } from './hooks/useRegisterPasskey.js'
|
|
8
10
|
export { useSendOTP } from './hooks/useSendOTP.js'
|
|
9
11
|
export { useVerifyOTP } from './hooks/useVerifyOTP.js'
|
|
10
|
-
export type {
|
|
11
|
-
export {
|
|
12
|
+
export type { OAuthMessageData, OAuthProvider } from './oauth.js'
|
|
13
|
+
export {
|
|
14
|
+
buildBackendOAuthUrl,
|
|
15
|
+
handleOAuthCallback,
|
|
16
|
+
listenForOAuthMessage,
|
|
17
|
+
OAUTH_PROVIDERS,
|
|
18
|
+
} from './oauth.js'
|
|
12
19
|
export type { ZeroDevProvider } from './provider.js'
|
|
13
20
|
export type { ZeroDevWalletState } from './store.js'
|
|
14
21
|
export { createZeroDevWalletStore } from './store.js'
|
package/src/oauth.ts
CHANGED
|
@@ -7,56 +7,17 @@ export const OAUTH_PROVIDERS = {
|
|
|
7
7
|
export type OAuthProvider =
|
|
8
8
|
(typeof OAUTH_PROVIDERS)[keyof typeof OAUTH_PROVIDERS]
|
|
9
9
|
|
|
10
|
-
export type
|
|
11
|
-
googleClientId?: string
|
|
12
|
-
redirectUri: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type OAuthFlowParams = {
|
|
10
|
+
export type BackendOAuthFlowParams = {
|
|
16
11
|
provider: OAuthProvider
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
backendUrl: string
|
|
13
|
+
projectId: string
|
|
14
|
+
publicKey: string
|
|
15
|
+
returnTo: string
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
|
24
|
-
|
|
25
18
|
const POPUP_WIDTH = 500
|
|
26
19
|
const POPUP_HEIGHT = 600
|
|
27
20
|
|
|
28
|
-
export function buildOAuthUrl(params: OAuthFlowParams): string {
|
|
29
|
-
const { provider, clientId, redirectUri, nonce, state } = params
|
|
30
|
-
|
|
31
|
-
if (provider !== OAUTH_PROVIDERS.GOOGLE) {
|
|
32
|
-
throw new Error(`Unsupported OAuth provider: ${provider}`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const authUrl = new URL(GOOGLE_AUTH_URL)
|
|
36
|
-
authUrl.searchParams.set('client_id', clientId)
|
|
37
|
-
authUrl.searchParams.set('redirect_uri', redirectUri)
|
|
38
|
-
authUrl.searchParams.set('response_type', 'id_token')
|
|
39
|
-
authUrl.searchParams.set('scope', 'openid email profile')
|
|
40
|
-
authUrl.searchParams.set('nonce', nonce)
|
|
41
|
-
authUrl.searchParams.set('prompt', 'select_account')
|
|
42
|
-
|
|
43
|
-
let stateParam = `provider=${provider}`
|
|
44
|
-
if (state) {
|
|
45
|
-
const additionalState = Object.entries(state)
|
|
46
|
-
.map(
|
|
47
|
-
([key, value]) =>
|
|
48
|
-
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
|
|
49
|
-
)
|
|
50
|
-
.join('&')
|
|
51
|
-
if (additionalState) {
|
|
52
|
-
stateParam += `&${additionalState}`
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
authUrl.searchParams.set('state', stateParam)
|
|
56
|
-
|
|
57
|
-
return authUrl.toString()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
21
|
export function openOAuthPopup(url: string): Window | null {
|
|
61
22
|
const width = POPUP_WIDTH
|
|
62
23
|
const height = POPUP_HEIGHT
|
|
@@ -76,49 +37,107 @@ export function openOAuthPopup(url: string): Window | null {
|
|
|
76
37
|
return authWindow
|
|
77
38
|
}
|
|
78
39
|
|
|
79
|
-
export function
|
|
80
|
-
|
|
81
|
-
|
|
40
|
+
export function generateOAuthNonce(publicKey: string): string {
|
|
41
|
+
return sha256(publicKey as Hex).replace(/^0x/, '')
|
|
42
|
+
}
|
|
82
43
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Build OAuth URL that redirects to backend's OAuth endpoint
|
|
46
|
+
* The backend handles PKCE, client credentials, and token exchange
|
|
47
|
+
*/
|
|
48
|
+
export function buildBackendOAuthUrl(params: BackendOAuthFlowParams): string {
|
|
49
|
+
const { provider, backendUrl, projectId, publicKey, returnTo } = params
|
|
50
|
+
|
|
51
|
+
if (provider !== OAUTH_PROVIDERS.GOOGLE) {
|
|
52
|
+
throw new Error(`Unsupported OAuth provider: ${provider}`)
|
|
86
53
|
}
|
|
87
54
|
|
|
88
|
-
|
|
55
|
+
const oauthUrl = new URL(`${backendUrl}/oauth/google/login`)
|
|
56
|
+
oauthUrl.searchParams.set('project_id', projectId)
|
|
57
|
+
oauthUrl.searchParams.set('pub_key', publicKey.replace(/^0x/, ''))
|
|
58
|
+
oauthUrl.searchParams.set('return_to', returnTo)
|
|
59
|
+
|
|
60
|
+
return oauthUrl.toString()
|
|
89
61
|
}
|
|
90
62
|
|
|
91
|
-
export
|
|
63
|
+
export type OAuthMessageData = {
|
|
64
|
+
type: 'oauth_success' | 'oauth_error'
|
|
65
|
+
error?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Listen for OAuth completion via postMessage from popup
|
|
70
|
+
* The popup sends a message when it detects a successful redirect
|
|
71
|
+
*/
|
|
72
|
+
export function listenForOAuthMessage(
|
|
92
73
|
authWindow: Window,
|
|
93
|
-
|
|
94
|
-
onSuccess: (
|
|
74
|
+
expectedOrigin: string,
|
|
75
|
+
onSuccess: () => void,
|
|
95
76
|
onError: (error: Error) => void,
|
|
96
|
-
): void {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Ignore cross-origin errors
|
|
77
|
+
): () => void {
|
|
78
|
+
let cleaned = false
|
|
79
|
+
|
|
80
|
+
const handleMessage = (event: MessageEvent<OAuthMessageData>) => {
|
|
81
|
+
// Only trust messages from expected origin
|
|
82
|
+
if (event.origin !== expectedOrigin) return
|
|
83
|
+
if (!event.data || typeof event.data !== 'object') return
|
|
84
|
+
|
|
85
|
+
if (event.data.type === 'oauth_success') {
|
|
86
|
+
cleanup()
|
|
87
|
+
onSuccess()
|
|
88
|
+
} else if (event.data.type === 'oauth_error') {
|
|
89
|
+
cleanup()
|
|
90
|
+
onError(new Error(event.data.error || 'OAuth authentication failed'))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const checkWindowClosed = setInterval(() => {
|
|
95
|
+
if (authWindow.closed) {
|
|
96
|
+
cleanup()
|
|
97
|
+
onError(new Error('Authentication window was closed'))
|
|
118
98
|
}
|
|
119
99
|
}, 500)
|
|
100
|
+
|
|
101
|
+
const cleanup = () => {
|
|
102
|
+
if (cleaned) return
|
|
103
|
+
cleaned = true
|
|
104
|
+
window.removeEventListener('message', handleMessage)
|
|
105
|
+
clearInterval(checkWindowClosed)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
window.addEventListener('message', handleMessage)
|
|
109
|
+
|
|
110
|
+
return cleanup
|
|
120
111
|
}
|
|
121
112
|
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Handle OAuth callback on the return page
|
|
115
|
+
* Call this on the page that receives the OAuth redirect
|
|
116
|
+
* It sends a postMessage to the opener and closes the window
|
|
117
|
+
*/
|
|
118
|
+
export function handleOAuthCallback(successParam = 'oauth_success'): boolean {
|
|
119
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
120
|
+
const isSuccess = urlParams.get(successParam) === 'true'
|
|
121
|
+
const error = urlParams.get('error')
|
|
122
|
+
|
|
123
|
+
if (window.opener) {
|
|
124
|
+
if (isSuccess) {
|
|
125
|
+
window.opener.postMessage(
|
|
126
|
+
{ type: 'oauth_success' } satisfies OAuthMessageData,
|
|
127
|
+
window.location.origin,
|
|
128
|
+
)
|
|
129
|
+
window.close()
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
if (error) {
|
|
133
|
+
window.opener.postMessage(
|
|
134
|
+
{ type: 'oauth_error', error } satisfies OAuthMessageData,
|
|
135
|
+
window.location.origin,
|
|
136
|
+
)
|
|
137
|
+
window.close()
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false
|
|
124
143
|
}
|
package/src/provider.ts
CHANGED
|
@@ -115,7 +115,10 @@ export function createProvider({
|
|
|
115
115
|
|
|
116
116
|
async request({ method, params }: { method: string; params?: any[] }) {
|
|
117
117
|
const state = store.getState()
|
|
118
|
-
const activeChainId = state.
|
|
118
|
+
const activeChainId = state.activeChainId
|
|
119
|
+
if (!activeChainId) {
|
|
120
|
+
throw new Error('No active chain')
|
|
121
|
+
}
|
|
119
122
|
|
|
120
123
|
switch (method) {
|
|
121
124
|
case 'eth_accounts': {
|
|
@@ -219,7 +222,7 @@ export function createProvider({
|
|
|
219
222
|
const chainId_number = parseInt(chainId, 16)
|
|
220
223
|
|
|
221
224
|
// Update active chain
|
|
222
|
-
store.getState().
|
|
225
|
+
store.getState().setActiveChainId(chainId_number)
|
|
223
226
|
|
|
224
227
|
// Emit chainChanged event
|
|
225
228
|
emitter.emit('chainChanged', chainId)
|
package/src/store.ts
CHANGED
|
@@ -10,7 +10,12 @@ import type { LocalAccount } from 'viem'
|
|
|
10
10
|
import type { SmartAccount } from 'viem/account-abstraction'
|
|
11
11
|
import { create } from 'zustand'
|
|
12
12
|
import { persist, subscribeWithSelector } from 'zustand/middleware'
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Internal OAuth config stored in the state (derived from connector params)
|
|
15
|
+
type InternalOAuthConfig = {
|
|
16
|
+
backendUrl: string
|
|
17
|
+
projectId: string
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
export type ZeroDevWalletState = {
|
|
16
21
|
// Core
|
|
@@ -19,15 +24,15 @@ export type ZeroDevWalletState = {
|
|
|
19
24
|
session: ZeroDevWalletSession | null
|
|
20
25
|
|
|
21
26
|
// Multi-chain support
|
|
22
|
-
|
|
27
|
+
activeChainId: number | null
|
|
23
28
|
kernelAccounts: Map<number, SmartAccount<KernelSmartAccountImplementation>>
|
|
24
29
|
kernelClients: Map<number, KernelAccountClient>
|
|
25
30
|
|
|
26
31
|
// Session expiry
|
|
27
32
|
isExpiring: boolean
|
|
28
33
|
|
|
29
|
-
// OAuth config (
|
|
30
|
-
oauthConfig:
|
|
34
|
+
// OAuth config (derived from connector params)
|
|
35
|
+
oauthConfig: InternalOAuthConfig | null
|
|
31
36
|
|
|
32
37
|
// Actions
|
|
33
38
|
setWallet: (wallet: ZeroDevWalletSDK) => void
|
|
@@ -38,9 +43,9 @@ export type ZeroDevWalletState = {
|
|
|
38
43
|
) => void
|
|
39
44
|
setKernelClient: (chainId: number, client: KernelAccountClient) => void
|
|
40
45
|
setSession: (session: ZeroDevWalletSession | null) => void
|
|
41
|
-
|
|
46
|
+
setActiveChainId: (chainId: number | null) => void
|
|
42
47
|
setIsExpiring: (isExpiring: boolean) => void
|
|
43
|
-
setOAuthConfig: (config:
|
|
48
|
+
setOAuthConfig: (config: InternalOAuthConfig | null) => void
|
|
44
49
|
clear: () => void
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -53,7 +58,7 @@ export const createZeroDevWalletStore = () =>
|
|
|
53
58
|
wallet: null,
|
|
54
59
|
eoaAccount: null,
|
|
55
60
|
session: null,
|
|
56
|
-
|
|
61
|
+
activeChainId: null,
|
|
57
62
|
kernelAccounts: new Map(),
|
|
58
63
|
kernelClients: new Map(),
|
|
59
64
|
isExpiring: false,
|
|
@@ -78,13 +83,7 @@ export const createZeroDevWalletStore = () =>
|
|
|
78
83
|
|
|
79
84
|
setSession: (session) => set({ session }),
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
const { chainIds } = get()
|
|
83
|
-
// Move chainId to front, remove duplicates
|
|
84
|
-
set({
|
|
85
|
-
chainIds: [chainId, ...chainIds.filter((id) => id !== chainId)],
|
|
86
|
-
})
|
|
87
|
-
},
|
|
86
|
+
setActiveChainId: (chainId) => set({ activeChainId: chainId }),
|
|
88
87
|
|
|
89
88
|
setIsExpiring: (isExpiring) => set({ isExpiring }),
|
|
90
89
|
|
|
@@ -97,7 +96,7 @@ export const createZeroDevWalletStore = () =>
|
|
|
97
96
|
kernelAccounts: new Map(),
|
|
98
97
|
kernelClients: new Map(),
|
|
99
98
|
isExpiring: false,
|
|
100
|
-
|
|
99
|
+
activeChainId: null,
|
|
101
100
|
}),
|
|
102
101
|
}),
|
|
103
102
|
{
|
|
@@ -105,7 +104,7 @@ export const createZeroDevWalletStore = () =>
|
|
|
105
104
|
// Only persist session data, not clients or accounts
|
|
106
105
|
partialize: (state) => ({
|
|
107
106
|
session: state.session,
|
|
108
|
-
|
|
107
|
+
activeChainId: state.activeChainId,
|
|
109
108
|
}),
|
|
110
109
|
},
|
|
111
110
|
),
|
package/src/utils/aaUtils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZERODEV_AA_URL } from '../constants.js'
|
|
2
2
|
|
|
3
|
-
export function getAAUrl(chainId: number, aaUrl?: string) {
|
|
4
|
-
return aaUrl || `${ZERODEV_AA_URL}${
|
|
3
|
+
export function getAAUrl(projectId: string, chainId: number, aaUrl?: string) {
|
|
4
|
+
return aaUrl || `${ZERODEV_AA_URL}${projectId}/chain/${chainId}`
|
|
5
5
|
}
|