@zerodev/wallet-react 0.0.1-alpha.0

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 (88) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/_cjs/actions.js +193 -0
  3. package/dist/_cjs/connector.js +221 -0
  4. package/dist/_cjs/constants.js +4 -0
  5. package/dist/_cjs/hooks/useAuthenticateOAuth.js +18 -0
  6. package/dist/_cjs/hooks/useExportWallet.js +18 -0
  7. package/dist/_cjs/hooks/useLoginPasskey.js +18 -0
  8. package/dist/_cjs/hooks/useRefreshSession.js +18 -0
  9. package/dist/_cjs/hooks/useRegisterPasskey.js +18 -0
  10. package/dist/_cjs/hooks/useSendOTP.js +18 -0
  11. package/dist/_cjs/hooks/useVerifyOTP.js +18 -0
  12. package/dist/_cjs/index.js +23 -0
  13. package/dist/_cjs/oauth.js +84 -0
  14. package/dist/_cjs/package.json +1 -0
  15. package/dist/_cjs/provider.js +163 -0
  16. package/dist/_cjs/store.js +51 -0
  17. package/dist/_cjs/utils/aaUtils.js +7 -0
  18. package/dist/_cjs/utils/timers.js +50 -0
  19. package/dist/_esm/actions.js +225 -0
  20. package/dist/_esm/connector.js +248 -0
  21. package/dist/_esm/constants.js +1 -0
  22. package/dist/_esm/hooks/useAuthenticateOAuth.js +18 -0
  23. package/dist/_esm/hooks/useExportWallet.js +18 -0
  24. package/dist/_esm/hooks/useLoginPasskey.js +18 -0
  25. package/dist/_esm/hooks/useRefreshSession.js +18 -0
  26. package/dist/_esm/hooks/useRegisterPasskey.js +18 -0
  27. package/dist/_esm/hooks/useSendOTP.js +18 -0
  28. package/dist/_esm/hooks/useVerifyOTP.js +18 -0
  29. package/dist/_esm/index.js +10 -0
  30. package/dist/_esm/oauth.js +77 -0
  31. package/dist/_esm/package.json +1 -0
  32. package/dist/_esm/provider.js +169 -0
  33. package/dist/_esm/store.js +51 -0
  34. package/dist/_esm/utils/aaUtils.js +4 -0
  35. package/dist/_esm/utils/timers.js +55 -0
  36. package/dist/_types/actions.d.ts +124 -0
  37. package/dist/_types/actions.d.ts.map +1 -0
  38. package/dist/_types/connector.d.ts +18 -0
  39. package/dist/_types/connector.d.ts.map +1 -0
  40. package/dist/_types/constants.d.ts +2 -0
  41. package/dist/_types/constants.d.ts.map +1 -0
  42. package/dist/_types/hooks/useAuthenticateOAuth.d.ts +18 -0
  43. package/dist/_types/hooks/useAuthenticateOAuth.d.ts.map +1 -0
  44. package/dist/_types/hooks/useExportWallet.d.ts +18 -0
  45. package/dist/_types/hooks/useExportWallet.d.ts.map +1 -0
  46. package/dist/_types/hooks/useLoginPasskey.d.ts +18 -0
  47. package/dist/_types/hooks/useLoginPasskey.d.ts.map +1 -0
  48. package/dist/_types/hooks/useRefreshSession.d.ts +18 -0
  49. package/dist/_types/hooks/useRefreshSession.d.ts.map +1 -0
  50. package/dist/_types/hooks/useRegisterPasskey.d.ts +18 -0
  51. package/dist/_types/hooks/useRegisterPasskey.d.ts.map +1 -0
  52. package/dist/_types/hooks/useSendOTP.d.ts +18 -0
  53. package/dist/_types/hooks/useSendOTP.d.ts.map +1 -0
  54. package/dist/_types/hooks/useVerifyOTP.d.ts +18 -0
  55. package/dist/_types/hooks/useVerifyOTP.d.ts.map +1 -0
  56. package/dist/_types/index.d.ts +15 -0
  57. package/dist/_types/index.d.ts.map +1 -0
  58. package/dist/_types/oauth.d.ts +21 -0
  59. package/dist/_types/oauth.d.ts.map +1 -0
  60. package/dist/_types/provider.d.ts +19 -0
  61. package/dist/_types/provider.d.ts.map +1 -0
  62. package/dist/_types/store.d.ts +52 -0
  63. package/dist/_types/store.d.ts.map +1 -0
  64. package/dist/_types/utils/aaUtils.d.ts +2 -0
  65. package/dist/_types/utils/aaUtils.d.ts.map +1 -0
  66. package/dist/_types/utils/timers.d.ts +22 -0
  67. package/dist/_types/utils/timers.d.ts.map +1 -0
  68. package/dist/tsconfig.build.tsbuildinfo +1 -0
  69. package/package.json +48 -0
  70. package/package.json.type +1 -0
  71. package/src/actions.ts +402 -0
  72. package/src/connector.ts +336 -0
  73. package/src/constants.ts +1 -0
  74. package/src/hooks/useAuthenticateOAuth.ts +57 -0
  75. package/src/hooks/useExportWallet.ts +57 -0
  76. package/src/hooks/useLoginPasskey.ts +57 -0
  77. package/src/hooks/useRefreshSession.ts +57 -0
  78. package/src/hooks/useRegisterPasskey.ts +57 -0
  79. package/src/hooks/useSendOTP.ts +57 -0
  80. package/src/hooks/useVerifyOTP.ts +57 -0
  81. package/src/index.ts +14 -0
  82. package/src/oauth.ts +124 -0
  83. package/src/provider.ts +235 -0
  84. package/src/store.ts +113 -0
  85. package/src/utils/aaUtils.ts +5 -0
  86. package/src/utils/timers.ts +80 -0
  87. package/tsconfig.build.json +10 -0
  88. package/tsconfig.build.tsbuildinfo +1 -0
