@imtbl/wallet 2.12.6 → 2.12.7-alpha.1

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/src/types.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import { Flow } from '@imtbl/metrics';
2
- import {
3
- Auth, TypedEventEmitter, type AuthEventMap,
4
- } from '@imtbl/auth';
2
+ import { TypedEventEmitter, User } from '@imtbl/auth';
5
3
  import { JsonRpcError } from './zkEvm/JsonRpcError';
6
4
 
7
5
  export enum EvmChain {
@@ -22,15 +20,15 @@ export interface WalletSigner {
22
20
 
23
21
  // Re-export auth types for convenience
24
22
  export type {
25
- User, UserProfile, UserZkEvm, DirectLoginMethod, AuthEventMap,
23
+ User, UserProfile, UserZkEvm, DirectLoginMethod,
26
24
  } from '@imtbl/auth';
27
25
  export { isUserZkEvm } from '@imtbl/auth';
28
26
  export type { RollupType } from '@imtbl/auth';
29
- export { AuthEvents } from '@imtbl/auth';
30
27
 
31
- // Wallet-specific event (in addition to AuthEvents)
28
+ // Wallet events
32
29
  export enum WalletEvents {
33
30
  ACCOUNTS_REQUESTED = 'accountsRequested',
31
+ LOGGED_OUT = 'loggedOut',
34
32
  }
35
33
 
36
34
  export type AccountsRequestedEvent = {
@@ -41,11 +39,15 @@ export type AccountsRequestedEvent = {
41
39
  flow?: Flow;
42
40
  };
43
41
 
44
- // PassportEventMap combines auth events and wallet-specific events
45
- export interface PassportEventMap extends AuthEventMap {
42
+ // WalletEventMap for internal wallet events
43
+ export interface WalletEventMap extends Record<string, any> {
46
44
  [WalletEvents.ACCOUNTS_REQUESTED]: [AccountsRequestedEvent];
45
+ [WalletEvents.LOGGED_OUT]: [];
47
46
  }
48
47
 
48
+ // Legacy alias for backwards compatibility with Passport
49
+ export type PassportEventMap = WalletEventMap;
50
+
49
51
  /**
50
52
  * EIP-1193 Provider Interface
51
53
  * Standard Ethereum provider interface for all chain types
@@ -228,16 +230,46 @@ export interface PopupOverlayOptions {
228
230
  disableBlockedPopupOverlay?: boolean;
229
231
  }
230
232
 
233
+ /**
234
+ * Function type for getting the current user.
235
+ * Used as an alternative to passing an Auth instance.
236
+ * This function should return fresh tokens from the session manager.
237
+ *
238
+ * @param forceRefresh - When true, the auth layer should trigger a server-side
239
+ * token refresh to get updated claims (e.g., after zkEVM registration).
240
+ * This ensures the returned user has the latest data from the identity provider.
241
+ */
242
+ export type GetUserFunction = (forceRefresh?: boolean) => Promise<User | null>;
243
+
231
244
  /**
232
245
  * Options for connecting a wallet via connectWallet()
233
246
  * High-level configuration that gets transformed into internal WalletConfiguration
234
247
  */
235
248
  export interface ConnectWalletOptions {
236
249
  /**
237
- * Auth instance. Optional if omitted, a default Auth instance
238
- * configured with Immutable hosted defaults will be created.
250
+ * Function that returns the current user with fresh tokens.
251
+ * This is the primary way to provide authentication to the wallet.
252
+ *
253
+ * For NextAuth integration:
254
+ * @example
255
+ * ```typescript
256
+ * import { connectWallet } from '@imtbl/wallet';
257
+ * import { useImmutableSession } from '@imtbl/auth-next-client';
258
+ *
259
+ * const { getUser } = useImmutableSession();
260
+ * const provider = await connectWallet({ getUser });
261
+ * ```
262
+ *
263
+ * If not provided, a default implementation using @imtbl/auth will be created.
264
+ */
265
+ getUser?: GetUserFunction;
266
+
267
+ /**
268
+ * Client ID for Immutable authentication.
269
+ * Required when getUser is not provided (for default auth).
270
+ * Also used for session activity tracking.
239
271
  */
240
- auth?: Auth;
272
+ clientId?: string;
241
273
 
242
274
  /**
243
275
  * Chain configurations (supports multi-chain)
@@ -272,5 +304,5 @@ export interface ConnectWalletOptions {
272
304
  /**
273
305
  * @internal - Only used by Passport for internal event communication
274
306
  */
275
- passportEventEmitter?: TypedEventEmitter<PassportEventMap>;
307
+ passportEventEmitter?: TypedEventEmitter<WalletEventMap>;
276
308
  }
@@ -1,13 +1,14 @@
1
1
  import type { PublicClient, Hex } from 'viem';
2
- import { Auth } from '@imtbl/auth';
2
+ import { isUserZkEvm, type UserZkEvm } from '@imtbl/auth';
3
3
  import { WalletConfiguration } from '../config';
4
4
  import { FeeOption, RelayerTransaction, TypedDataPayload } from './types';
5
+ import type { GetUserFunction } from '../types';
5
6
  import { getEip155ChainId } from './walletHelpers';
6
7
 
7
8
  export type RelayerClientInput = {
8
9
  config: WalletConfiguration,
9
10
  rpcProvider: PublicClient,
10
- auth: Auth
11
+ getUser: GetUserFunction,
11
12
  };
12
13
 
13
14
  // JsonRpc base Types
@@ -94,12 +95,14 @@ export class RelayerClient {
94
95
 
95
96
  private readonly rpcProvider: PublicClient;
96
97
 
97
- private readonly auth: Auth;
98
+ private readonly getUser: GetUserFunction;
98
99
 
99
- constructor({ config, rpcProvider, auth }: RelayerClientInput) {
100
+ constructor({
101
+ config, rpcProvider, getUser,
102
+ }: RelayerClientInput) {
100
103
  this.config = config;
101
104
  this.rpcProvider = rpcProvider;
102
- this.auth = auth;
105
+ this.getUser = getUser;
103
106
  }
104
107
 
105
108
  private static getResponsePreview(text: string): string {
@@ -108,6 +111,19 @@ export class RelayerClient {
108
111
  : text;
109
112
  }
110
113
 
114
+ /**
115
+ * Get zkEvm user using getUser function.
116
+ */
117
+ private async getUserZkEvm(): Promise<UserZkEvm> {
118
+ const user = await this.getUser();
119
+
120
+ if (!user || !isUserZkEvm(user)) {
121
+ throw new Error('User not authenticated or missing zkEvm data');
122
+ }
123
+
124
+ return user;
125
+ }
126
+
111
127
  private async postToRelayer<T>(request: RelayerTransactionRequest): Promise<T> {
112
128
  const body: RelayerTransactionRequest & JsonRpc = {
113
129
  id: 1,
@@ -115,7 +131,7 @@ export class RelayerClient {
115
131
  ...request,
116
132
  };
117
133
 
118
- const user = await this.auth.getUserZkEvm();
134
+ const user = await this.getUserZkEvm();
119
135
 
120
136
  const response = await fetch(`${this.config.relayerUrl}/v1/transactions`, {
121
137
  method: 'POST',
@@ -2,13 +2,16 @@ import { MultiRollupApiClients } from '@imtbl/generated-clients';
2
2
  import { Flow } from '@imtbl/metrics';
3
3
  import type { PublicClient } from 'viem';
4
4
  import { getEip155ChainId } from '../walletHelpers';
5
- import { Auth } from '@imtbl/auth';
6
5
  import { JsonRpcError, RpcErrorCode } from '../JsonRpcError';
7
6
  import { signRaw } from '../../utils/crypto';
8
- import type { WalletSigner } from '../../types';
7
+ import type { WalletSigner, GetUserFunction } from '../../types';
9
8
 
10
9
  export type RegisterZkEvmUserInput = {
11
- auth: Auth;
10
+ /**
11
+ * Function to get fresh user tokens. Used for triggering background refresh after registration.
12
+ * If not provided, no background refresh is performed.
13
+ */
14
+ getUser?: GetUserFunction;
12
15
  ethSigner: WalletSigner,
13
16
  multiRollupApiClients: MultiRollupApiClients,
14
17
  accessToken: string;
@@ -19,7 +22,7 @@ export type RegisterZkEvmUserInput = {
19
22
  const MESSAGE_TO_SIGN = 'Only sign this message from Immutable Passport';
20
23
 
21
24
  export async function registerZkEvmUser({
22
- auth,
25
+ getUser,
23
26
  ethSigner,
24
27
  multiRollupApiClients,
25
28
  accessToken,
@@ -67,7 +70,15 @@ export async function registerZkEvmUser({
67
70
  });
68
71
  flow.addEvent('endCreateCounterfactualAddress');
69
72
 
70
- auth.forceUserRefreshInBackground();
73
+ // Trigger a background refresh to get the updated user with zkEvm info.
74
+ // This is a best-effort operation - the caller will need to get updated
75
+ // user data from their session manager (e.g., NextAuth session callback
76
+ // will refresh tokens and return updated user).
77
+ if (getUser) {
78
+ getUser().catch(() => {
79
+ // Ignore errors - this is a best-effort refresh
80
+ });
81
+ }
71
82
 
72
83
  return registrationResponse.data.counterfactual_address;
73
84
  } catch (error) {
@@ -14,10 +14,10 @@ import {
14
14
  ProviderEventMap,
15
15
  RequestArguments,
16
16
  } from './types';
17
- import { Auth, TypedEventEmitter } from '@imtbl/auth';
17
+ import { TypedEventEmitter } from '@imtbl/auth';
18
18
  import { WalletConfiguration } from '../config';
19
19
  import {
20
- PassportEventMap, AuthEvents, WalletEvents, User, UserZkEvm, WalletSigner,
20
+ WalletEventMap, WalletEvents, User, UserZkEvm, WalletSigner, GetUserFunction,
21
21
  } from '../types';
22
22
  import { RelayerClient } from './relayerClient';
23
23
  import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from './JsonRpcError';
@@ -32,20 +32,28 @@ import { sendDeployTransactionAndPersonalSign } from './sendDeployTransactionAnd
32
32
  import { signEjectionTransaction } from './signEjectionTransaction';
33
33
 
34
34
  export type ZkEvmProviderInput = {
35
- auth: Auth;
35
+ /**
36
+ * Function that returns the current user with fresh tokens.
37
+ * This is the primary way to provide authentication to the wallet.
38
+ */
39
+ getUser: GetUserFunction;
40
+ /**
41
+ * Client ID for session activity tracking.
42
+ */
43
+ clientId: string;
36
44
  config: WalletConfiguration;
37
45
  multiRollupApiClients: MultiRollupApiClients;
38
- passportEventEmitter: TypedEventEmitter<PassportEventMap>;
46
+ walletEventEmitter: TypedEventEmitter<WalletEventMap>;
39
47
  guardianClient: GuardianClient;
40
48
  ethSigner: WalletSigner;
41
49
  user: User | null;
42
50
  sessionActivityApiUrl: string | null;
43
51
  };
44
52
 
45
- const isZkEvmUser = (user: User): user is UserZkEvm => 'zkEvm' in user;
53
+ const isZkEvmUser = (user: User): user is UserZkEvm => !!user.zkEvm;
46
54
 
47
55
  export class ZkEvmProvider implements Provider {
48
- readonly #auth: Auth;
56
+ readonly #getUser: GetUserFunction;
49
57
 
50
58
  readonly #config: WalletConfiguration;
51
59
 
@@ -57,9 +65,9 @@ export class ZkEvmProvider implements Provider {
57
65
  readonly #providerEventEmitter: TypedEventEmitter<ProviderEventMap>;
58
66
 
59
67
  /**
60
- * intended to emit internal Passport events
68
+ * intended to emit internal wallet events
61
69
  */
62
- readonly #passportEventEmitter: TypedEventEmitter<ProviderEventMap>;
70
+ readonly #walletEventEmitter: TypedEventEmitter<WalletEventMap>;
63
71
 
64
72
  readonly #guardianClient: GuardianClient;
65
73
 
@@ -71,22 +79,26 @@ export class ZkEvmProvider implements Provider {
71
79
 
72
80
  readonly #ethSigner: WalletSigner;
73
81
 
82
+ readonly #clientId: string;
83
+
74
84
  public readonly isPassport: boolean = true;
75
85
 
76
86
  constructor({
77
- auth,
87
+ getUser,
88
+ clientId,
78
89
  config,
79
90
  multiRollupApiClients,
80
- passportEventEmitter,
91
+ walletEventEmitter,
81
92
  guardianClient,
82
93
  ethSigner,
83
94
  user,
84
95
  sessionActivityApiUrl,
85
96
  }: ZkEvmProviderInput) {
86
- this.#auth = auth;
97
+ this.#getUser = getUser;
98
+ this.#clientId = clientId;
87
99
  this.#config = config;
88
100
  this.#guardianClient = guardianClient;
89
- this.#passportEventEmitter = passportEventEmitter;
101
+ this.#walletEventEmitter = walletEventEmitter;
90
102
  this.#sessionActivityApiUrl = sessionActivityApiUrl;
91
103
  this.#ethSigner = ethSigner;
92
104
 
@@ -99,7 +111,7 @@ export class ZkEvmProvider implements Provider {
99
111
  this.#relayerClient = new RelayerClient({
100
112
  config: this.#config,
101
113
  rpcProvider: this.#rpcProvider,
102
- auth: this.#auth,
114
+ getUser: this.#getUser,
103
115
  });
104
116
 
105
117
  this.#multiRollupApiClients = multiRollupApiClients;
@@ -109,13 +121,9 @@ export class ZkEvmProvider implements Provider {
109
121
  this.#callSessionActivity(user.zkEvm.ethAddress);
110
122
  }
111
123
 
112
- passportEventEmitter.on(AuthEvents.LOGGED_IN, (loggedInUser: User) => {
113
- if (isZkEvmUser(loggedInUser)) {
114
- this.#callSessionActivity(loggedInUser.zkEvm.ethAddress);
115
- }
116
- });
117
- passportEventEmitter.on(AuthEvents.LOGGED_OUT, this.#handleLogout);
118
- passportEventEmitter.on(
124
+ // Listen for logout events
125
+ walletEventEmitter.on(WalletEvents.LOGGED_OUT, this.#handleLogout);
126
+ walletEventEmitter.on(
119
127
  WalletEvents.ACCOUNTS_REQUESTED,
120
128
  trackSessionActivity,
121
129
  );
@@ -125,7 +133,14 @@ export class ZkEvmProvider implements Provider {
125
133
  this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, []);
126
134
  };
127
135
 
128
- async #callSessionActivity(zkEvmAddress: string, clientId?: string) {
136
+ /**
137
+ * Get the current user using getUser function.
138
+ */
139
+ async #getCurrentUser(): Promise<User | null> {
140
+ return this.#getUser();
141
+ }
142
+
143
+ async #callSessionActivity(zkEvmAddress: string) {
129
144
  // Only emit session activity event for supported chains (mainnet, testnet, devnet)
130
145
  if (!this.#sessionActivityApiUrl) {
131
146
  return;
@@ -147,18 +162,19 @@ export class ZkEvmProvider implements Provider {
147
162
  nonceSpace,
148
163
  isBackgroundTransaction: true,
149
164
  });
150
- this.#passportEventEmitter.emit(WalletEvents.ACCOUNTS_REQUESTED, {
165
+
166
+ this.#walletEventEmitter.emit(WalletEvents.ACCOUNTS_REQUESTED, {
151
167
  sessionActivityApiUrl: this.#sessionActivityApiUrl,
152
168
  sendTransaction: sendTransactionClosure,
153
169
  walletAddress: zkEvmAddress,
154
- passportClient: clientId || await this.#auth.getClientId(),
170
+ passportClient: this.#clientId,
155
171
  });
156
172
  }
157
173
 
158
174
  // Used to get the registered zkEvm address from the User session
159
175
  async #getZkEvmAddress() {
160
176
  try {
161
- const user = await this.#auth.getUser();
177
+ const user = await this.#getCurrentUser();
162
178
  if (user && isZkEvmUser(user)) {
163
179
  return user.zkEvm.ethAddress;
164
180
  }
@@ -179,8 +195,15 @@ export class ZkEvmProvider implements Provider {
179
195
  const flow = trackFlow('passport', 'ethRequestAccounts');
180
196
 
181
197
  try {
182
- const user = await this.#auth.getUserOrLogin();
183
- flow.addEvent('endGetUserOrLogin');
198
+ // Get user via getUser function
199
+ const user = await this.#getUser();
200
+ if (!user) {
201
+ throw new JsonRpcError(
202
+ ProviderErrorCode.UNAUTHORIZED,
203
+ 'User not authenticated. Please log in first.',
204
+ );
205
+ }
206
+ flow.addEvent('endGetUser');
184
207
 
185
208
  let userZkEvmEthAddress: string | undefined;
186
209
 
@@ -189,13 +212,18 @@ export class ZkEvmProvider implements Provider {
189
212
 
190
213
  userZkEvmEthAddress = await registerZkEvmUser({
191
214
  ethSigner: this.#ethSigner,
192
- auth: this.#auth,
215
+ getUser: this.#getUser,
193
216
  multiRollupApiClients: this.#multiRollupApiClients,
194
217
  accessToken: user.accessToken,
195
218
  rpcProvider: this.#rpcProvider,
196
219
  flow,
197
220
  });
198
221
  flow.addEvent('endUserRegistration');
222
+
223
+ // Force refresh to update session with zkEvm claims from IDP
224
+ // This ensures subsequent getUser() calls return the updated user
225
+ await this.#getUser(true);
226
+ flow.addEvent('endForceRefresh');
199
227
  } else {
200
228
  userZkEvmEthAddress = user.zkEvm.ethAddress;
201
229
  }
@@ -420,10 +448,9 @@ export class ZkEvmProvider implements Provider {
420
448
  }
421
449
  }
422
450
  case 'im_addSessionActivity': {
423
- const [clientId] = request.params || [];
424
451
  const zkEvmAddress = await this.#getZkEvmAddress();
425
452
  if (zkEvmAddress) {
426
- this.#callSessionActivity(zkEvmAddress, clientId);
453
+ this.#callSessionActivity(zkEvmAddress);
427
454
  }
428
455
  return null;
429
456
  }