@dynamic-labs/waas 4.89.0 → 4.91.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.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,40 @@
1
1
 
2
+ ## [4.91.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.90.0...v4.91.0) (2026-06-23)
3
+
4
+
5
+ ### Features
6
+
7
+ * **midnight:** add WaaS layer to @dynamic-labs/midnight ([#11006](https://github.com/dynamic-labs/dynamic-auth/issues/11006)) ([01b9d16](https://github.com/dynamic-labs/dynamic-auth/commit/01b9d16d0ba92a141a9be546ca00cca4029894d5))
8
+ * **sdk-react-core:** widget support for Midnight multi-address display + export hiding ([#11008](https://github.com/dynamic-labs/dynamic-auth/issues/11008)) ([87320b4](https://github.com/dynamic-labs/dynamic-auth/commit/87320b4d6e286b88e17f144641346e16a27e9e71))
9
+ * **waas:** wire session public key through host backup ops for keyshare diagnostics [DYNT-1280] ([#11675](https://github.com/dynamic-labs/dynamic-auth/issues/11675)) ([4f7981c](https://github.com/dynamic-labs/dynamic-auth/commit/4f7981c009930469dec7b3dec93f20f99bbe5498))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **ethereum-aa:** make ZeroDevConnector inert instead of throwing when ZeroDev config is absent ([#11610](https://github.com/dynamic-labs/dynamic-auth/issues/11610)) ([a3aab6a](https://github.com/dynamic-labs/dynamic-auth/commit/a3aab6a97e2e996f96b725f9336704b844f79603))
15
+ * **react-native-extension:** fall back to react-native-webview when embedded native module is unavailable ([#11683](https://github.com/dynamic-labs/dynamic-auth/issues/11683)) ([3f8d92c](https://github.com/dynamic-labs/dynamic-auth/commit/3f8d92ca0c327128b16e3355ab2f4e63d2c8e731))
16
+ * **sdk-react-core:** warn instead of throwing when ZeroDev connector is absent ([#11611](https://github.com/dynamic-labs/dynamic-auth/issues/11611)) ([5836252](https://github.com/dynamic-labs/dynamic-auth/commit/58362524e45205399c0fdc5b92414190d1929e0b))
17
+ * **waas-evm:** sign EOA transactions with the resolved chainId, not the connector default ([#11653](https://github.com/dynamic-labs/dynamic-auth/issues/11653)) ([e634098](https://github.com/dynamic-labs/dynamic-auth/commit/e634098f0f23f4c53f92a580db6f4aacbd6b1f92))
18
+
19
+ ## [4.90.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.89.0...v4.90.0) (2026-06-19)
20
+
21
+
22
+ ### Features
23
+
24
+ * add microsoft to social providers list ([#11631](https://github.com/dynamic-labs/dynamic-auth/issues/11631)) ([761b82d](https://github.com/dynamic-labs/dynamic-auth/commit/761b82dea2e2e86ec1c8b3fc8bab32ed72492132))
25
+ * **sdk-react-core:** preconnect to waas iframe origin at auth-flow start ([#11549](https://github.com/dynamic-labs/dynamic-auth/issues/11549)) ([1bb810c](https://github.com/dynamic-labs/dynamic-auth/commit/1bb810c4eb3d592b43f62c1eaf5450a0e42981c9)), closes [dynamic-labs/dynamic-wallet-sdk#1314](https://github.com/dynamic-labs/dynamic-wallet-sdk/issues/1314)
26
+ * **waas:** opt into SDK eager key-share recovery, drop host RECOVER loop (DYNT-1125) ([#11620](https://github.com/dynamic-labs/dynamic-auth/issues/11620)) ([4bef955](https://github.com/dynamic-labs/dynamic-auth/commit/4bef955b902a7e484ba8bd6323582843bb692a17))
27
+ * **wallet-connector-core:** add isMidnightConnector util + Midnight interface hooks ([#11005](https://github.com/dynamic-labs/dynamic-auth/issues/11005)) ([55117a8](https://github.com/dynamic-labs/dynamic-auth/commit/55117a8cf59640d00b358f68be8df6b29231fab5))
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * **sdk-react-core:** gate smart-wallet init on user:basic scope ([#11624](https://github.com/dynamic-labs/dynamic-auth/issues/11624)) ([9f891a4](https://github.com/dynamic-labs/dynamic-auth/commit/9f891a46a191532acdd4a91b74c4586089ef0e1a)), closes [#11623](https://github.com/dynamic-labs/dynamic-auth/issues/11623) [#11623](https://github.com/dynamic-labs/dynamic-auth/issues/11623) [#11626](https://github.com/dynamic-labs/dynamic-auth/issues/11626)
33
+ * **sdk-react-core:** search wallet list by metadata name in addition to connector name ([#11638](https://github.com/dynamic-labs/dynamic-auth/issues/11638)) ([a647614](https://github.com/dynamic-labs/dynamic-auth/commit/a647614bcba769e09ef59bf1ddb925ce9835842e))
34
+ * **waas:** rebuild WaaS client on session change after step-up reauth ([#11581](https://github.com/dynamic-labs/dynamic-auth/issues/11581)) ([2b7093e](https://github.com/dynamic-labs/dynamic-auth/commit/2b7093e27f57f1dfcdd303361d436d9bf33e28a9))
35
+ * **waas:** refuse to build WaaS client while auth scope is pending (createRooms 401 chokepoint) ([#11623](https://github.com/dynamic-labs/dynamic-auth/issues/11623)) ([08755f0](https://github.com/dynamic-labs/dynamic-auth/commit/08755f0bda2b74c7bcdff3d0ac9c228a5205ad6e)), closes [#11624](https://github.com/dynamic-labs/dynamic-auth/issues/11624)
36
+ * **wallet-connect:** skip address comparison on reconnect when expectedAddress is empty ([#11639](https://github.com/dynamic-labs/dynamic-auth/issues/11639)) ([8d665cf](https://github.com/dynamic-labs/dynamic-auth/commit/8d665cf3bf1fb0e498d71f14cc8a53d91593c23d))
37
+
2
38
  ## [4.89.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.88.6...v4.89.0) (2026-06-16)
3
39
 
4
40
 
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.89.0";
6
+ var version = "4.91.0";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.89.0";
2
+ var version = "4.91.0";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/waas",
3
- "version": "4.89.0",
3
+ "version": "4.91.0",
4
4
  "type": "module",
5
5
  "author": "Dynamic Labs, Inc.",
6
6
  "license": "MIT",
@@ -16,18 +16,18 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "dependencies": {
19
- "@dynamic-labs-sdk/client": "1.8.2",
20
- "@dynamic-labs/assert-package-version": "4.89.0",
21
- "@dynamic-labs/sdk-api-core": "0.0.1015",
22
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.16",
23
- "@dynamic-labs-wallet/forward-mpc-client": "0.10.1",
24
- "@dynamic-labs/ethereum-core": "4.89.0",
25
- "@dynamic-labs/logger": "4.89.0",
26
- "@dynamic-labs/solana-core": "4.89.0",
27
- "@dynamic-labs/sui-core": "4.89.0",
28
- "@dynamic-labs/utils": "4.89.0",
29
- "@dynamic-labs/wallet-book": "4.89.0",
30
- "@dynamic-labs/wallet-connector-core": "4.89.0"
19
+ "@dynamic-labs-sdk/client": "1.12.1",
20
+ "@dynamic-labs/assert-package-version": "4.91.0",
21
+ "@dynamic-labs/sdk-api-core": "0.0.1046",
22
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.44",
23
+ "@dynamic-labs-wallet/forward-mpc-client": "0.12.0",
24
+ "@dynamic-labs/ethereum-core": "4.91.0",
25
+ "@dynamic-labs/logger": "4.91.0",
26
+ "@dynamic-labs/solana-core": "4.91.0",
27
+ "@dynamic-labs/sui-core": "4.91.0",
28
+ "@dynamic-labs/utils": "4.91.0",
29
+ "@dynamic-labs/wallet-book": "4.91.0",
30
+ "@dynamic-labs/wallet-connector-core": "4.91.0"
31
31
  },
32
32
  "peerDependencies": {}
33
33
  }
@@ -11,6 +11,7 @@ var _package = require('../package.cjs');
11
11
  var constants = require('../utils/constants.cjs');
12
12
  var createWaasClientSecureStorage = require('../utils/createWaasClientSecureStorage.cjs');
13
13
  var instrumentation = require('../utils/instrumentation.cjs');
14
+ var tokenHasPendingAuthScope = require('../utils/tokenHasPendingAuthScope.cjs');
14
15
 
15
16
  // Fallback error code used when a thrown value carries no code, or when
16
17
  // formatting the error itself fails.
@@ -126,9 +127,12 @@ const withDynamicWaas = (BaseClass) => {
126
127
  setGetSignedSessionIdFunction(getSignedSessionId) {
127
128
  this.getSignedSessionId = getSignedSessionId;
128
129
  }
130
+ setGetSessionPublicKeyFunction(getSessionPublicKey) {
131
+ this.getSessionPublicKey = getSessionPublicKey;
132
+ }
129
133
  delegateKeyShares(_a) {
130
134
  return _tslib.__awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
131
- var _b, _c;
135
+ var _b, _c, _d;
132
136
  if (!accountAddress) {
133
137
  throw new Error('Account address is required');
134
138
  }
@@ -146,6 +150,7 @@ const withDynamicWaas = (BaseClass) => {
146
150
  accountAddress,
147
151
  authToken,
148
152
  password: resolvedPassword,
153
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
149
154
  signedSessionId,
150
155
  });
151
156
  });
@@ -165,6 +170,7 @@ const withDynamicWaas = (BaseClass) => {
165
170
  ALEO: 'ALEO',
166
171
  BTC: 'BTC',
167
172
  EVM: 'EVM',
173
+ MIDNIGHT: 'MIDNIGHT',
168
174
  SOL: 'SVM',
169
175
  STELLAR: 'STELLAR',
170
176
  SUI: 'SUI',
@@ -194,12 +200,17 @@ const withDynamicWaas = (BaseClass) => {
194
200
  baseClientKeysharesRelayApiUrl: this.baseClientKeysharesRelayApiUrl,
195
201
  baseMPCRelayApiUrl: this.relayUrl || constants.DEFAULT_BASE_MPC_RELAY_API_URL,
196
202
  chainName: this.chainName,
203
+ // Opt into the SDK self-driving eager key-share recovery on auth-token
204
+ // arrival, so the host no longer runs its own per-wallet RECOVER loop.
205
+ eagerlyRecoverKeyShares: true,
197
206
  environmentId: this.environmentId,
198
207
  sdkVersion: _package.version,
199
- }, Object.assign(Object.assign(Object.assign({}, (utils.PlatformService.isWaasSecureStorageSupported
208
+ }, Object.assign(Object.assign(Object.assign(Object.assign({}, (utils.PlatformService.isWaasSecureStorageSupported
200
209
  ? { secureStorage: createWaasClientSecureStorage.createWaasClientSecureStorage() }
201
210
  : {})), (this.getSignedSessionId
202
211
  ? { getSignedSessionId: this.getSignedSessionId }
212
+ : {})), (this.getSessionPublicKey
213
+ ? { getSessionPublicKey: this.getSessionPublicKey }
203
214
  : {})), (this.onUnauthorized
204
215
  ? { onUnauthorized: this.onUnauthorized }
205
216
  : {})));
@@ -212,9 +223,29 @@ const withDynamicWaas = (BaseClass) => {
212
223
  return client;
213
224
  });
214
225
  }
215
- getWaasWalletClient(traceContext) {
216
- return _tslib.__awaiter(this, void 0, void 0, function* () {
226
+ getWaasWalletClient(traceContext_1) {
227
+ return _tslib.__awaiter(this, arguments, void 0, function* (traceContext, { forceRebuild = false } = {}) {
228
+ var _a;
229
+ // forceRebuild drops the cached client so the iframe re-auths with the
230
+ // fresh session (e.g. after a step-up reauth) instead of 401-ing on the
231
+ // stale cached token. Callers must only force a rebuild when a token is
232
+ // available (see getWaasWalletConnector's live-token guard); otherwise
233
+ // createDynamicWaasClient would throw "Auth token is required".
234
+ if (forceRebuild) {
235
+ this.dynamicWaasClient = undefined;
236
+ }
217
237
  if (!this.dynamicWaasClient) {
238
+ // Chokepoint: never build + initialize the iframe client while the auth
239
+ // token is still pending MFA/KYC/device registration. The iframe's
240
+ // init-time room-cache pre-warm fires `createRooms` using the token
241
+ // captured at construction — before any per-operation token resync
242
+ // exists to correct it. Built during the pending window, that pre-warm
243
+ // 401s and trips the iframe's unauthorized → forced-logout path. Every
244
+ // WaaS caller funnels through here, so this single guard covers auto
245
+ // wallet creation, ZeroDev smart-wallet init, getWallet, etc.
246
+ if (tokenHasPendingAuthScope.tokenHasPendingAuthScope((_a = this.getAuthToken) === null || _a === void 0 ? void 0 : _a.call(this))) {
247
+ throw new utils.WaasAuthScopePendingError();
248
+ }
218
249
  this.dynamicWaasClient = yield this.createDynamicWaasClient(traceContext);
219
250
  }
220
251
  return this.dynamicWaasClient;
@@ -320,7 +351,7 @@ const withDynamicWaas = (BaseClass) => {
320
351
  }
321
352
  backupKeySharesToGoogleDrive(_a) {
322
353
  return _tslib.__awaiter(this, arguments, void 0, function* ({ accountAddress, password, googleDriveAccessToken, }) {
323
- var _b, _c;
354
+ var _b, _c, _d;
324
355
  if (!accountAddress) {
325
356
  throw new utils.DynamicError('Account address is required');
326
357
  }
@@ -335,6 +366,7 @@ const withDynamicWaas = (BaseClass) => {
335
366
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
336
367
  googleDriveAccessToken,
337
368
  password: resolvedPassword,
369
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
338
370
  signedSessionId,
339
371
  });
340
372
  });
@@ -361,7 +393,7 @@ const withDynamicWaas = (BaseClass) => {
361
393
  }
362
394
  backupKeySharesToICloud(_a) {
363
395
  return _tslib.__awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
364
- var _b, _c;
396
+ var _b, _c, _d;
365
397
  if (!accountAddress) {
366
398
  throw new utils.DynamicError('Account address is required');
367
399
  }
@@ -375,6 +407,7 @@ const withDynamicWaas = (BaseClass) => {
375
407
  accountAddress,
376
408
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
377
409
  password: resolvedPassword,
410
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
378
411
  signedSessionId,
379
412
  });
380
413
  });
@@ -401,7 +434,7 @@ const withDynamicWaas = (BaseClass) => {
401
434
  }
402
435
  refreshWalletAccountShares(_a) {
403
436
  return _tslib.__awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
404
- var _b, _c, _d, _e, _f, _g;
437
+ var _b, _c, _d, _e, _f, _g, _h;
405
438
  if (!accountAddress) {
406
439
  throw new utils.DynamicError('Account address is required');
407
440
  }
@@ -429,13 +462,14 @@ const withDynamicWaas = (BaseClass) => {
429
462
  elevatedAccessToken,
430
463
  mfaToken,
431
464
  password: resolvedPassword,
465
+ sessionPublicKey: yield ((_h = this.getSessionPublicKey) === null || _h === void 0 ? void 0 : _h.call(this)),
432
466
  signedSessionId: backupSignedSessionId,
433
467
  });
434
468
  });
435
469
  }
436
470
  reshareWalletAccountShares(_a) {
437
471
  return _tslib.__awaiter(this, arguments, void 0, function* ({ accountAddress, thresholdSignatureScheme, password, }) {
438
- var _b, _c, _d, _e;
472
+ var _b, _c, _d, _e, _f;
439
473
  if (!accountAddress) {
440
474
  throw new utils.DynamicError('Account address is required');
441
475
  }
@@ -465,13 +499,14 @@ const withDynamicWaas = (BaseClass) => {
465
499
  newThresholdSignatureScheme: thresholdSignatureScheme,
466
500
  oldThresholdSignatureScheme,
467
501
  password: resolvedPassword,
502
+ sessionPublicKey: yield ((_f = this.getSessionPublicKey) === null || _f === void 0 ? void 0 : _f.call(this)),
468
503
  signedSessionId,
469
504
  });
470
505
  });
471
506
  }
472
507
  revokeDelegation(_a) {
473
508
  return _tslib.__awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
474
- var _b, _c;
509
+ var _b, _c, _d;
475
510
  if (!accountAddress) {
476
511
  throw new utils.DynamicError('Account address is required');
477
512
  }
@@ -485,6 +520,7 @@ const withDynamicWaas = (BaseClass) => {
485
520
  accountAddress,
486
521
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
487
522
  password: resolvedPassword,
523
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
488
524
  signedSessionId,
489
525
  });
490
526
  });
@@ -506,7 +542,7 @@ const withDynamicWaas = (BaseClass) => {
506
542
  const encryptedWallets = wallets.filter((w) => { var _a; return (_a = w.clientKeySharesBackupInfo) === null || _a === void 0 ? void 0 : _a.passwordEncrypted; });
507
543
  const passwordUpdateBatchId = crypto.randomUUID();
508
544
  yield Promise.all(encryptedWallets.map((wallet) => _tslib.__awaiter(this, void 0, void 0, function* () {
509
- var _b;
545
+ var _b, _c;
510
546
  const signedSessionId = yield this.getSignedSessionId();
511
547
  yield walletClient.updatePassword({
512
548
  accountAddress: wallet.accountAddress,
@@ -514,6 +550,7 @@ const withDynamicWaas = (BaseClass) => {
514
550
  existingPassword: resolvedExistingPassword,
515
551
  newPassword,
516
552
  passwordUpdateBatchId,
553
+ sessionPublicKey: yield ((_c = this.getSessionPublicKey) === null || _c === void 0 ? void 0 : _c.call(this)),
517
554
  signedSessionId,
518
555
  });
519
556
  })));
@@ -531,13 +568,14 @@ const withDynamicWaas = (BaseClass) => {
531
568
  const wallets = yield walletClient.getAllWallets();
532
569
  const passwordUpdateBatchId = crypto.randomUUID();
533
570
  yield Promise.all(wallets.map((wallet) => _tslib.__awaiter(this, void 0, void 0, function* () {
534
- var _b;
571
+ var _b, _c;
535
572
  const signedSessionId = yield this.getSignedSessionId();
536
573
  yield walletClient.setPassword({
537
574
  accountAddress: wallet.accountAddress,
538
575
  authToken: (_b = this.getAuthToken) === null || _b === void 0 ? void 0 : _b.call(this),
539
576
  newPassword,
540
577
  passwordUpdateBatchId,
578
+ sessionPublicKey: yield ((_c = this.getSessionPublicKey) === null || _c === void 0 ? void 0 : _c.call(this)),
541
579
  signedSessionId,
542
580
  });
543
581
  })));
@@ -612,9 +650,18 @@ const withDynamicWaas = (BaseClass) => {
612
650
  }
613
651
  endSession(reason) {
614
652
  return _tslib.__awaiter(this, void 0, void 0, function* () {
615
- const waasClient = yield this.getWaasWalletClient();
616
- if (!waasClient) {
617
- return;
653
+ // Building the wallet client requires an auth token (createDynamicWaasClient
654
+ // throws in header auth mode when none is present); the new scope guard in
655
+ // getWaasWalletClient also throws during pending-auth (MFA/KYC/device). On
656
+ // logout we must not let either throw abort logout — the security-critical
657
+ // key share removal below does not need the client. Only the iframe cleanup
658
+ // does, so we proceed without it if the client can't be obtained.
659
+ let waasClient;
660
+ try {
661
+ waasClient = yield this.getWaasWalletClient();
662
+ }
663
+ catch (error) {
664
+ this.instrument('[endSession] getWaasWalletClient failed; proceeding', Object.assign({ key: 'endSession-getClient-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
618
665
  }
619
666
  // When a session token expires, we preserve key shares in storage instead
620
667
  // of clearing them. Customers with short-lived sessions (minutes) were
@@ -646,9 +693,13 @@ const withDynamicWaas = (BaseClass) => {
646
693
  // iframe cleanup's request-channel timeout. The iframe handler already
647
694
  // swallows its own errors, and the DOM teardown obsoletes anything it
648
695
  // misses. Promise.resolve guards against a non-promise return in tests.
649
- void Promise.resolve(waasClient.cleanup()).catch((error) => {
650
- this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
651
- });
696
+ // Skipped when the client could not be built/obtained (no token, or
697
+ // pending-auth scope guard) there is no iframe to tear down then.
698
+ if (waasClient) {
699
+ void Promise.resolve(waasClient.cleanup()).catch((error) => {
700
+ this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
701
+ });
702
+ }
652
703
  }
653
704
  this.dynamicWaasClient = undefined;
654
705
  });
@@ -755,6 +806,11 @@ const withDynamicWaas = (BaseClass) => {
755
806
  }
756
807
  });
757
808
  }
809
+ getPrivateBalance() {
810
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
811
+ throw new utils.DynamicError(`getPrivateBalance is not supported for ${this.chainName} wallets`);
812
+ });
813
+ }
758
814
  }
759
815
  return DynamicWaasMixin;
760
816
  };
@@ -15,6 +15,7 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
15
15
  overrideKey: string;
16
16
  isEmbeddedWallet: boolean;
17
17
  getSignedSessionId?: (() => Promise<string>) | undefined;
18
+ getSessionPublicKey?: (() => Promise<string | undefined>) | undefined;
18
19
  getMfaToken?: ((props?: {
19
20
  mfaAction?: MFAAction;
20
21
  }) => Promise<string | undefined>) | undefined;
@@ -71,12 +72,15 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
71
72
  setBaseClientKeysharesRelayApiUrl(baseClientKeysharesRelayApiUrl?: string): void;
72
73
  setRelayUrl(relayUrl: string): void;
73
74
  setGetSignedSessionIdFunction(getSignedSessionId: () => Promise<string>): void;
75
+ setGetSessionPublicKeyFunction(getSessionPublicKey: () => Promise<string | undefined>): void;
74
76
  delegateKeyShares({ accountAddress, password, }: {
75
77
  accountAddress: string;
76
78
  password?: string;
77
79
  }): Promise<void>;
78
80
  createDynamicWaasClient(traceContext?: TraceContext): Promise<DynamicWalletClient>;
79
- getWaasWalletClient(traceContext?: TraceContext): Promise<DynamicWalletClient>;
81
+ getWaasWalletClient(traceContext?: TraceContext, { forceRebuild }?: {
82
+ forceRebuild?: boolean;
83
+ }): Promise<DynamicWalletClient>;
80
84
  createWalletAccount({ thresholdSignatureScheme, password, bitcoinConfig, }?: {
81
85
  thresholdSignatureScheme?: string;
82
86
  password?: string;
@@ -199,4 +203,14 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
199
203
  fn: (timing: InstrumentationTimer) => Promise<T_1>;
200
204
  context?: Record<string, any> | undefined;
201
205
  }): Promise<T_1>;
206
+ getPrivateBalance(): Promise<{
207
+ unshielded: Record<string, string>;
208
+ shielded: Record<string, string>;
209
+ dust: {
210
+ balance: string;
211
+ cap: string;
212
+ };
213
+ address: string;
214
+ dustSynced: boolean;
215
+ }>;
202
216
  }) & T;
@@ -2,11 +2,12 @@
2
2
  import { __awaiter } from '../_virtual/_tslib.js';
3
3
  import { DynamicWalletClient } from '@dynamic-labs-wallet/browser-wallet-client';
4
4
  import { MFAAction, TokenScope } from '@dynamic-labs/sdk-api-core';
5
- import { DynamicError, UserRejectedRequestError, PlatformService } from '@dynamic-labs/utils';
5
+ import { DynamicError, UserRejectedRequestError, PlatformService, WaasAuthScopePendingError } from '@dynamic-labs/utils';
6
6
  import { version } from '../package.js';
7
7
  import { DEFAULT_BASE_API_URL, DEFAULT_BASE_MPC_RELAY_API_URL } from '../utils/constants.js';
8
8
  import { createWaasClientSecureStorage } from '../utils/createWaasClientSecureStorage.js';
9
9
  import { InstrumentationTimer } from '../utils/instrumentation.js';
10
+ import { tokenHasPendingAuthScope } from '../utils/tokenHasPendingAuthScope.js';
10
11
 
11
12
  // Fallback error code used when a thrown value carries no code, or when
12
13
  // formatting the error itself fails.
@@ -122,9 +123,12 @@ const withDynamicWaas = (BaseClass) => {
122
123
  setGetSignedSessionIdFunction(getSignedSessionId) {
123
124
  this.getSignedSessionId = getSignedSessionId;
124
125
  }
126
+ setGetSessionPublicKeyFunction(getSessionPublicKey) {
127
+ this.getSessionPublicKey = getSessionPublicKey;
128
+ }
125
129
  delegateKeyShares(_a) {
126
130
  return __awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
127
- var _b, _c;
131
+ var _b, _c, _d;
128
132
  if (!accountAddress) {
129
133
  throw new Error('Account address is required');
130
134
  }
@@ -142,6 +146,7 @@ const withDynamicWaas = (BaseClass) => {
142
146
  accountAddress,
143
147
  authToken,
144
148
  password: resolvedPassword,
149
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
145
150
  signedSessionId,
146
151
  });
147
152
  });
@@ -161,6 +166,7 @@ const withDynamicWaas = (BaseClass) => {
161
166
  ALEO: 'ALEO',
162
167
  BTC: 'BTC',
163
168
  EVM: 'EVM',
169
+ MIDNIGHT: 'MIDNIGHT',
164
170
  SOL: 'SVM',
165
171
  STELLAR: 'STELLAR',
166
172
  SUI: 'SUI',
@@ -190,12 +196,17 @@ const withDynamicWaas = (BaseClass) => {
190
196
  baseClientKeysharesRelayApiUrl: this.baseClientKeysharesRelayApiUrl,
191
197
  baseMPCRelayApiUrl: this.relayUrl || DEFAULT_BASE_MPC_RELAY_API_URL,
192
198
  chainName: this.chainName,
199
+ // Opt into the SDK self-driving eager key-share recovery on auth-token
200
+ // arrival, so the host no longer runs its own per-wallet RECOVER loop.
201
+ eagerlyRecoverKeyShares: true,
193
202
  environmentId: this.environmentId,
194
203
  sdkVersion: version,
195
- }, Object.assign(Object.assign(Object.assign({}, (PlatformService.isWaasSecureStorageSupported
204
+ }, Object.assign(Object.assign(Object.assign(Object.assign({}, (PlatformService.isWaasSecureStorageSupported
196
205
  ? { secureStorage: createWaasClientSecureStorage() }
197
206
  : {})), (this.getSignedSessionId
198
207
  ? { getSignedSessionId: this.getSignedSessionId }
208
+ : {})), (this.getSessionPublicKey
209
+ ? { getSessionPublicKey: this.getSessionPublicKey }
199
210
  : {})), (this.onUnauthorized
200
211
  ? { onUnauthorized: this.onUnauthorized }
201
212
  : {})));
@@ -208,9 +219,29 @@ const withDynamicWaas = (BaseClass) => {
208
219
  return client;
209
220
  });
210
221
  }
211
- getWaasWalletClient(traceContext) {
212
- return __awaiter(this, void 0, void 0, function* () {
222
+ getWaasWalletClient(traceContext_1) {
223
+ return __awaiter(this, arguments, void 0, function* (traceContext, { forceRebuild = false } = {}) {
224
+ var _a;
225
+ // forceRebuild drops the cached client so the iframe re-auths with the
226
+ // fresh session (e.g. after a step-up reauth) instead of 401-ing on the
227
+ // stale cached token. Callers must only force a rebuild when a token is
228
+ // available (see getWaasWalletConnector's live-token guard); otherwise
229
+ // createDynamicWaasClient would throw "Auth token is required".
230
+ if (forceRebuild) {
231
+ this.dynamicWaasClient = undefined;
232
+ }
213
233
  if (!this.dynamicWaasClient) {
234
+ // Chokepoint: never build + initialize the iframe client while the auth
235
+ // token is still pending MFA/KYC/device registration. The iframe's
236
+ // init-time room-cache pre-warm fires `createRooms` using the token
237
+ // captured at construction — before any per-operation token resync
238
+ // exists to correct it. Built during the pending window, that pre-warm
239
+ // 401s and trips the iframe's unauthorized → forced-logout path. Every
240
+ // WaaS caller funnels through here, so this single guard covers auto
241
+ // wallet creation, ZeroDev smart-wallet init, getWallet, etc.
242
+ if (tokenHasPendingAuthScope((_a = this.getAuthToken) === null || _a === void 0 ? void 0 : _a.call(this))) {
243
+ throw new WaasAuthScopePendingError();
244
+ }
214
245
  this.dynamicWaasClient = yield this.createDynamicWaasClient(traceContext);
215
246
  }
216
247
  return this.dynamicWaasClient;
@@ -316,7 +347,7 @@ const withDynamicWaas = (BaseClass) => {
316
347
  }
317
348
  backupKeySharesToGoogleDrive(_a) {
318
349
  return __awaiter(this, arguments, void 0, function* ({ accountAddress, password, googleDriveAccessToken, }) {
319
- var _b, _c;
350
+ var _b, _c, _d;
320
351
  if (!accountAddress) {
321
352
  throw new DynamicError('Account address is required');
322
353
  }
@@ -331,6 +362,7 @@ const withDynamicWaas = (BaseClass) => {
331
362
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
332
363
  googleDriveAccessToken,
333
364
  password: resolvedPassword,
365
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
334
366
  signedSessionId,
335
367
  });
336
368
  });
@@ -357,7 +389,7 @@ const withDynamicWaas = (BaseClass) => {
357
389
  }
358
390
  backupKeySharesToICloud(_a) {
359
391
  return __awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
360
- var _b, _c;
392
+ var _b, _c, _d;
361
393
  if (!accountAddress) {
362
394
  throw new DynamicError('Account address is required');
363
395
  }
@@ -371,6 +403,7 @@ const withDynamicWaas = (BaseClass) => {
371
403
  accountAddress,
372
404
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
373
405
  password: resolvedPassword,
406
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
374
407
  signedSessionId,
375
408
  });
376
409
  });
@@ -397,7 +430,7 @@ const withDynamicWaas = (BaseClass) => {
397
430
  }
398
431
  refreshWalletAccountShares(_a) {
399
432
  return __awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
400
- var _b, _c, _d, _e, _f, _g;
433
+ var _b, _c, _d, _e, _f, _g, _h;
401
434
  if (!accountAddress) {
402
435
  throw new DynamicError('Account address is required');
403
436
  }
@@ -425,13 +458,14 @@ const withDynamicWaas = (BaseClass) => {
425
458
  elevatedAccessToken,
426
459
  mfaToken,
427
460
  password: resolvedPassword,
461
+ sessionPublicKey: yield ((_h = this.getSessionPublicKey) === null || _h === void 0 ? void 0 : _h.call(this)),
428
462
  signedSessionId: backupSignedSessionId,
429
463
  });
430
464
  });
431
465
  }
432
466
  reshareWalletAccountShares(_a) {
433
467
  return __awaiter(this, arguments, void 0, function* ({ accountAddress, thresholdSignatureScheme, password, }) {
434
- var _b, _c, _d, _e;
468
+ var _b, _c, _d, _e, _f;
435
469
  if (!accountAddress) {
436
470
  throw new DynamicError('Account address is required');
437
471
  }
@@ -461,13 +495,14 @@ const withDynamicWaas = (BaseClass) => {
461
495
  newThresholdSignatureScheme: thresholdSignatureScheme,
462
496
  oldThresholdSignatureScheme,
463
497
  password: resolvedPassword,
498
+ sessionPublicKey: yield ((_f = this.getSessionPublicKey) === null || _f === void 0 ? void 0 : _f.call(this)),
464
499
  signedSessionId,
465
500
  });
466
501
  });
467
502
  }
468
503
  revokeDelegation(_a) {
469
504
  return __awaiter(this, arguments, void 0, function* ({ accountAddress, password, }) {
470
- var _b, _c;
505
+ var _b, _c, _d;
471
506
  if (!accountAddress) {
472
507
  throw new DynamicError('Account address is required');
473
508
  }
@@ -481,6 +516,7 @@ const withDynamicWaas = (BaseClass) => {
481
516
  accountAddress,
482
517
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
483
518
  password: resolvedPassword,
519
+ sessionPublicKey: yield ((_d = this.getSessionPublicKey) === null || _d === void 0 ? void 0 : _d.call(this)),
484
520
  signedSessionId,
485
521
  });
486
522
  });
@@ -502,7 +538,7 @@ const withDynamicWaas = (BaseClass) => {
502
538
  const encryptedWallets = wallets.filter((w) => { var _a; return (_a = w.clientKeySharesBackupInfo) === null || _a === void 0 ? void 0 : _a.passwordEncrypted; });
503
539
  const passwordUpdateBatchId = crypto.randomUUID();
504
540
  yield Promise.all(encryptedWallets.map((wallet) => __awaiter(this, void 0, void 0, function* () {
505
- var _b;
541
+ var _b, _c;
506
542
  const signedSessionId = yield this.getSignedSessionId();
507
543
  yield walletClient.updatePassword({
508
544
  accountAddress: wallet.accountAddress,
@@ -510,6 +546,7 @@ const withDynamicWaas = (BaseClass) => {
510
546
  existingPassword: resolvedExistingPassword,
511
547
  newPassword,
512
548
  passwordUpdateBatchId,
549
+ sessionPublicKey: yield ((_c = this.getSessionPublicKey) === null || _c === void 0 ? void 0 : _c.call(this)),
513
550
  signedSessionId,
514
551
  });
515
552
  })));
@@ -527,13 +564,14 @@ const withDynamicWaas = (BaseClass) => {
527
564
  const wallets = yield walletClient.getAllWallets();
528
565
  const passwordUpdateBatchId = crypto.randomUUID();
529
566
  yield Promise.all(wallets.map((wallet) => __awaiter(this, void 0, void 0, function* () {
530
- var _b;
567
+ var _b, _c;
531
568
  const signedSessionId = yield this.getSignedSessionId();
532
569
  yield walletClient.setPassword({
533
570
  accountAddress: wallet.accountAddress,
534
571
  authToken: (_b = this.getAuthToken) === null || _b === void 0 ? void 0 : _b.call(this),
535
572
  newPassword,
536
573
  passwordUpdateBatchId,
574
+ sessionPublicKey: yield ((_c = this.getSessionPublicKey) === null || _c === void 0 ? void 0 : _c.call(this)),
537
575
  signedSessionId,
538
576
  });
539
577
  })));
@@ -608,9 +646,18 @@ const withDynamicWaas = (BaseClass) => {
608
646
  }
609
647
  endSession(reason) {
610
648
  return __awaiter(this, void 0, void 0, function* () {
611
- const waasClient = yield this.getWaasWalletClient();
612
- if (!waasClient) {
613
- return;
649
+ // Building the wallet client requires an auth token (createDynamicWaasClient
650
+ // throws in header auth mode when none is present); the new scope guard in
651
+ // getWaasWalletClient also throws during pending-auth (MFA/KYC/device). On
652
+ // logout we must not let either throw abort logout — the security-critical
653
+ // key share removal below does not need the client. Only the iframe cleanup
654
+ // does, so we proceed without it if the client can't be obtained.
655
+ let waasClient;
656
+ try {
657
+ waasClient = yield this.getWaasWalletClient();
658
+ }
659
+ catch (error) {
660
+ this.instrument('[endSession] getWaasWalletClient failed; proceeding', Object.assign({ key: 'endSession-getClient-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
614
661
  }
615
662
  // When a session token expires, we preserve key shares in storage instead
616
663
  // of clearing them. Customers with short-lived sessions (minutes) were
@@ -642,9 +689,13 @@ const withDynamicWaas = (BaseClass) => {
642
689
  // iframe cleanup's request-channel timeout. The iframe handler already
643
690
  // swallows its own errors, and the DOM teardown obsoletes anything it
644
691
  // misses. Promise.resolve guards against a non-promise return in tests.
645
- void Promise.resolve(waasClient.cleanup()).catch((error) => {
646
- this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
647
- });
692
+ // Skipped when the client could not be built/obtained (no token, or
693
+ // pending-auth scope guard) there is no iframe to tear down then.
694
+ if (waasClient) {
695
+ void Promise.resolve(waasClient.cleanup()).catch((error) => {
696
+ this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
697
+ });
698
+ }
648
699
  }
649
700
  this.dynamicWaasClient = undefined;
650
701
  });
@@ -751,6 +802,11 @@ const withDynamicWaas = (BaseClass) => {
751
802
  }
752
803
  });
753
804
  }
805
+ getPrivateBalance() {
806
+ return __awaiter(this, void 0, void 0, function* () {
807
+ throw new DynamicError(`getPrivateBalance is not supported for ${this.chainName} wallets`);
808
+ });
809
+ }
754
810
  }
755
811
  return DynamicWaasMixin;
756
812
  };
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var sdkApiCore = require('@dynamic-labs/sdk-api-core');
7
+
8
+ // Scopes that mean the user has not finished authenticating (MFA / KYC /
9
+ // device registration still pending). A JWT carrying any of these has not
10
+ // upgraded to `user:basic`, and the WaaS backend rejects calls made with it
11
+ // (e.g. createRooms) with a 401.
12
+ //
13
+ // Note `user:basic` and `waasBackupToken` are intentionally NOT in this list:
14
+ // backup / recovery flows legitimately run with a `waasBackupToken` scope, so
15
+ // only the pending-auth scopes below should block WaaS client creation.
16
+ const PENDING_AUTH_SCOPES = [
17
+ sdkApiCore.JwtScope.RequiresAdditionalAuth,
18
+ sdkApiCore.JwtScope.UserDataForm,
19
+ sdkApiCore.JwtScope.Deviceregister,
20
+ ];
21
+ const decodeScope = (authToken) => {
22
+ const payload = authToken === null || authToken === void 0 ? void 0 : authToken.split('.')[1];
23
+ if (!payload) {
24
+ return undefined;
25
+ }
26
+ try {
27
+ return JSON.parse(atob(payload)).scope;
28
+ }
29
+ catch (_a) {
30
+ return undefined;
31
+ }
32
+ };
33
+ /**
34
+ * Returns true when the auth token still carries a pending-authentication
35
+ * scope (`requiresAdditionalAuth` / `userDataForm` / `device:register`).
36
+ *
37
+ * The WaaS iframe's API client is a shared, mutable axios instance. The token
38
+ * captured at client construction is what the init-time room-cache pre-warm
39
+ * `createRooms` uses — it runs before any per-operation token resync. So if the
40
+ * client is built during this pending window, that pre-warm fires with the
41
+ * stale pre-`user:basic` token and 401s; the iframe reports the 401 back to the
42
+ * host, which force-logs-out the user. Callers must therefore refuse to build
43
+ * the client until the token clears (the vulnerable window is construction +
44
+ * init, not the whole client lifetime).
45
+ *
46
+ * A token with no decodable scope is treated as not-pending (returns false):
47
+ * the backend remains the source of truth and will reject anything invalid.
48
+ */
49
+ const tokenHasPendingAuthScope = (authToken) => {
50
+ var _a, _b;
51
+ const scopes = (_b = (_a = decodeScope(authToken)) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0 ? _b : [];
52
+ return scopes.some((scope) => PENDING_AUTH_SCOPES.includes(scope));
53
+ };
54
+
55
+ exports.tokenHasPendingAuthScope = tokenHasPendingAuthScope;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns true when the auth token still carries a pending-authentication
3
+ * scope (`requiresAdditionalAuth` / `userDataForm` / `device:register`).
4
+ *
5
+ * The WaaS iframe's API client is a shared, mutable axios instance. The token
6
+ * captured at client construction is what the init-time room-cache pre-warm
7
+ * `createRooms` uses — it runs before any per-operation token resync. So if the
8
+ * client is built during this pending window, that pre-warm fires with the
9
+ * stale pre-`user:basic` token and 401s; the iframe reports the 401 back to the
10
+ * host, which force-logs-out the user. Callers must therefore refuse to build
11
+ * the client until the token clears (the vulnerable window is construction +
12
+ * init, not the whole client lifetime).
13
+ *
14
+ * A token with no decodable scope is treated as not-pending (returns false):
15
+ * the backend remains the source of truth and will reject anything invalid.
16
+ */
17
+ export declare const tokenHasPendingAuthScope: (authToken?: string) => boolean;
@@ -0,0 +1,51 @@
1
+ 'use client'
2
+ import { JwtScope } from '@dynamic-labs/sdk-api-core';
3
+
4
+ // Scopes that mean the user has not finished authenticating (MFA / KYC /
5
+ // device registration still pending). A JWT carrying any of these has not
6
+ // upgraded to `user:basic`, and the WaaS backend rejects calls made with it
7
+ // (e.g. createRooms) with a 401.
8
+ //
9
+ // Note `user:basic` and `waasBackupToken` are intentionally NOT in this list:
10
+ // backup / recovery flows legitimately run with a `waasBackupToken` scope, so
11
+ // only the pending-auth scopes below should block WaaS client creation.
12
+ const PENDING_AUTH_SCOPES = [
13
+ JwtScope.RequiresAdditionalAuth,
14
+ JwtScope.UserDataForm,
15
+ JwtScope.Deviceregister,
16
+ ];
17
+ const decodeScope = (authToken) => {
18
+ const payload = authToken === null || authToken === void 0 ? void 0 : authToken.split('.')[1];
19
+ if (!payload) {
20
+ return undefined;
21
+ }
22
+ try {
23
+ return JSON.parse(atob(payload)).scope;
24
+ }
25
+ catch (_a) {
26
+ return undefined;
27
+ }
28
+ };
29
+ /**
30
+ * Returns true when the auth token still carries a pending-authentication
31
+ * scope (`requiresAdditionalAuth` / `userDataForm` / `device:register`).
32
+ *
33
+ * The WaaS iframe's API client is a shared, mutable axios instance. The token
34
+ * captured at client construction is what the init-time room-cache pre-warm
35
+ * `createRooms` uses — it runs before any per-operation token resync. So if the
36
+ * client is built during this pending window, that pre-warm fires with the
37
+ * stale pre-`user:basic` token and 401s; the iframe reports the 401 back to the
38
+ * host, which force-logs-out the user. Callers must therefore refuse to build
39
+ * the client until the token clears (the vulnerable window is construction +
40
+ * init, not the whole client lifetime).
41
+ *
42
+ * A token with no decodable scope is treated as not-pending (returns false):
43
+ * the backend remains the source of truth and will reject anything invalid.
44
+ */
45
+ const tokenHasPendingAuthScope = (authToken) => {
46
+ var _a, _b;
47
+ const scopes = (_b = (_a = decodeScope(authToken)) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0 ? _b : [];
48
+ return scopes.some((scope) => PENDING_AUTH_SCOPES.includes(scope));
49
+ };
50
+
51
+ export { tokenHasPendingAuthScope };