@@ -0,0 +1,57 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type UseMutationOptions,
5
+ type UseMutationResult,
6
+ useMutation,
7
+ } from '@tanstack/react-query'
8
+ import { type Config, type ResolvedRegister, useConfig } from 'wagmi'
9
+ import { verifyOTP } from '../actions.js'
10
+
11
+ type ConfigParameter<config extends Config = Config> = {
12
+ config?: Config | config | undefined
13
+ }
14
+
15
+ /**
16
+ * Hook to verify OTP code
17
+ */
18
+ export function useVerifyOTP<
19
+ config extends Config = ResolvedRegister['config'],
20
+ context = unknown,
21
+ >(
22
+ parameters: useVerifyOTP.Parameters<config, context> = {},
23
+ ): useVerifyOTP.ReturnType<context> {
24
+ const { mutation } = parameters
25
+ const config = useConfig(parameters)
26
+
27
+ return useMutation({
28
+ ...mutation,
29
+ async mutationFn(variables: verifyOTP.Parameters) {
30
+ return verifyOTP(config, variables)
31
+ },
32
+ mutationKey: ['verifyOTP'],
33
+ })
34
+ }
35
+
36
+ export declare namespace useVerifyOTP {
37
+ type Parameters<
38
+ config extends Config = Config,
39
+ context = unknown,
40
+ > = ConfigParameter<config> & {
41
+ mutation?:
42
+ | UseMutationOptions<
43
+ verifyOTP.ReturnType,
44
+ verifyOTP.ErrorType,
45
+ verifyOTP.Parameters,
46
+ context
47
+ >
48
+ | undefined
49
+ }
50
+
51
+ type ReturnType<context = unknown> = UseMutationResult<
52
+ verifyOTP.ReturnType,
53
+ verifyOTP.ErrorType,
54
+ verifyOTP.Parameters,
55
+ context
56
+ >
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export type { ZeroDevWalletConnectorParams } from './connector.js'
2
+ export { zeroDevWallet } from './connector.js'
3
+ export { useAuthenticateOAuth } from './hooks/useAuthenticateOAuth.js'
4
+ export { useExportWallet } from './hooks/useExportWallet.js'
5
+ export { useLoginPasskey } from './hooks/useLoginPasskey.js'
6
+ export { useRefreshSession } from './hooks/useRefreshSession.js'
7
+ export { useRegisterPasskey } from './hooks/useRegisterPasskey.js'
8
+ export { useSendOTP } from './hooks/useSendOTP.js'
9
+ export { useVerifyOTP } from './hooks/useVerifyOTP.js'
10
+ export type { OAuthConfig, OAuthProvider } from './oauth.js'
11
+ export { OAUTH_PROVIDERS } from './oauth.js'
12
+ export type { ZeroDevProvider } from './provider.js'
13
+ export type { ZeroDevWalletState } from './store.js'
14
+ export { createZeroDevWalletStore } from './store.js'
package/src/oauth.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { type Hex, sha256 } from 'viem'
2
+
3
+ export const OAUTH_PROVIDERS = {
4
+ GOOGLE: 'google',
5
+ } as const
6
+
7
+ export type OAuthProvider =
8
+ (typeof OAUTH_PROVIDERS)[keyof typeof OAUTH_PROVIDERS]
9
+
10
+ export type OAuthConfig = {
11
+ googleClientId?: string
12
+ redirectUri: string
13
+ }
14
+
15
+ export type OAuthFlowParams = {
16
+ provider: OAuthProvider
17
+ clientId: string
18
+ redirectUri: string
19
+ nonce: string
20
+ state?: Record<string, string>
21
+ }
22
+
23
+ const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
24
+
25
+ const POPUP_WIDTH = 500
26
+ const POPUP_HEIGHT = 600
27
+
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
+ export function openOAuthPopup(url: string): Window | null {
61
+ const width = POPUP_WIDTH
62
+ const height = POPUP_HEIGHT
63
+ const left = window.screenX + (window.innerWidth - width) / 2
64
+ const top = window.screenY + (window.innerHeight - height) / 2
65
+
66
+ const authWindow = window.open(
67
+ 'about:blank',
68
+ '_blank',
69
+ `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes`,
70
+ )
71
+
72
+ if (authWindow) {
73
+ authWindow.location.href = url
74
+ }
75
+
76
+ return authWindow
77
+ }
78
+
79
+ export function extractOAuthToken(url: string): string | null {
80
+ const hashParams = new URLSearchParams(url.split('#')[1])
81
+ let idToken = hashParams.get('id_token')
82
+
83
+ if (!idToken) {
84
+ const queryParams = new URLSearchParams(url.split('?')[1])
85
+ idToken = queryParams.get('id_token')
86
+ }
87
+
88
+ return idToken
89
+ }
90
+
91
+ export function pollOAuthPopup(
92
+ authWindow: Window,
93
+ originUrl: string,
94
+ onSuccess: (token: string) => void,
95
+ onError: (error: Error) => void,
96
+ ): void {
97
+ const interval = setInterval(() => {
98
+ try {
99
+ if (authWindow.closed) {
100
+ clearInterval(interval)
101
+ onError(new Error('Authentication window was closed'))
102
+ return
103
+ }
104
+
105
+ const url = authWindow.location.href || ''
106
+
107
+ if (url.startsWith(originUrl)) {
108
+ const token = extractOAuthToken(url)
109
+
110
+ if (token) {
111
+ authWindow.close()
112
+ clearInterval(interval)
113
+ onSuccess(token)
114
+ }
115
+ }
116
+ } catch {
117
+ // Ignore cross-origin errors
118
+ }
119
+ }, 500)
120
+ }
121
+
122
+ export function generateOAuthNonce(publicKey: string): string {
123
+ return sha256(publicKey as Hex).replace(/^0x/, '')
124
+ }
@@ -0,0 +1,235 @@
1
+ import type { KernelSmartAccountImplementation } from '@zerodev/sdk'
2
+ import { normalizeTimestamp } from '@zerodev/wallet-core'
3
+ import { Provider } from 'ox'
4
+ import type { Chain, LocalAccount } from 'viem'
5
+ import type { SmartAccount } from 'viem/account-abstraction'
6
+ import type { ZeroDevWalletConnectorParams } from './connector.js'
7
+ import type { createZeroDevWalletStore } from './store.js'
8
+
9
+ const SESSION_WARNING_THRESHOLD_MS = 60 * 1000 // 1 minute before expiry
10
+
11
+ type CreateProviderParams = {
12
+ store: ReturnType<typeof createZeroDevWalletStore>
13
+ config: ZeroDevWalletConnectorParams
14
+ chains: Chain[]
15
+ }
16
+
17
+ export type ZeroDevProvider = ReturnType<typeof Provider.createEmitter> & {
18
+ request(args: { method: string; params?: unknown[] }): Promise<unknown>
19
+ destroy(): void
20
+ }
21
+
22
+ export function createProvider({
23
+ store,
24
+ config,
25
+ }: CreateProviderParams): ZeroDevProvider {
26
+ const emitter = Provider.createEmitter()
27
+ let sessionRefreshTimer: NodeJS.Timeout | null = null
28
+
29
+ // Session auto-refresh logic
30
+ const scheduleSessionRefresh = () => {
31
+ if (config.autoRefreshSession === false) return
32
+
33
+ const state = store.getState()
34
+ if (!state.session || !state.wallet) return
35
+
36
+ const expiryMs = normalizeTimestamp(state.session.expiry)
37
+ const now = Date.now()
38
+ const timeUntilExpiry = expiryMs - now
39
+
40
+ if (timeUntilExpiry <= 0) {
41
+ console.log('Session already expired')
42
+ return
43
+ }
44
+
45
+ // Clear existing timer
46
+ if (sessionRefreshTimer) {
47
+ clearTimeout(sessionRefreshTimer)
48
+ sessionRefreshTimer = null
49
+ }
50
+
51
+ const threshold =
52
+ config.sessionWarningThreshold || SESSION_WARNING_THRESHOLD_MS
53
+ const refreshAt = expiryMs - threshold
54
+ const timeUntilRefresh = refreshAt - now
55
+
56
+ if (timeUntilRefresh <= 0) {
57
+ console.log('Session expiring soon, refreshing immediately...')
58
+ refreshSessionNow()
59
+ } else {
60
+ console.log(`Scheduling session refresh in ${timeUntilRefresh}ms`)
61
+ sessionRefreshTimer = setTimeout(() => {
62
+ refreshSessionNow()
63
+ }, timeUntilRefresh)
64
+ }
65
+ }
66
+
67
+ const refreshSessionNow = async () => {
68
+ const state = store.getState()
69
+ if (!state.wallet || !state.session) return
70
+
71
+ console.log('Auto-refreshing session...')
72
+ store.getState().setIsExpiring(true)
73
+
74
+ try {
75
+ const newSession = await state.wallet.refreshSession(state.session.id)
76
+ console.log('Session refreshed successfully')
77
+ store.getState().setSession(newSession || null)
78
+ store.getState().setIsExpiring(false)
79
+
80
+ if (newSession) {
81
+ scheduleSessionRefresh()
82
+ }
83
+ } catch (err) {
84
+ console.error('Session refresh failed:', err)
85
+ store.getState().setIsExpiring(false)
86
+ store.getState().clear()
87
+ }
88
+ }
89
+
90
+ // Subscribe to session changes
91
+ const unsubscribe = store.subscribe(
92
+ (state) => state.session,
93
+ () => {
94
+ scheduleSessionRefresh()
95
+ },
96
+ )
97
+
98
+ // Schedule initial refresh if session exists
99
+ const initialSession = store.getState().session
100
+ if (initialSession) {
101
+ scheduleSessionRefresh()
102
+ }
103
+
104
+ return {
105
+ ...emitter,
106
+
107
+ destroy() {
108
+ // Cleanup timer and subscription
109
+ if (sessionRefreshTimer) {
110
+ clearTimeout(sessionRefreshTimer)
111
+ sessionRefreshTimer = null
112
+ }
113
+ unsubscribe()
114
+ },
115
+
116
+ async request({ method, params }: { method: string; params?: any[] }) {
117
+ const state = store.getState()
118
+ const activeChainId = state.chainIds[0]
119
+
120
+ switch (method) {
121
+ case 'eth_accounts': {
122
+ const account = state.kernelAccounts.get(activeChainId)
123
+ return account ? [account.address] : []
124
+ }
125
+
126
+ case 'eth_requestAccounts': {
127
+ const account = state.kernelAccounts.get(activeChainId)
128
+ if (!account) throw new Error('Not authenticated')
129
+ return [account.address]
130
+ }
131
+
132
+ case 'eth_chainId': {
133
+ return `0x${activeChainId.toString(16)}`
134
+ }
135
+
136
+ case 'wallet_sendTransaction':
137
+ case 'eth_sendTransaction': {
138
+ if (!params || params.length === 0) {
139
+ throw new Error('Missing transaction parameters')
140
+ }
141
+
142
+ const [tx] = params
143
+ const chainId = tx.chainId ? parseInt(tx.chainId, 16) : activeChainId
144
+
145
+ // Get kernel client for this chain
146
+ const kernelClient = store.getState().kernelClients.get(chainId)
147
+ if (!kernelClient) {
148
+ throw new Error(`No kernel client for chain ${chainId}`)
149
+ }
150
+
151
+ // Send gasless transaction (always UserOp for EIP-7702)
152
+ const hash = await kernelClient.sendTransaction({
153
+ calls: [
154
+ {
155
+ to: tx.to,
156
+ value: tx.value ? BigInt(tx.value) : 0n,
157
+ data: tx.data || '0x',
158
+ },
159
+ ],
160
+ })
161
+
162
+ return hash
163
+ }
164
+
165
+ case 'personal_sign': {
166
+ if (!params || params.length < 2) {
167
+ throw new Error('Missing sign parameters')
168
+ }
169
+
170
+ const [message] = params
171
+ let account:
172
+ | SmartAccount<KernelSmartAccountImplementation>
173
+ | LocalAccount
174
+ | undefined
175
+ | null = state.kernelAccounts.get(activeChainId)
176
+ if (
177
+ account &&
178
+ 'isDeployed' in account &&
179
+ !(await account.isDeployed())
180
+ ) {
181
+ account = state.eoaAccount
182
+ }
183
+
184
+ if (!account) throw new Error('Not authenticated')
185
+
186
+ return await account.signMessage({ message })
187
+ }
188
+
189
+ case 'eth_signTypedData_v4': {
190
+ if (!params || params.length < 2) {
191
+ throw new Error('Missing typed data parameters')
192
+ }
193
+
194
+ const [, typedDataJson] = params
195
+ let account:
196
+ | SmartAccount<KernelSmartAccountImplementation>
197
+ | LocalAccount
198
+ | undefined
199
+ | null = state.kernelAccounts.get(activeChainId)
200
+ if (
201
+ account &&
202
+ 'isDeployed' in account &&
203
+ !(await account.isDeployed())
204
+ ) {
205
+ account = state.eoaAccount
206
+ }
207
+ if (!account) throw new Error('Not authenticated')
208
+
209
+ const typedData = JSON.parse(typedDataJson)
210
+ return await account.signTypedData(typedData)
211
+ }
212
+
213
+ case 'wallet_switchEthereumChain': {
214
+ if (!params || params.length === 0) {
215
+ throw new Error('Missing chain parameter')
216
+ }
217
+
218
+ const [{ chainId }] = params
219
+ const chainId_number = parseInt(chainId, 16)
220
+
221
+ // Update active chain
222
+ store.getState().setActiveChain(chainId_number)
223
+
224
+ // Emit chainChanged event
225
+ emitter.emit('chainChanged', chainId)
226
+
227
+ return null
228
+ }
229
+
230
+ default:
231
+ throw new Error(`Method not supported: ${method}`)
232
+ }
233
+ },
234
+ }
235
+ }
package/src/store.ts ADDED
@@ -0,0 +1,113 @@
1
+ import type {
2
+ KernelAccountClient,
3
+ KernelSmartAccountImplementation,
4
+ } from '@zerodev/sdk'
5
+ import type {
6
+ ZeroDevWalletSDK,
7
+ ZeroDevWalletSession,
8
+ } from '@zerodev/wallet-core'
9
+ import type { LocalAccount } from 'viem'
10
+ import type { SmartAccount } from 'viem/account-abstraction'
11
+ import { create } from 'zustand'
12
+ import { persist, subscribeWithSelector } from 'zustand/middleware'
13
+ import type { OAuthConfig } from './oauth.js'
14
+
15
+ export type ZeroDevWalletState = {
16
+ // Core
17
+ wallet: ZeroDevWalletSDK | null
18
+ eoaAccount: LocalAccount | null
19
+ session: ZeroDevWalletSession | null
20
+
21
+ // Multi-chain support
22
+ chainIds: number[] // [activeChain, ...otherChains]
23
+ kernelAccounts: Map<number, SmartAccount<KernelSmartAccountImplementation>>
24
+ kernelClients: Map<number, KernelAccountClient>
25
+
26
+ // Session expiry
27
+ isExpiring: boolean
28
+
29
+ // OAuth config (optional)
30
+ oauthConfig: OAuthConfig | null
31
+
32
+ // Actions
33
+ setWallet: (wallet: ZeroDevWalletSDK) => void
34
+ setEoaAccount: (account: LocalAccount | null) => void
35
+ setKernelAccount: (
36
+ chainId: number,
37
+ account: SmartAccount<KernelSmartAccountImplementation>,
38
+ ) => void
39
+ setKernelClient: (chainId: number, client: KernelAccountClient) => void
40
+ setSession: (session: ZeroDevWalletSession | null) => void
41
+ setActiveChain: (chainId: number) => void
42
+ setIsExpiring: (isExpiring: boolean) => void
43
+ setOAuthConfig: (config: OAuthConfig | null) => void
44
+ clear: () => void
45
+ }
46
+
47
+ export const createZeroDevWalletStore = () =>
48
+ create<ZeroDevWalletState>()(
49
+ subscribeWithSelector(
50
+ persist(
51
+ (set, get) => ({
52
+ // Initial state
53
+ wallet: null,
54
+ eoaAccount: null,
55
+ session: null,
56
+ chainIds: [],
57
+ kernelAccounts: new Map(),
58
+ kernelClients: new Map(),
59
+ isExpiring: false,
60
+ oauthConfig: null,
61
+
62
+ // Actions
63
+ setWallet: (wallet) => set({ wallet }),
64
+
65
+ setEoaAccount: (account) => set({ eoaAccount: account }),
66
+
67
+ setKernelAccount: (chainId, account) => {
68
+ const accounts = new Map(get().kernelAccounts)
69
+ accounts.set(chainId, account)
70
+ set({ kernelAccounts: accounts })
71
+ },
72
+
73
+ setKernelClient: (chainId, client) => {
74
+ const clients = new Map(get().kernelClients)
75
+ clients.set(chainId, client)
76
+ set({ kernelClients: clients })
77
+ },
78
+
79
+ setSession: (session) => set({ session }),
80
+
81
+ setActiveChain: (chainId) => {
82
+ const { chainIds } = get()
83
+ // Move chainId to front, remove duplicates
84
+ set({
85
+ chainIds: [chainId, ...chainIds.filter((id) => id !== chainId)],
86
+ })
87
+ },
88
+
89
+ setIsExpiring: (isExpiring) => set({ isExpiring }),
90
+
91
+ setOAuthConfig: (config) => set({ oauthConfig: config }),
92
+
93
+ clear: () =>
94
+ set({
95
+ eoaAccount: null,
96
+ session: null,
97
+ kernelAccounts: new Map(),
98
+ kernelClients: new Map(),
99
+ isExpiring: false,
100
+ chainIds: [],
101
+ }),
102
+ }),
103
+ {
104
+ name: 'zerodev-wallet',
105
+ // Only persist session data, not clients or accounts
106
+ partialize: (state) => ({
107
+ session: state.session,
108
+ chainIds: state.chainIds,
109
+ }),
110
+ },
111
+ ),
112
+ ),
113
+ )
@@ -0,0 +1,5 @@
1
+ import { ZERODEV_AA_URL } from '../constants.js'
2
+
3
+ export function getAAUrl(chainId: number, aaUrl?: string) {
4
+ return aaUrl || `${ZERODEV_AA_URL}${chainId}/chain/${chainId}`
5
+ }
@@ -0,0 +1,80 @@
1
+ // Timer utilities for session management
2
+ // Handles delays >24.8 days (setTimeout limit) by chunking
3
+
4
+ export type TimerController = { clear: () => void }
5
+ export type TimerMap = Record<string, TimerController>
6
+
7
+ export const MAX_DELAY_MS = 2_147_483_647 // ~24.8 days
8
+
9
+ function toIntMs(x: number) {
10
+ return Math.max(0, Math.floor(Number.isFinite(x) ? x : 0))
11
+ }
12
+
13
+ /**
14
+ * A drop-in replacement for `setTimeout` that supports arbitrarily long delays.
15
+ * Browsers clamp `setTimeout` delays to ~24.8 days. This helper safely schedules
16
+ * timeouts that can be months or years into the future by chunking.
17
+ */
18
+ export function setCappedTimeout(
19
+ cb: () => void,
20
+ delayMs: number,
21
+ ): TimerController {
22
+ const target = Date.now() + toIntMs(delayMs)
23
+ let handle: ReturnType<typeof setTimeout> | undefined
24
+
25
+ const tick = () => {
26
+ const remaining = target - Date.now()
27
+ if (remaining <= 0) {
28
+ cb()
29
+ return
30
+ }
31
+ handle = setTimeout(tick, Math.min(MAX_DELAY_MS, remaining))
32
+ }
33
+
34
+ tick()
35
+
36
+ return {
37
+ clear() {
38
+ if (handle !== undefined) {
39
+ clearTimeout(handle)
40
+ handle = undefined
41
+ }
42
+ },
43
+ }
44
+ }
45
+
46
+ /** Replace any existing timer for `key` with `controller`. */
47
+ export function putTimer(
48
+ map: TimerMap,
49
+ key: string,
50
+ controller: TimerController,
51
+ ) {
52
+ map[key]?.clear?.()
53
+ map[key] = controller
54
+ }
55
+
56
+ /** Clear a specific key (noop if missing). */
57
+ export function clearKey(map: TimerMap, key: string) {
58
+ map[key]?.clear?.()
59
+ delete map[key]
60
+ }
61
+
62
+ /** Clear all timers in the map. */
63
+ export function clearAll(map: TimerMap) {
64
+ for (const k of Object.keys(map)) {
65
+ map[k]?.clear?.()
66
+ delete map[k]
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Convenience: set a capped timeout directly into the map for `key`.
72
+ */
73
+ export function setCappedTimeoutInMap(
74
+ map: TimerMap,
75
+ key: string,
76
+ cb: () => void,
77
+ delayMs: number,
78
+ ) {
79
+ putTimer(map, key, setCappedTimeout(cb, delayMs))
80
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../templates/typescript/tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "jsx": "react-jsx",
6
+ "skipLibCheck": true
7
+ },
8
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
9
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
10
+ }