@etherplay/connect 0.0.34 → 0.0.36

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/index.ts CHANGED
@@ -38,7 +38,8 @@ export type BasicChainInfo = {
38
38
 
39
39
  chainType?: string;
40
40
  };
41
- export type ChainInfo = BasicChainInfo & {
41
+
42
+ type ChainInfoWithRPCUrl = BasicChainInfo & {
42
43
  readonly rpcUrls: {
43
44
  readonly default: {
44
45
  http: readonly string[];
@@ -46,6 +47,12 @@ export type ChainInfo = BasicChainInfo & {
46
47
  };
47
48
  };
48
49
 
50
+ type ChainInfoWithProvider<WalletProviderType> = BasicChainInfo & {
51
+ provider: WalletProviderType;
52
+ };
53
+
54
+ export type ChainInfo<WalletProviderType> = ChainInfoWithRPCUrl | ChainInfoWithProvider<WalletProviderType>;
55
+
49
56
  export type PopupSettings = {
50
57
  walletHost: string;
51
58
  mechanism: AlchemyMechanism;
@@ -61,6 +68,8 @@ export type Mechanism = AlchemyMechanism | WalletMechanism<string | undefined, `
61
68
 
62
69
  export type FullfilledMechanism = AlchemyMechanism | WalletMechanism<string, `0x${string}`>;
63
70
 
71
+ export type TargetStep = 'WalletConnected' | 'SignedIn';
72
+
64
73
  export type WalletState<WalletProviderType> = {
65
74
  provider: WalletProvider<WalletProviderType>;
66
75
  accounts: `0x${string}`[];
@@ -70,12 +79,18 @@ export type WalletState<WalletProviderType> = {
70
79
  switchingChain: 'addingChain' | 'switchingChain' | false;
71
80
  } & ({status: 'connected'} | {status: 'locked'; unlocking: boolean} | {status: 'disconnected'; connecting: boolean});
72
81
 
73
- type WalletConnected<WalletProviderType> = {
82
+ type WaitingForSignature<WalletProviderType> = {
74
83
  step: 'WaitingForSignature';
75
84
  mechanism: WalletMechanism<string, `0x${string}`>;
76
85
  wallet: WalletState<WalletProviderType>;
77
86
  };
78
87
 
88
+ type WalletConnected<WalletProviderType> = {
89
+ step: 'WalletConnected';
90
+ mechanism: WalletMechanism<string, `0x${string}`>;
91
+ wallet: WalletState<WalletProviderType>;
92
+ };
93
+
79
94
  type SignedIn<WalletProviderType> =
80
95
  | {
81
96
  step: 'SignedIn';
@@ -137,13 +152,9 @@ export type Connection<WalletProviderType> = {
137
152
  }
138
153
  // Once the wallet is connected, the system will need a signature
139
154
  // this state represent the fact and require another user interaction to request the signature
140
- | {
141
- step: 'WalletConnected';
142
- mechanism: WalletMechanism<string, `0x${string}`>;
143
- wallet: WalletState<WalletProviderType>;
144
- }
145
- // This state is triggered once the signature is requested, the user will have to confirm with its wallet
146
155
  | WalletConnected<WalletProviderType>
156
+ // This state is triggered once the signature is requested, the user will have to confirm with its wallet
157
+ | WaitingForSignature<WalletProviderType>
147
158
  // Finally the user is fully signed in
148
159
  // wallet?.accountChanged if set, represent the fact that the user has changed its web3-wallet accounnt.
149
160
  // wallet?.invalidChainId if set, represent the fact that the wallet is connected to a different chain.
@@ -152,6 +163,56 @@ export type Connection<WalletProviderType> = {
152
163
  | SignedIn<WalletProviderType>
153
164
  );
154
165
 
166
+ // Type for SignedIn state that was reached via wallet authentication (not popup-based auth)
167
+ // This variant always has wallet and WalletMechanism
168
+ export type SignedInWithWallet<WalletProviderType> = Extract<
169
+ Connection<WalletProviderType>,
170
+ {step: 'SignedIn'; wallet: WalletState<WalletProviderType>}
171
+ >;
172
+
173
+ // Full WalletConnected type from Connection
174
+ export type WalletConnectedState<WalletProviderType> = Extract<
175
+ Connection<WalletProviderType>,
176
+ {step: 'WalletConnected'}
177
+ >;
178
+
179
+ // Type representing wallet-connected states (both WalletConnected and SignedIn-via-wallet)
180
+ // This is what you get when targetStep is 'WalletConnected' and target is reached
181
+ // Both variants have WalletMechanism and wallet
182
+ export type ConnectedWithWallet<WalletProviderType> =
183
+ | WalletConnectedState<WalletProviderType>
184
+ | SignedInWithWallet<WalletProviderType>;
185
+
186
+ // Full SignedIn type from Connection (includes both popup-based and wallet-based variants)
187
+ export type SignedInState<WalletProviderType> = Extract<Connection<WalletProviderType>, {step: 'SignedIn'}>;
188
+
189
+ // Type guard - narrows Connection based on targetStep and walletOnly
190
+ // For 'WalletConnected' target: narrows to ConnectedWithWallet (WalletConnected | SignedIn-with-wallet)
191
+ // For 'SignedIn' target with walletOnly: narrows to SignedInWithWallet
192
+ // For 'SignedIn' target (default): narrows to SignedIn
193
+ export function isTargetStepReached<WalletProviderType, Target extends TargetStep, WalletOnly extends boolean = false>(
194
+ connection: Connection<WalletProviderType>,
195
+ targetStep: Target,
196
+ walletOnly?: WalletOnly,
197
+ ): connection is Target extends 'WalletConnected'
198
+ ? ConnectedWithWallet<WalletProviderType>
199
+ : WalletOnly extends true
200
+ ? SignedInWithWallet<WalletProviderType>
201
+ : SignedInState<WalletProviderType> {
202
+ if (targetStep === 'WalletConnected') {
203
+ // For WalletConnected target, accept WalletConnected OR SignedIn-with-wallet
204
+ return connection.step === 'WalletConnected' || (connection.step === 'SignedIn' && connection.wallet !== undefined);
205
+ }
206
+ // For SignedIn target (regardless of walletOnly), only accept SignedIn
207
+ // walletOnly affects the return type narrowing, not the step check
208
+ if (walletOnly) {
209
+ // For SignedIn + walletOnly, only accept SignedIn-with-wallet
210
+ return connection.step === 'SignedIn' && connection.wallet !== undefined;
211
+ }
212
+ // For SignedIn target, accept any SignedIn variant
213
+ return connection.step === 'SignedIn';
214
+ }
215
+
155
216
  function viemChainInfoToSwitchChainInfo(chainInfo: BasicChainInfo): {
156
217
  chainId: `0x${string}`;
157
218
  readonly rpcUrls?: readonly string[];
@@ -176,15 +237,25 @@ function viemChainInfoToSwitchChainInfo(chainInfo: BasicChainInfo): {
176
237
  const storageKeyAccount = '__origin_account';
177
238
  const storageKeyLastWallet = '__last_wallet';
178
239
 
179
- export type ConnectionStore<WalletProviderType> = {
240
+ export type ConnectionOptions = {
241
+ requireUserConfirmationBeforeSignatureRequest?: boolean;
242
+ doNotStoreLocally?: boolean;
243
+ requestSignatureRightAway?: boolean;
244
+ };
245
+
246
+ export type ConnectionStore<
247
+ WalletProviderType,
248
+ Target extends TargetStep = 'SignedIn',
249
+ WalletOnly extends boolean = false,
250
+ > = {
180
251
  subscribe: (run: (value: Connection<WalletProviderType>) => void) => () => void;
181
252
  connect: (
182
- mechanism?: Mechanism,
183
- options?: {
184
- requireUserConfirmationBeforeSignatureRequest?: boolean;
185
- doNotStoreLocally?: boolean;
186
- requestSignatureRightAway?: boolean;
187
- },
253
+ mechanism?: Target extends 'WalletConnected'
254
+ ? WalletMechanism<string | undefined, `0x${string}` | undefined>
255
+ : WalletOnly extends true
256
+ ? WalletMechanism<string | undefined, `0x${string}` | undefined>
257
+ : Mechanism,
258
+ options?: ConnectionOptions,
188
259
  ) => Promise<void>;
189
260
  cancel: () => void;
190
261
  back: (step: 'MechanismToChoose' | 'Idle' | 'WalletToChoose') => void;
@@ -197,101 +268,187 @@ export type ConnectionStore<WalletProviderType> = {
197
268
  getSignatureForPublicKeyPublication: () => Promise<`0x${string}`>;
198
269
  switchWalletChain: (chainInfo?: BasicChainInfo) => Promise<void>;
199
270
  unlock: () => Promise<void>;
200
- ensureConnected: {
201
- (
202
- step: 'WalletConnected',
203
- mechanism?: WalletMechanism<string | undefined, `0x${string}` | undefined>,
204
- options?: {
205
- requireUserConfirmationBeforeSignatureRequest?: boolean;
206
- doNotStoreLocally?: boolean;
207
- requestSignatureRightAway?: boolean;
208
- },
209
- ): Promise<WalletConnected<WalletProviderType>>;
210
- (
211
- step: 'SignedIn',
212
- mechanism?: Mechanism,
213
- options?: {
214
- requireUserConfirmationBeforeSignatureRequest?: boolean;
215
- doNotStoreLocally?: boolean;
216
- requestSignatureRightAway?: boolean;
217
- },
218
- ): Promise<SignedIn<WalletProviderType>>;
219
- (
220
- mechanism?: Mechanism,
221
- options?: {
222
- requireUserConfirmationBeforeSignatureRequest?: boolean;
223
- doNotStoreLocally?: boolean;
224
- requestSignatureRightAway?: boolean;
225
- },
226
- ): Promise<SignedIn<WalletProviderType>>;
227
- };
271
+
272
+ // ensureConnected signature depends on target and walletOnly
273
+ ensureConnected: Target extends 'WalletConnected'
274
+ ? {
275
+ (options?: ConnectionOptions): Promise<WalletConnected<WalletProviderType>>;
276
+ (
277
+ step: 'WalletConnected',
278
+ mechanism?: WalletMechanism<string | undefined, `0x${string}` | undefined>,
279
+ options?: ConnectionOptions,
280
+ ): Promise<WalletConnected<WalletProviderType>>;
281
+ }
282
+ : WalletOnly extends true
283
+ ? {
284
+ // walletOnly: true for SignedIn - returns SignedInWithWallet (not full SignedIn union)
285
+ (options?: ConnectionOptions): Promise<SignedInWithWallet<WalletProviderType>>;
286
+ (
287
+ step: 'WalletConnected',
288
+ mechanism?: WalletMechanism<string | undefined, `0x${string}` | undefined>,
289
+ options?: ConnectionOptions,
290
+ ): Promise<WalletConnected<WalletProviderType>>;
291
+ (
292
+ step: 'SignedIn',
293
+ mechanism?: WalletMechanism<string | undefined, `0x${string}` | undefined>,
294
+ options?: ConnectionOptions,
295
+ ): Promise<SignedInWithWallet<WalletProviderType>>;
296
+ }
297
+ : {
298
+ (options?: ConnectionOptions): Promise<SignedIn<WalletProviderType>>;
299
+ (
300
+ step: 'WalletConnected',
301
+ mechanism?: WalletMechanism<string | undefined, `0x${string}` | undefined>,
302
+ options?: ConnectionOptions,
303
+ ): Promise<WalletConnected<WalletProviderType>>;
304
+ (step: 'SignedIn', mechanism?: Mechanism, options?: ConnectionOptions): Promise<SignedIn<WalletProviderType>>;
305
+ };
306
+
307
+ // Method to check if target step is reached with proper type narrowing
308
+ isTargetStepReached: (
309
+ connection: Connection<WalletProviderType>,
310
+ ) => connection is Target extends 'WalletConnected'
311
+ ? ConnectedWithWallet<WalletProviderType>
312
+ : WalletOnly extends true
313
+ ? SignedInWithWallet<WalletProviderType>
314
+ : SignedInState<WalletProviderType>;
315
+
316
+ // New properties
317
+ targetStep: Target;
318
+ walletOnly: WalletOnly;
319
+
320
+ // Existing properties
228
321
  provider: WalletProviderType;
229
322
  chainId: string;
230
- chainInfo: ChainInfo;
323
+ chainInfo: ChainInfo<WalletProviderType>;
231
324
  };
232
325
 
233
326
  // Function overloads for proper typing
327
+
328
+ // WalletConnected target with custom wallet connector - walletHost optional
234
329
  export function createConnection<WalletProviderType>(settings: {
235
- signingOrigin?: string;
236
- walletHost: string;
330
+ targetStep: 'WalletConnected';
331
+ walletHost?: string;
332
+ chainInfo: ChainInfo<WalletProviderType>;
333
+ walletConnector: WalletConnector<WalletProviderType>;
334
+ autoConnect?: boolean;
335
+ alwaysUseCurrentAccount?: boolean;
336
+ prioritizeWalletProvider?: boolean;
337
+ requestsPerSecond?: number;
338
+ }): ConnectionStore<WalletProviderType, 'WalletConnected'>;
339
+
340
+ // WalletConnected target with default Ethereum connector - walletHost optional
341
+ export function createConnection(settings: {
342
+ targetStep: 'WalletConnected';
343
+ walletHost?: string;
344
+ chainInfo: ChainInfo<UnderlyingEthereumProvider>;
345
+ walletConnector?: undefined;
237
346
  autoConnect?: boolean;
238
- autoConnectWallet?: boolean;
347
+ alwaysUseCurrentAccount?: boolean;
348
+ prioritizeWalletProvider?: boolean;
349
+ requestsPerSecond?: number;
350
+ }): ConnectionStore<UnderlyingEthereumProvider, 'WalletConnected', true>;
351
+
352
+ // SignedIn target with walletOnly: true (custom wallet connector) - walletHost optional
353
+ export function createConnection<WalletProviderType>(settings: {
354
+ targetStep?: 'SignedIn';
355
+ walletOnly: true;
356
+ walletHost?: string;
357
+ chainInfo: ChainInfo<WalletProviderType>;
239
358
  walletConnector: WalletConnector<WalletProviderType>;
359
+ signingOrigin?: string;
360
+ autoConnect?: boolean;
240
361
  requestSignatureAutomaticallyIfPossible?: boolean;
241
362
  alwaysUseCurrentAccount?: boolean;
242
- chainInfo: ChainInfo;
243
363
  prioritizeWalletProvider?: boolean;
244
364
  requestsPerSecond?: number;
245
- }): ConnectionStore<WalletProviderType>;
365
+ }): ConnectionStore<WalletProviderType, 'SignedIn', true>;
246
366
 
367
+ // SignedIn target with walletOnly: true (default Ethereum connector) - walletHost optional
247
368
  export function createConnection(settings: {
369
+ targetStep?: 'SignedIn';
370
+ walletOnly: true;
371
+ walletHost?: string;
372
+ chainInfo: ChainInfo<UnderlyingEthereumProvider>;
373
+ walletConnector?: undefined;
248
374
  signingOrigin?: string;
375
+ autoConnect?: boolean;
376
+ requestSignatureAutomaticallyIfPossible?: boolean;
377
+ alwaysUseCurrentAccount?: boolean;
378
+ prioritizeWalletProvider?: boolean;
379
+ requestsPerSecond?: number;
380
+ }): ConnectionStore<UnderlyingEthereumProvider, 'SignedIn', true>;
381
+
382
+ // SignedIn target (explicit) with custom wallet connector - walletHost required
383
+ export function createConnection<WalletProviderType>(settings: {
384
+ targetStep?: 'SignedIn';
385
+ walletOnly?: false;
249
386
  walletHost: string;
387
+ chainInfo: ChainInfo<WalletProviderType>;
388
+ walletConnector: WalletConnector<WalletProviderType>;
389
+ signingOrigin?: string;
250
390
  autoConnect?: boolean;
251
- autoConnectWallet?: boolean;
391
+ requestSignatureAutomaticallyIfPossible?: boolean;
392
+ alwaysUseCurrentAccount?: boolean;
393
+ prioritizeWalletProvider?: boolean;
394
+ requestsPerSecond?: number;
395
+ }): ConnectionStore<WalletProviderType, 'SignedIn', false>;
396
+
397
+ // SignedIn target (default) with default Ethereum connector - walletHost required
398
+ export function createConnection(settings: {
399
+ targetStep?: 'SignedIn';
400
+ walletOnly?: false;
401
+ walletHost: string;
402
+ chainInfo: ChainInfo<UnderlyingEthereumProvider>;
252
403
  walletConnector?: undefined;
404
+ signingOrigin?: string;
405
+ autoConnect?: boolean;
253
406
  requestSignatureAutomaticallyIfPossible?: boolean;
254
407
  alwaysUseCurrentAccount?: boolean;
255
- chainInfo: ChainInfo;
256
408
  prioritizeWalletProvider?: boolean;
257
409
  requestsPerSecond?: number;
258
- }): ConnectionStore<UnderlyingEthereumProvider>;
410
+ }): ConnectionStore<UnderlyingEthereumProvider, 'SignedIn', false>;
259
411
 
412
+ // Implementation signature
260
413
  export function createConnection<WalletProviderType = UnderlyingEthereumProvider>(settings: {
414
+ targetStep?: TargetStep;
415
+ walletOnly?: boolean;
261
416
  signingOrigin?: string;
262
- walletHost: string;
417
+ walletHost?: string;
263
418
  autoConnect?: boolean;
264
- autoConnectWallet?: boolean;
265
419
  walletConnector?: WalletConnector<WalletProviderType>;
266
420
  requestSignatureAutomaticallyIfPossible?: boolean;
267
421
  alwaysUseCurrentAccount?: boolean;
268
- chainInfo: ChainInfo;
422
+ chainInfo: ChainInfo<WalletProviderType>;
269
423
  prioritizeWalletProvider?: boolean;
270
424
  requestsPerSecond?: number;
271
- }) {
425
+ }): ConnectionStore<WalletProviderType, TargetStep, boolean> {
272
426
  function originToSignWith() {
273
427
  return settings.signingOrigin || origin;
274
428
  }
275
429
 
276
- const endpoint = settings.chainInfo.rpcUrls.default.http[0];
277
430
  const walletConnector =
278
431
  settings.walletConnector || (new EthereumWalletConnector() as unknown as WalletConnector<WalletProviderType>);
279
432
  const alwaysOnChainId = '' + settings.chainInfo.id;
280
433
  const alwaysOnProviderWrapper = walletConnector.createAlwaysOnProvider({
281
- endpoint,
434
+ endpoint:
435
+ 'provider' in settings.chainInfo ? settings.chainInfo.provider : settings.chainInfo.rpcUrls.default.http[0],
282
436
  chainId: '' + settings.chainInfo.id,
283
437
  prioritizeWalletProvider: settings.prioritizeWalletProvider,
284
438
  requestsPerSecond: settings.requestsPerSecond,
285
439
  });
440
+ // Determine target step (defaults to 'SignedIn')
441
+ const targetStep: TargetStep = settings.targetStep || 'SignedIn';
442
+
286
443
  let autoConnect = true;
287
444
  if (typeof settings.autoConnect !== 'undefined') {
288
445
  autoConnect = settings.autoConnect;
289
446
  }
290
- let autoConnectWallet = true;
291
- if (typeof settings.autoConnectWallet !== 'undefined') {
292
- autoConnectWallet = settings.autoConnectWallet;
293
- }
294
- const requestSignatureAutomaticallyIfPossible = settings.requestSignatureAutomaticallyIfPossible || false;
447
+
448
+ // For SignedIn target, we can auto-request signature if configured
449
+ // For WalletConnected target, this is always false (we never auto-request signature)
450
+ const requestSignatureAutomaticallyIfPossible =
451
+ targetStep === 'SignedIn' ? settings.requestSignatureAutomaticallyIfPossible || false : false;
295
452
 
296
453
  let $connection: Connection<WalletProviderType> = {step: 'Idle', loading: true, wallet: undefined, wallets: []};
297
454
  const _store = writable<Connection<WalletProviderType>>($connection);
@@ -344,13 +501,18 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
344
501
  });
345
502
  }
346
503
 
504
+ // Auto-connect logic based on targetStep
505
+ // When targetStep: 'WalletConnected' - only check lastWallet
506
+ // When targetStep: 'SignedIn' - check originAccount first, then fallback to lastWallet
507
+ let autoConnectHandled = false;
347
508
  if (autoConnect) {
348
509
  if (typeof window !== 'undefined') {
349
- // set({step: 'Idle', loading: true, wallets: $connection.wallets});
350
510
  try {
351
- const existingAccount = getOriginAccount();
352
- if (existingAccount) {
353
- if (existingAccount.signer) {
511
+ // For SignedIn target, check for existing account first
512
+ if (targetStep === 'SignedIn') {
513
+ const existingAccount = getOriginAccount();
514
+ if (existingAccount && existingAccount.signer) {
515
+ autoConnectHandled = true;
354
516
  const mechanismUsed = existingAccount.mechanismUsed as
355
517
  | AlchemyMechanism
356
518
  | WalletMechanism<string, `0x${string}`>;
@@ -362,15 +524,11 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
362
524
  const chainIdAsHex = await withTimeout(walletProvider.getChainId());
363
525
  const chainId = Number(chainIdAsHex).toString();
364
526
  _wallet = {provider: walletProvider, chainId};
365
- // TODO
366
527
  alwaysOnProviderWrapper.setWalletProvider(walletProvider.underlyingProvider);
367
528
  watchForChainIdChange(_wallet.provider);
368
529
  let accounts: `0x${string}`[] = [];
369
- // try {
370
530
  accounts = await withTimeout(walletProvider.getAccounts());
371
531
  accounts = accounts.map((v) => v.toLowerCase() as `0x${string}`);
372
- // } catch {}
373
- // // TODO try catch ? and use logic of onAccountChanged
374
532
  set({
375
533
  step: 'SignedIn',
376
534
  account: existingAccount,
@@ -387,7 +545,6 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
387
545
  },
388
546
  });
389
547
  alwaysOnProviderWrapper.setWalletStatus('connected');
390
- // TODO use the same logic before hand
391
548
  onAccountChanged(accounts);
392
549
  watchForAccountChange(walletProvider);
393
550
  })
@@ -403,54 +560,46 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
403
560
  wallet: undefined,
404
561
  });
405
562
  }
406
- } else {
407
- set({step: 'Idle', loading: false, wallet: undefined, wallets: $connection.wallets});
408
563
  }
409
- } else {
410
- if (autoConnectWallet) {
411
- const lastWallet = getLastWallet();
412
- if (lastWallet) {
413
- waitForWallet(lastWallet.name)
414
- .then(async (walletDetails: WalletHandle<WalletProviderType>) => {
415
- const walletProvider = walletDetails.walletProvider;
416
- const chainIdAsHex = await withTimeout(walletProvider.getChainId());
417
- const chainId = Number(chainIdAsHex).toString();
418
- _wallet = {provider: walletProvider, chainId};
419
- // TODO
420
- alwaysOnProviderWrapper.setWalletProvider(walletProvider.underlyingProvider);
421
- watchForChainIdChange(_wallet.provider);
564
+ }
422
565
 
423
- let accounts: `0x${string}`[] = [];
424
- // try {
425
- accounts = await withTimeout(walletProvider.getAccounts());
426
- accounts = accounts.map((v) => v.toLowerCase() as `0x${string}`);
427
- // } catch {}
428
- // // TODO try catch ? and use logic of onAccountChanged
429
- set({
430
- step: 'WalletConnected',
431
- mechanism: lastWallet,
432
- wallets: $connection.wallets,
433
- wallet: {
434
- provider: walletProvider,
435
- accounts,
436
- status: 'connected',
437
- accountChanged: undefined,
438
- chainId,
439
- invalidChainId: alwaysOnChainId != chainId,
440
- switchingChain: false,
441
- },
442
- });
443
- alwaysOnProviderWrapper.setWalletStatus('connected');
444
- // TODO use the same logic before hand
445
- onAccountChanged(accounts);
446
- watchForAccountChange(walletProvider);
447
- })
448
- .catch((err) => {
449
- set({step: 'Idle', loading: false, wallet: undefined, wallets: $connection.wallets});
566
+ // For both targets, fallback to lastWallet if no account found (or WalletConnected target)
567
+ if (!autoConnectHandled) {
568
+ const lastWallet = getLastWallet();
569
+ if (lastWallet) {
570
+ waitForWallet(lastWallet.name)
571
+ .then(async (walletDetails: WalletHandle<WalletProviderType>) => {
572
+ const walletProvider = walletDetails.walletProvider;
573
+ const chainIdAsHex = await withTimeout(walletProvider.getChainId());
574
+ const chainId = Number(chainIdAsHex).toString();
575
+ _wallet = {provider: walletProvider, chainId};
576
+ alwaysOnProviderWrapper.setWalletProvider(walletProvider.underlyingProvider);
577
+ watchForChainIdChange(_wallet.provider);
578
+
579
+ let accounts: `0x${string}`[] = [];
580
+ accounts = await withTimeout(walletProvider.getAccounts());
581
+ accounts = accounts.map((v) => v.toLowerCase() as `0x${string}`);
582
+ set({
583
+ step: 'WalletConnected',
584
+ mechanism: lastWallet,
585
+ wallets: $connection.wallets,
586
+ wallet: {
587
+ provider: walletProvider,
588
+ accounts,
589
+ status: 'connected',
590
+ accountChanged: undefined,
591
+ chainId,
592
+ invalidChainId: alwaysOnChainId != chainId,
593
+ switchingChain: false,
594
+ },
450
595
  });
451
- } else {
452
- set({step: 'Idle', loading: false, wallet: undefined, wallets: $connection.wallets});
453
- }
596
+ alwaysOnProviderWrapper.setWalletStatus('connected');
597
+ onAccountChanged(accounts);
598
+ watchForAccountChange(walletProvider);
599
+ })
600
+ .catch((err) => {
601
+ set({step: 'Idle', loading: false, wallet: undefined, wallets: $connection.wallets});
602
+ });
454
603
  } else {
455
604
  set({step: 'Idle', loading: false, wallet: undefined, wallets: $connection.wallets});
456
605
  }
@@ -977,6 +1126,10 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
977
1126
  });
978
1127
  }
979
1128
  } else {
1129
+ // Popup-based auth requires walletHost
1130
+ if (!settings.walletHost) {
1131
+ throw new Error('walletHost is required for popup-based authentication (email, oauth, mnemonic)');
1132
+ }
980
1133
  popup = connectViaPopup({
981
1134
  mechanism,
982
1135
  walletHost: settings.walletHost,
@@ -1028,6 +1181,10 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
1028
1181
  }
1029
1182
  }
1030
1183
 
1184
+ // ensureConnected overloads - the default step depends on targetStep
1185
+ function ensureConnected(
1186
+ options?: ConnectionOptions,
1187
+ ): Promise<WalletConnected<WalletProviderType> | SignedIn<WalletProviderType>>;
1031
1188
  function ensureConnected(
1032
1189
  step: 'WalletConnected',
1033
1190
  mechanism?: WalletMechanism<string | undefined, `0x${string}` | undefined>,
@@ -1038,51 +1195,70 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
1038
1195
  mechanism?: Mechanism,
1039
1196
  options?: ConnectionOptions,
1040
1197
  ): Promise<SignedIn<WalletProviderType>>;
1041
- function ensureConnected(mechanism?: Mechanism, options?: ConnectionOptions): Promise<SignedIn<WalletProviderType>>;
1042
- async function ensureConnected<Step extends 'WalletConnected' | 'SignedIn' = 'SignedIn'>(
1043
- stepOrMechanism?: Step | Mechanism,
1198
+ async function ensureConnected<Step extends 'WalletConnected' | 'SignedIn'>(
1199
+ stepOrMechanismOrOptions?: Step | Mechanism | ConnectionOptions,
1044
1200
  mechanismOrOptions?: Mechanism | ConnectionOptions,
1045
1201
  options?: ConnectionOptions,
1046
- ) {
1047
- const step = typeof stepOrMechanism === 'string' ? stepOrMechanism : 'SignedIn';
1048
- let mechanism = typeof stepOrMechanism === 'string' ? (mechanismOrOptions as Mechanism) : stepOrMechanism;
1202
+ ): Promise<WalletConnected<WalletProviderType> | SignedIn<WalletProviderType>> {
1203
+ // Determine if first arg is a step string, mechanism, or options
1204
+ let step: 'WalletConnected' | 'SignedIn';
1205
+ let mechanism: Mechanism | undefined;
1206
+ let opts: ConnectionOptions | undefined;
1207
+
1208
+ if (typeof stepOrMechanismOrOptions === 'string') {
1209
+ // First arg is a step
1210
+ step = stepOrMechanismOrOptions as 'WalletConnected' | 'SignedIn';
1211
+ mechanism = mechanismOrOptions as Mechanism | undefined;
1212
+ opts = options;
1213
+ } else if (stepOrMechanismOrOptions && 'type' in (stepOrMechanismOrOptions as any)) {
1214
+ // First arg is a mechanism
1215
+ step = targetStep; // Use configured target as default
1216
+ mechanism = stepOrMechanismOrOptions as Mechanism;
1217
+ opts = mechanismOrOptions as ConnectionOptions | undefined;
1218
+ } else {
1219
+ // First arg is options or undefined
1220
+ step = targetStep; // Use configured target as default
1221
+ mechanism = undefined;
1222
+ opts = stepOrMechanismOrOptions as ConnectionOptions | undefined;
1223
+ }
1224
+
1225
+ // For WalletConnected step, default to wallet mechanism
1049
1226
  if (!mechanism && step === 'WalletConnected') {
1050
1227
  mechanism = {type: 'wallet'};
1051
1228
  }
1052
- options = typeof stepOrMechanism === 'string' ? options : (mechanismOrOptions as ConnectionOptions);
1053
- const promise = new Promise<
1054
- Step extends 'WalletConnected' ? WalletConnected<WalletProviderType> : SignedIn<WalletProviderType>
1055
- >((resolve, reject) => {
1056
- let forceConnect = false;
1057
- if (
1058
- $connection.step == 'WalletConnected' &&
1059
- ($connection.wallet.status == 'locked' || $connection.wallet.status === 'disconnected')
1060
- ) {
1061
- // console.log(`locked / disconnected : we assume it needs reconnection`);
1062
- forceConnect = true;
1063
- mechanism = $connection.mechanism; // we reuse existing mechanism as we just want to reconnect
1064
- } else if ($connection.step == step) {
1065
- resolve($connection as any);
1066
- return;
1067
- }
1068
- let idlePassed = $connection.step != 'Idle';
1069
- if (!idlePassed || forceConnect) {
1070
- connect(mechanism, options);
1071
- }
1072
- const unsubscribe = _store.subscribe((connection) => {
1073
- if (connection.step === 'Idle' && idlePassed) {
1074
- unsubscribe();
1075
- reject();
1076
- }
1077
- if (!idlePassed && connection.step !== 'Idle') {
1078
- idlePassed = true;
1229
+
1230
+ const promise = new Promise<WalletConnected<WalletProviderType> | SignedIn<WalletProviderType>>(
1231
+ (resolve, reject) => {
1232
+ let forceConnect = false;
1233
+ if (
1234
+ $connection.step == 'WalletConnected' &&
1235
+ ($connection.wallet.status == 'locked' || $connection.wallet.status === 'disconnected')
1236
+ ) {
1237
+ forceConnect = true;
1238
+ mechanism = $connection.mechanism; // we reuse existing mechanism as we just want to reconnect
1239
+ } else if ($connection.step == step) {
1240
+ resolve($connection as any);
1241
+ return;
1079
1242
  }
1080
- if (connection.step === step) {
1081
- unsubscribe();
1082
- resolve(connection as any);
1243
+ let idlePassed = $connection.step != 'Idle';
1244
+ if (!idlePassed || forceConnect) {
1245
+ connect(mechanism, opts);
1083
1246
  }
1084
- });
1085
- });
1247
+ const unsubscribe = _store.subscribe((connection) => {
1248
+ if (connection.step === 'Idle' && idlePassed) {
1249
+ unsubscribe();
1250
+ reject();
1251
+ }
1252
+ if (!idlePassed && connection.step !== 'Idle') {
1253
+ idlePassed = true;
1254
+ }
1255
+ if (connection.step === step) {
1256
+ unsubscribe();
1257
+ resolve(connection as any);
1258
+ }
1259
+ });
1260
+ },
1261
+ );
1086
1262
 
1087
1263
  return promise;
1088
1264
  }
@@ -1386,7 +1562,27 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
1386
1562
  }
1387
1563
  }
1388
1564
 
1389
- return {
1565
+ // Determine walletOnly (defaults to false, but true implies WalletConnected target behavior for mechanism)
1566
+ const walletOnly = settings.walletOnly || targetStep === 'WalletConnected';
1567
+
1568
+ // Method on the store to check if target step is reached
1569
+ function storeIsTargetStepReached(connection: Connection<WalletProviderType>): boolean {
1570
+ if (targetStep === 'WalletConnected') {
1571
+ // For WalletConnected target, accept WalletConnected OR SignedIn-with-wallet
1572
+ return (
1573
+ connection.step === 'WalletConnected' || (connection.step === 'SignedIn' && connection.wallet !== undefined)
1574
+ );
1575
+ }
1576
+ // For SignedIn target
1577
+ if (walletOnly) {
1578
+ // With walletOnly, only accept SignedIn-with-wallet
1579
+ return connection.step === 'SignedIn' && connection.wallet !== undefined;
1580
+ }
1581
+ // Accept any SignedIn variant
1582
+ return connection.step === 'SignedIn';
1583
+ }
1584
+
1585
+ const store = {
1390
1586
  subscribe: _store.subscribe,
1391
1587
  connect,
1392
1588
  cancel,
@@ -1397,9 +1593,14 @@ export function createConnection<WalletProviderType = UnderlyingEthereumProvider
1397
1593
  getSignatureForPublicKeyPublication,
1398
1594
  switchWalletChain,
1399
1595
  unlock,
1400
- ensureConnected,
1596
+ ensureConnected: ensureConnected as any, // Cast to bypass complex conditional typing
1597
+ isTargetStepReached: storeIsTargetStepReached as any, // Cast for type guard
1598
+ targetStep,
1599
+ walletOnly,
1401
1600
  provider: alwaysOnProviderWrapper.provider,
1402
1601
  chainId: '' + settings.chainInfo.id,
1403
1602
  chainInfo: settings.chainInfo,
1404
1603
  };
1604
+
1605
+ return store as ConnectionStore<WalletProviderType, TargetStep, boolean>;
1405
1606
  }