@dynamic-labs-wallet/browser 0.0.0-pr384.2 → 0.0.0-pr534.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 (42) hide show
  1. package/index.cjs.js +719 -341
  2. package/index.esm.js +714 -344
  3. package/internal/core/bip340.cjs +1 -0
  4. package/internal/core/bip340.d.ts +1 -1
  5. package/internal/core/common.cjs +1 -0
  6. package/internal/core/common.d.ts +1 -0
  7. package/internal/core/ecdsa.cjs +1 -0
  8. package/internal/core/ecdsa.d.ts +1 -1
  9. package/internal/core/ed25519.cjs +1 -0
  10. package/internal/core/ed25519_exportable.cjs +1 -0
  11. package/internal/core/ed25519_exportable.d.ts +0 -1
  12. package/internal/core/index.cjs +1 -0
  13. package/internal/core/native.d.ts +2 -2
  14. package/internal/core/sr25519.cjs +1 -0
  15. package/internal/core/types.cjs +1 -0
  16. package/internal/web/generated/libmpc_executor.d.ts +30 -30
  17. package/internal/web/generated/libmpc_executor.js +2 -2
  18. package/internal/web/generated/libmpc_executor_bg.wasm +0 -0
  19. package/internal/web/index.d.ts +1 -1
  20. package/internal/web/index.js +1 -1
  21. package/package.json +4 -4
  22. package/src/client.d.ts +99 -34
  23. package/src/client.d.ts.map +1 -1
  24. package/src/constants.d.ts +2 -0
  25. package/src/constants.d.ts.map +1 -1
  26. package/src/services/encryption.d.ts +19 -0
  27. package/src/services/encryption.d.ts.map +1 -0
  28. package/src/types.d.ts +36 -1
  29. package/src/types.d.ts.map +1 -1
  30. package/src/utils.d.ts +8 -2
  31. package/src/utils.d.ts.map +1 -1
  32. package/internal/core/bip340.js +0 -1
  33. package/internal/core/common.js +0 -1
  34. package/internal/core/ecdsa.js +0 -1
  35. package/internal/core/ed25519.js +0 -1
  36. package/internal/core/ed25519_exportable.js +0 -1
  37. package/internal/core/index.js +0 -1
  38. package/internal/core/package.json +0 -17
  39. package/internal/core/sr25519.js +0 -1
  40. package/internal/core/types.js +0 -1
  41. package/internal/web/package.json +0 -17
  42. /package/internal/core/{native.js → native.cjs} +0 -0
package/index.cjs.js CHANGED
@@ -5,10 +5,10 @@ var web = require('#internal/web');
5
5
  var semver = require('semver');
6
6
  var uuid = require('uuid');
7
7
  var logger$1 = require('@dynamic-labs/logger');
8
+ var sdkApiCore = require('@dynamic-labs/sdk-api-core');
8
9
  var loadArgon2idWasm = require('argon2id');
9
10
  var axios = require('axios');
10
11
  var createHttpError = require('http-errors');
11
- var sdkApiCore = require('@dynamic-labs/sdk-api-core');
12
12
 
13
13
  function _extends() {
14
14
  _extends = Object.assign || function assign(target) {
@@ -463,10 +463,13 @@ const SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE = {
463
463
  WalletKit: '4.25.4',
464
464
  ClientSDK: '0.1.0-alpha.0'
465
465
  };
466
+ const ROOM_EXPIRATION_TIME = 1000 * 60 * 10; // 10 minutes
467
+ const ROOM_CACHE_COUNT = 5;
466
468
 
467
469
  const isBrowser = ()=>typeof window !== 'undefined';
468
- const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress })=>{
469
- return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}.json`;
470
+ const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress, isGoogleDrive = false })=>{
471
+ const suffix = isGoogleDrive ? '-google-drive' : '';
472
+ return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}${suffix}.json`;
470
473
  };
471
474
  const getClientKeyShareBackupInfo = (params)=>{
472
475
  var _params_walletProperties, _params_walletProperties_keyShares_;
@@ -475,7 +478,8 @@ const getClientKeyShareBackupInfo = (params)=>{
475
478
  [core.BackupLocation.GOOGLE_DRIVE]: [],
476
479
  [core.BackupLocation.ICLOUD]: [],
477
480
  [core.BackupLocation.USER]: [],
478
- [core.BackupLocation.EXTERNAL]: []
481
+ [core.BackupLocation.EXTERNAL]: [],
482
+ [core.BackupLocation.DELEGATED]: []
479
483
  };
480
484
  if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
481
485
  return {
@@ -605,6 +609,19 @@ const createBackupData = ({ encryptedKeyShares, accountAddress, thresholdSignatu
605
609
  }
606
610
  };
607
611
  };
612
+ const downloadStringAsFile = ({ filename, content, mimeType = 'application/json' })=>{
613
+ const blob = new Blob([
614
+ content
615
+ ], {
616
+ type: mimeType
617
+ });
618
+ const url = URL.createObjectURL(blob);
619
+ const a = document.createElement('a');
620
+ a.href = url;
621
+ a.download = filename;
622
+ a.click();
623
+ URL.revokeObjectURL(url);
624
+ };
608
625
 
609
626
  /**
610
627
  * Uploads a backup to Google Drive App
@@ -651,6 +668,84 @@ const createBackupData = ({ encryptedKeyShares, accountAddress, thresholdSignatu
651
668
  }
652
669
  };
653
670
 
671
+ const ALG_LABEL_RSA = 'HYBRID-RSA-AES-256';
672
+ /**
673
+ * Convert base64 to base64url encoding
674
+ */ const toBase64Url = (buffer)=>{
675
+ const base64 = Buffer.from(buffer).toString('base64');
676
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
677
+ };
678
+ /**
679
+ * Convert ArrayBuffer to base64url
680
+ */ const arrayBufferToBase64Url = (buffer)=>{
681
+ return toBase64Url(buffer);
682
+ };
683
+ /**
684
+ * Import RSA public key from PEM format
685
+ */ const importRSAPublicKey = async (publicKeyPem)=>{
686
+ // Remove PEM headers and decode base64
687
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
688
+ const pemFooter = '-----END PUBLIC KEY-----';
689
+ const pemContents = publicKeyPem.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, '');
690
+ const binaryDer = Buffer.from(pemContents, 'base64').toString('binary');
691
+ const keyData = new Uint8Array(binaryDer.length);
692
+ for(let i = 0; i < binaryDer.length; i++){
693
+ keyData[i] = binaryDer.charCodeAt(i);
694
+ }
695
+ return await crypto.subtle.importKey('spki', keyData, {
696
+ name: 'RSA-OAEP',
697
+ hash: 'SHA-256'
698
+ }, false, [
699
+ 'encrypt'
700
+ ]);
701
+ };
702
+ // encodedEnvelopeBytes intentionally omitted; alg/ct/ek/iv/tag/kid is sufficient
703
+ /**
704
+ * Encrypts data using HYBRID-RSA-AES-256 encryption scheme with Web Crypto API.
705
+ * 1. Generate random AES-256 key
706
+ * 2. Encrypt AES key with RSA public key
707
+ * 3. Encrypt data with AES-256-GCM
708
+ */ const encryptDelegatedKeyShare = async (data, publicKeyPem, keyId)=>{
709
+ try {
710
+ // Step 1: Generate a random AES-256 key and 16-byte IV
711
+ const aesKey = await crypto.subtle.generateKey({
712
+ name: 'AES-GCM',
713
+ length: 256
714
+ }, true, [
715
+ 'encrypt'
716
+ ]);
717
+ const iv = crypto.getRandomValues(new Uint8Array(16)); // 128-bit IV for GCM
718
+ // Step 2: Encrypt the data with AES-256-GCM
719
+ const plaintext = new TextEncoder().encode(data);
720
+ const encryptedData = await crypto.subtle.encrypt({
721
+ name: 'AES-GCM',
722
+ iv: iv
723
+ }, aesKey, plaintext);
724
+ // Extract the auth tag from the encrypted data (last 16 bytes)
725
+ const encryptedDataArray = new Uint8Array(encryptedData);
726
+ const authTag = encryptedDataArray.slice(-16);
727
+ const ciphertext = encryptedDataArray.slice(0, -16);
728
+ // Step 3: Encrypt the AES key with RSA public key
729
+ const rsaPublicKey = await importRSAPublicKey(publicKeyPem);
730
+ // Export the AES key to encrypt it
731
+ const aesKeyData = await crypto.subtle.exportKey('raw', aesKey);
732
+ const encryptedAesKey = await crypto.subtle.encrypt({
733
+ name: 'RSA-OAEP'
734
+ }, rsaPublicKey, aesKeyData);
735
+ return _extends({
736
+ alg: ALG_LABEL_RSA,
737
+ iv: arrayBufferToBase64Url(iv.buffer),
738
+ ct: arrayBufferToBase64Url(ciphertext.buffer),
739
+ tag: arrayBufferToBase64Url(authTag.buffer),
740
+ ek: arrayBufferToBase64Url(encryptedAesKey)
741
+ }, keyId ? {
742
+ kid: keyId
743
+ } : {});
744
+ } catch (error) {
745
+ throw new Error(`Encryption failed: ${error instanceof Error ? error.message : String(error)}`);
746
+ }
747
+ };
748
+
654
749
  const localStorageWriteTest = {
655
750
  tested: false,
656
751
  writable: false
@@ -720,7 +815,61 @@ const localStorageWriteTest = {
720
815
  }
721
816
  });
722
817
 
818
+ const createDelegationWithGoogleDriveDistribution = ({ existingShares, delegatedShare })=>({
819
+ dynamicBackendShares: existingShares,
820
+ googleDriveShares: existingShares,
821
+ delegatedShare
822
+ });
823
+ const createDelegationOnlyDistribution = ({ existingShares, delegatedShare })=>({
824
+ dynamicBackendShares: existingShares,
825
+ googleDriveShares: [],
826
+ delegatedShare
827
+ });
828
+ const createGoogleDriveOnlyDistribution = ({ allShares })=>({
829
+ dynamicBackendShares: allShares.slice(0, -1),
830
+ googleDriveShares: allShares.slice(-1)
831
+ });
832
+ const createDynamicOnlyDistribution = ({ allShares })=>({
833
+ dynamicBackendShares: allShares,
834
+ googleDriveShares: []
835
+ });
836
+ const hasGoogleDriveBackup = (backupInfo)=>{
837
+ var _backupInfo_backups_BackupLocation_GOOGLE_DRIVE, _backupInfo_backups;
838
+ var _backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length;
839
+ return ((_backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length = backupInfo == null ? void 0 : (_backupInfo_backups = backupInfo.backups) == null ? void 0 : (_backupInfo_backups_BackupLocation_GOOGLE_DRIVE = _backupInfo_backups[core.BackupLocation.GOOGLE_DRIVE]) == null ? void 0 : _backupInfo_backups_BackupLocation_GOOGLE_DRIVE.length) != null ? _backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length : 0) > 0;
840
+ };
841
+ const hasDelegatedBackup = (backupInfo)=>{
842
+ var _backupInfo_backups_BackupLocation_DELEGATED, _backupInfo_backups;
843
+ var _backupInfo_backups_BackupLocation_DELEGATED_length;
844
+ return ((_backupInfo_backups_BackupLocation_DELEGATED_length = backupInfo == null ? void 0 : (_backupInfo_backups = backupInfo.backups) == null ? void 0 : (_backupInfo_backups_BackupLocation_DELEGATED = _backupInfo_backups[core.BackupLocation.DELEGATED]) == null ? void 0 : _backupInfo_backups_BackupLocation_DELEGATED.length) != null ? _backupInfo_backups_BackupLocation_DELEGATED_length : 0) > 0;
845
+ };
846
+ /**
847
+ * Distribution for adding Google Drive backup to an existing delegation.
848
+ * Client's shares go to both Dynamic and Google Drive.
849
+ * delegatedShare is undefined - we don't re-publish, but preserve the location.
850
+ */ const createAddGoogleDriveToExistingDelegationDistribution = ({ clientShares })=>({
851
+ dynamicBackendShares: clientShares,
852
+ googleDriveShares: clientShares
853
+ });
854
+
723
855
  class DynamicWalletClient {
856
+ async initializeForwardMPCClient() {
857
+ try {
858
+ await this.apiClient.forwardMPCClient.connect();
859
+ this.logger.info('Connected to ForwardMPC enclave websocket. Instance: ' + this.instanceId);
860
+ try {
861
+ await this.apiClient.forwardMPCClient.handshake();
862
+ this.logger.debug('Handshaked with ForwardMPC enclave websocket. Instance: ' + this.instanceId);
863
+ } catch (error) {
864
+ this.logger.error('Error handshaking with ForwardMPC enclave websocket. Instance: ' + this.instanceId, error);
865
+ }
866
+ } catch (error) {
867
+ this.logger.error('Error connecting to ForwardMPC enclave websocket. Instance: ' + this.instanceId, {
868
+ error,
869
+ environmentId: this.environmentId
870
+ });
871
+ }
872
+ }
724
873
  getAuthMode() {
725
874
  return this.authMode;
726
875
  }
@@ -792,30 +941,41 @@ class DynamicWalletClient {
792
941
  throw error;
793
942
  }
794
943
  }
795
- async initialize() {
944
+ async initialize(traceContext) {
796
945
  if (this.initializePromise) {
797
946
  return await this.initializePromise;
798
947
  }
799
948
  this.logger.debug('[DynamicWaasWalletClient] Initializing Dynamic Waas Wallet SDK');
800
- this.initializePromise = this._initialize();
949
+ this.initializePromise = this._initialize(traceContext);
801
950
  const result = await this.initializePromise;
802
951
  this.logger.debug('[DynamicWaasWalletClient] Dynamic Waas Wallet SDK initialized');
803
952
  return result;
804
953
  }
805
954
  /**
806
955
  * Client initialization logic
807
- */ async _initialize() {
956
+ */ async _initialize(traceContext) {
808
957
  try {
809
958
  const initializePromises = [
810
959
  this.restoreWallets()
811
960
  ];
961
+ if (this.featureFlags[core.FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] === true) {
962
+ // Restore rooms first, then create rooms if needed (in series, but not blocking other initialization)
963
+ initializePromises.push(this.restoreRooms().then(()=>// assume two of two threshold signature scheme
964
+ this.createRooms({
965
+ roomType: sdkApiCore.RoomTypeEnum.Threshold,
966
+ thresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_TWO,
967
+ roomCount: ROOM_CACHE_COUNT
968
+ })));
969
+ }
812
970
  await Promise.all(initializePromises);
813
971
  return {
814
- error: null
972
+ error: null,
973
+ traceContext
815
974
  };
816
975
  } catch (error) {
817
976
  return {
818
- error
977
+ error,
978
+ traceContext
819
979
  };
820
980
  }
821
981
  }
@@ -894,7 +1054,7 @@ class DynamicWalletClient {
894
1054
  clientKeygenResults
895
1055
  };
896
1056
  }
897
- async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
1057
+ async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete, traceContext }) {
898
1058
  const dynamicRequestId = uuid.v4();
899
1059
  try {
900
1060
  const clientKeygenInitResults = await this.clientInitializeKeyGen({
@@ -902,11 +1062,12 @@ class DynamicWalletClient {
902
1062
  thresholdSignatureScheme
903
1063
  });
904
1064
  const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
905
- this.logger.debug('[DynamicWaasWalletClient] Initialized client key generation', {
1065
+ this.instrument('[DynamicWaasWalletClient] Initialized client key generation', _extends({
906
1066
  clientKeygenIds: clientKeygenIds.join(', '),
907
1067
  chainName,
908
- thresholdSignatureScheme
909
- });
1068
+ thresholdSignatureScheme,
1069
+ key: 'keyGen'
1070
+ }, this.getTraceContext(traceContext)));
910
1071
  const { roomId, serverKeygenIds } = await this.serverInitializeKeyGen({
911
1072
  chainName,
912
1073
  clientKeygenIds,
@@ -914,12 +1075,14 @@ class DynamicWalletClient {
914
1075
  thresholdSignatureScheme,
915
1076
  onCeremonyComplete
916
1077
  });
917
- this.logger.debug('[DynamicWaasWalletClient] Server key generation initialized', {
1078
+ this.instrument('[DynamicWaasWalletClient] Server key generation initialized', _extends({
918
1079
  roomId,
919
1080
  clientKeygenIds: clientKeygenIds.join(', '),
920
1081
  serverKeygenIds: serverKeygenIds.join(', '),
921
- chainName
922
- });
1082
+ chainName,
1083
+ key: 'keyGen',
1084
+ operation: 'serverKeyGen'
1085
+ }, this.getTraceContext(traceContext)));
923
1086
  const { rawPublicKey, clientKeygenResults: clientKeyShares } = await this.clientKeyGen({
924
1087
  chainName,
925
1088
  roomId,
@@ -927,15 +1090,17 @@ class DynamicWalletClient {
927
1090
  clientKeygenInitResults,
928
1091
  thresholdSignatureScheme
929
1092
  });
930
- this.logger.debug('[DynamicWaasWalletClient] Client key generation completed', {
1093
+ this.instrument('[DynamicWaasWalletClient] Client key generation completed', _extends({
931
1094
  roomId,
932
1095
  serverKeygenIds: serverKeygenIds.join(', '),
933
1096
  clientKeygenIds: clientKeygenIds.join(', '),
934
1097
  chainName,
935
1098
  thresholdSignatureScheme,
936
1099
  rawPublicKey,
937
- clientKeySharesCount: clientKeyShares.length
938
- });
1100
+ clientKeySharesCount: clientKeyShares.length,
1101
+ key: 'keyGen',
1102
+ operation: 'clientKeyGen'
1103
+ }, this.getTraceContext(traceContext)));
939
1104
  return {
940
1105
  rawPublicKey,
941
1106
  clientKeyShares
@@ -944,16 +1109,16 @@ class DynamicWalletClient {
944
1109
  logError({
945
1110
  message: 'Error in keyGen',
946
1111
  error: error,
947
- context: {
1112
+ context: _extends({
948
1113
  chainName,
949
1114
  thresholdSignatureScheme,
950
1115
  dynamicRequestId
951
- }
1116
+ }, this.getTraceContext(traceContext))
952
1117
  });
953
1118
  throw error;
954
1119
  }
955
1120
  }
956
- async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete }) {
1121
+ async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete, traceContext }) {
957
1122
  const dynamicRequestId = uuid.v4();
958
1123
  try {
959
1124
  const mpcSigner = getMPCSigner({
@@ -965,11 +1130,13 @@ class DynamicWalletClient {
965
1130
  thresholdSignatureScheme
966
1131
  });
967
1132
  const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
968
- this.logger.debug('[DynamicWaasWalletClient] Client key generation initialized', {
1133
+ this.instrument('[DynamicWaasWalletClient] Client key generation initialized', _extends({
969
1134
  clientKeygenIds: clientKeygenIds.join(', '),
970
1135
  chainName,
971
- thresholdSignatureScheme
972
- });
1136
+ thresholdSignatureScheme,
1137
+ key: 'importRawPrivateKey',
1138
+ operation: 'clientKeyGen'
1139
+ }, this.getTraceContext(traceContext)));
973
1140
  const { roomId, serverKeygenIds } = await this.apiClient.importPrivateKey({
974
1141
  chainName,
975
1142
  clientKeygenIds,
@@ -978,12 +1145,14 @@ class DynamicWalletClient {
978
1145
  onError,
979
1146
  onCeremonyComplete
980
1147
  });
981
- this.logger.debug('[DynamicWaasWalletClient] Server key generation initialized', {
1148
+ this.instrument('[DynamicWaasWalletClient] Server key generation initialized', _extends({
982
1149
  roomId,
983
1150
  clientKeygenIds: clientKeygenIds.join(', '),
984
1151
  serverKeygenIds: serverKeygenIds.join(', '),
985
- chainName
986
- });
1152
+ chainName,
1153
+ key: 'importRawPrivateKey',
1154
+ operation: 'serverImportPrivateKey'
1155
+ }, this.getTraceContext(traceContext)));
987
1156
  const { threshold } = core.getTSSConfig(thresholdSignatureScheme);
988
1157
  const clientKeygenResults = await Promise.all(clientKeygenInitResults.map(async (currentInit, index)=>{
989
1158
  const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
@@ -1008,14 +1177,16 @@ class DynamicWalletClient {
1008
1177
  keyShare: clientKeygenResult,
1009
1178
  derivationPath: undefined
1010
1179
  });
1011
- this.logger.debug('[DynamicWaasWalletClient] Completed import of raw private key', {
1180
+ this.instrument('[DynamicWaasWalletClient] Completed import of raw private key', _extends({
1012
1181
  rawPublicKey,
1013
1182
  chainName,
1014
1183
  thresholdSignatureScheme,
1015
1184
  roomId,
1016
1185
  serverKeygenIds: serverKeygenIds.join(', '),
1017
- clientKeygenIds: clientKeygenIds.join(', ')
1018
- });
1186
+ clientKeygenIds: clientKeygenIds.join(', '),
1187
+ key: 'importRawPrivateKey',
1188
+ operation: 'importRawPrivateKey'
1189
+ }, this.getTraceContext(traceContext)));
1019
1190
  return {
1020
1191
  rawPublicKey,
1021
1192
  clientKeyShares: clientKeygenResults
@@ -1024,16 +1195,16 @@ class DynamicWalletClient {
1024
1195
  logError({
1025
1196
  message: 'Error in importRawPrivateKey',
1026
1197
  error: error,
1027
- context: {
1198
+ context: _extends({
1028
1199
  chainName,
1029
1200
  thresholdSignatureScheme,
1030
1201
  dynamicRequestId
1031
- }
1202
+ }, this.getTraceContext(traceContext))
1032
1203
  });
1033
1204
  throw error;
1034
1205
  }
1035
1206
  }
1036
- async serverSign({ walletId, message, isFormatted, mfaToken, context, onError, dynamicRequestId }) {
1207
+ async serverSign({ walletId, message, isFormatted, mfaToken, roomId, context, onError, dynamicRequestId, traceContext }) {
1037
1208
  // Create the room and sign the message
1038
1209
  if (typeof message !== 'string') {
1039
1210
  message = `0x${Buffer.from(message).toString('hex')}`;
@@ -1044,44 +1215,101 @@ class DynamicWalletClient {
1044
1215
  isFormatted,
1045
1216
  dynamicRequestId,
1046
1217
  mfaToken,
1218
+ roomId,
1047
1219
  context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined,
1048
- onError
1220
+ onError,
1221
+ forwardMPCClientEnabled: this.forwardMPCEnabled,
1222
+ traceContext
1049
1223
  });
1050
1224
  return data;
1051
1225
  }
1052
- async clientSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, dynamicRequestId }) {
1226
+ async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext }) {
1227
+ try {
1228
+ if (!this.apiClient.forwardMPCClient.connected) {
1229
+ await this.initializeForwardMPCClient();
1230
+ }
1231
+ const messageForForwardMPC = core.serializeMessageForForwardMPC({
1232
+ message,
1233
+ isFormatted,
1234
+ chainName
1235
+ });
1236
+ this.logger.info('Forward MPC enabled, signing message with forward MPC', this.getTraceContext(traceContext));
1237
+ const signature = await this.apiClient.forwardMPCClient.signMessage({
1238
+ keyshare: keyShare,
1239
+ message: chainName === 'SVM' ? formattedMessage : messageForForwardMPC,
1240
+ relayDomain: this.baseMPCRelayApiUrl || '',
1241
+ signingAlgo: chainName === 'EVM' ? 'ECDSA' : 'ED25519',
1242
+ hashAlgo: chainName === 'EVM' && !isFormatted ? 'keccak256' : undefined,
1243
+ derivationPath: derivationPath,
1244
+ roomUuid: roomId
1245
+ });
1246
+ const signatureBytes = signature.data.signature;
1247
+ if (!(signatureBytes instanceof Uint8Array)) {
1248
+ throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
1249
+ }
1250
+ // Convert to EcdsaSignature
1251
+ if (chainName === 'EVM') {
1252
+ const ecdsaSignature = web.EcdsaSignature.fromBuffer(signatureBytes);
1253
+ return ecdsaSignature;
1254
+ } else {
1255
+ return signatureBytes;
1256
+ }
1257
+ } catch (error) {
1258
+ this.logger.error('Error signing message with forward MPC client', {
1259
+ error,
1260
+ environmentId: this.environmentId,
1261
+ userId: this.userId,
1262
+ dynamicRequestId
1263
+ });
1264
+ throw error;
1265
+ }
1266
+ }
1267
+ async clientSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext }) {
1053
1268
  try {
1054
1269
  const mpcSigner = getMPCSigner({
1055
1270
  chainName,
1056
1271
  baseRelayUrl: this.baseMPCRelayApiUrl
1057
1272
  });
1058
1273
  const formattedMessage = isFormatted ? new web.MessageHash(message) : formatMessage(chainName, message);
1059
- this.logger.debug('[DynamicWaasWalletClient] Starting client sign', {
1274
+ this.logger.debug('[DynamicWaasWalletClient] Starting client sign', _extends({
1060
1275
  chainName,
1061
1276
  message,
1062
1277
  roomId,
1063
1278
  derivationPath,
1064
1279
  isFormatted
1065
- });
1280
+ }, this.getTraceContext(traceContext)));
1281
+ if (this.forwardMPCEnabled) {
1282
+ return this.forwardMPCClientSign({
1283
+ chainName,
1284
+ message,
1285
+ roomId,
1286
+ keyShare,
1287
+ derivationPath,
1288
+ formattedMessage,
1289
+ dynamicRequestId,
1290
+ isFormatted,
1291
+ traceContext
1292
+ });
1293
+ }
1066
1294
  const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
1067
1295
  return signature;
1068
1296
  } catch (error) {
1069
1297
  logError({
1070
1298
  message: 'Error in clientSign',
1071
1299
  error: error,
1072
- context: {
1300
+ context: _extends({
1073
1301
  chainName,
1074
1302
  roomId,
1075
1303
  derivationPath,
1076
1304
  isFormatted,
1077
1305
  dynamicRequestId
1078
- }
1306
+ }, this.getTraceContext(traceContext))
1079
1307
  });
1080
1308
  throw error;
1081
1309
  }
1082
1310
  }
1083
1311
  //todo: need to modify with imported flag
1084
- async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError }) {
1312
+ async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError, traceContext }) {
1085
1313
  const dynamicRequestId = uuid.v4();
1086
1314
  try {
1087
1315
  await this.verifyPassword({
@@ -1096,23 +1324,44 @@ class DynamicWalletClient {
1096
1324
  walletOperation: core.WalletOperation.SIGN_MESSAGE,
1097
1325
  signedSessionId
1098
1326
  });
1099
- // Perform the server sign
1100
- const data = await this.serverSign({
1327
+ let roomId;
1328
+ if (this.featureFlags[core.FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] === true) {
1329
+ const room = await this.getRoom(sdkApiCore.RoomTypeEnum.Threshold, wallet.thresholdSignatureScheme);
1330
+ roomId = room == null ? void 0 : room.roomId;
1331
+ if (roomId) {
1332
+ this.instrument('[DynamicWaasWalletClient] Using cached room', _extends({
1333
+ key: 'sign',
1334
+ operation: 'cachedRoomFound',
1335
+ accountAddress,
1336
+ chainName,
1337
+ walletId: wallet.walletId,
1338
+ roomId
1339
+ }, this.getTraceContext(traceContext)));
1340
+ }
1341
+ }
1342
+ // Perform the server sign - always call, but only await if roomId is not provided
1343
+ const serverSignPromise = this.serverSign({
1101
1344
  walletId: wallet.walletId,
1102
1345
  message,
1103
1346
  isFormatted,
1104
1347
  mfaToken,
1348
+ roomId,
1105
1349
  context,
1106
1350
  onError,
1107
- dynamicRequestId
1351
+ dynamicRequestId,
1352
+ traceContext
1108
1353
  });
1109
- this.logger.debug('[DynamicWaasWalletClient] Server sign completed', {
1354
+ // Only await if roomId is not provided
1355
+ roomId = roomId != null ? roomId : (await serverSignPromise).roomId;
1356
+ this.instrument('[DynamicWaasWalletClient] Server sign completed', _extends({
1110
1357
  message,
1111
1358
  accountAddress,
1112
1359
  walletId: wallet.walletId,
1113
- roomId: data.roomId,
1114
- dynamicRequestId
1115
- });
1360
+ roomId,
1361
+ dynamicRequestId,
1362
+ key: 'sign',
1363
+ operation: 'serverSign'
1364
+ }, this.getTraceContext(traceContext)));
1116
1365
  const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
1117
1366
  // Perform the client sign and return the signature
1118
1367
  const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
@@ -1121,35 +1370,39 @@ class DynamicWalletClient {
1121
1370
  const signature = await this.clientSign({
1122
1371
  chainName,
1123
1372
  message,
1124
- roomId: data.roomId,
1373
+ roomId,
1125
1374
  keyShare: clientKeyShares[0],
1126
1375
  derivationPath,
1127
1376
  isFormatted,
1128
- dynamicRequestId
1377
+ dynamicRequestId,
1378
+ traceContext
1129
1379
  });
1130
- this.logger.debug('[DynamicWaasWalletClient] Client sign completed', {
1380
+ this.instrument('[DynamicWaasWalletClient] Client sign completed', _extends({
1381
+ accountAddress,
1131
1382
  chainName,
1132
1383
  message,
1133
- roomId: data.roomId,
1384
+ roomId,
1134
1385
  derivationPath,
1135
- isFormatted
1136
- });
1386
+ isFormatted,
1387
+ key: 'sign',
1388
+ operation: 'clientSign'
1389
+ }, this.getTraceContext(traceContext)));
1137
1390
  return signature;
1138
1391
  } catch (error) {
1139
1392
  logError({
1140
1393
  message: 'Error in sign',
1141
1394
  error: error,
1142
- context: {
1395
+ context: _extends({
1143
1396
  accountAddress,
1144
1397
  chainName,
1145
1398
  isFormatted: isFormatted ? 'true' : 'false',
1146
1399
  dynamicRequestId
1147
- }
1400
+ }, this.getTraceContext(traceContext))
1148
1401
  });
1149
1402
  throw error;
1150
1403
  }
1151
1404
  }
1152
- async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken }) {
1405
+ async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
1153
1406
  const dynamicRequestId = uuid.v4();
1154
1407
  try {
1155
1408
  await this.verifyPassword({
@@ -1193,11 +1446,11 @@ class DynamicWalletClient {
1193
1446
  logError({
1194
1447
  message: 'Error in refreshWalletAccountShares',
1195
1448
  error: error,
1196
- context: {
1449
+ context: _extends({
1197
1450
  accountAddress,
1198
1451
  chainName,
1199
1452
  dynamicRequestId
1200
- }
1453
+ }, this.getTraceContext(traceContext))
1201
1454
  });
1202
1455
  throw error;
1203
1456
  }
@@ -1253,7 +1506,7 @@ class DynamicWalletClient {
1253
1506
  existingClientKeyShares
1254
1507
  };
1255
1508
  }
1256
- async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, backupToGoogleDrive = false, delegateToProjectEnvironment = false, mfaToken }) {
1509
+ async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, backupToGoogleDrive = false, delegateToProjectEnvironment = false, mfaToken, revokeDelegation = false }) {
1257
1510
  const dynamicRequestId = uuid.v4();
1258
1511
  try {
1259
1512
  await this.verifyPassword({
@@ -1292,7 +1545,8 @@ class DynamicWalletClient {
1292
1545
  newThresholdSignatureScheme,
1293
1546
  dynamicRequestId,
1294
1547
  delegateToProjectEnvironment,
1295
- mfaToken
1548
+ mfaToken,
1549
+ revokeDelegation
1296
1550
  });
1297
1551
  const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
1298
1552
  // Get the MPC config for the threshold signature scheme
@@ -1307,35 +1561,68 @@ class DynamicWalletClient {
1307
1561
  chainName,
1308
1562
  baseRelayUrl: this.baseMPCRelayApiUrl
1309
1563
  });
1310
- const reshareResults = await Promise.all([
1311
- ...newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
1312
- ...existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
1564
+ const existingResharePromises = existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds));
1565
+ const newResharePromises = newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds));
1566
+ // Run both share parties in parallel by group
1567
+ const [existingReshareResults, newReshareResults] = await Promise.all([
1568
+ Promise.all(existingResharePromises),
1569
+ Promise.all(newResharePromises)
1313
1570
  ]);
1571
+ const clientKeysharesToLocalStorage = delegateToProjectEnvironment ? [
1572
+ ...existingReshareResults
1573
+ ] : [
1574
+ ...existingReshareResults,
1575
+ ...newReshareResults
1576
+ ];
1314
1577
  this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1315
1578
  thresholdSignatureScheme: newThresholdSignatureScheme
1316
1579
  });
1580
+ // store client key shares to localStorage
1317
1581
  await this.setClientKeySharesToLocalStorage({
1318
1582
  accountAddress,
1319
- clientKeyShares: reshareResults,
1583
+ clientKeyShares: clientKeysharesToLocalStorage,
1320
1584
  overwriteOrMerge: 'overwrite'
1321
1585
  });
1322
- if (delegateToProjectEnvironment) {
1323
- await this.storeEncryptedBackupByDelegatedWallet({
1324
- accountAddress,
1325
- password,
1326
- signedSessionId
1586
+ const allClientShares = [
1587
+ ...existingReshareResults,
1588
+ ...newReshareResults
1589
+ ];
1590
+ let distribution;
1591
+ if (delegateToProjectEnvironment && backupToGoogleDrive) {
1592
+ // Delegation + Google Drive: Client's existing share backs up to both Dynamic and Google Drive.
1593
+ // The new share goes to the webhook for delegation.
1594
+ distribution = createDelegationWithGoogleDriveDistribution({
1595
+ existingShares: existingReshareResults,
1596
+ delegatedShare: newReshareResults[0]
1597
+ });
1598
+ } else if (delegateToProjectEnvironment) {
1599
+ // Delegation only: Client's existing share backs up to Dynamic.
1600
+ // The new share goes to the webhook for delegation. No Google Drive backup.
1601
+ distribution = createDelegationOnlyDistribution({
1602
+ existingShares: existingReshareResults,
1603
+ delegatedShare: newReshareResults[0]
1604
+ });
1605
+ } else if (backupToGoogleDrive) {
1606
+ // Google Drive only: Split shares between Dynamic (N-1) and Google Drive (1).
1607
+ // The last share (new share) goes to Google Drive.
1608
+ distribution = createGoogleDriveOnlyDistribution({
1609
+ allShares: allClientShares
1327
1610
  });
1328
1611
  } else {
1329
- await this.storeEncryptedBackupByWallet({
1330
- accountAddress,
1331
- password,
1332
- signedSessionId,
1333
- backupToGoogleDrive
1612
+ // No delegation, no Google Drive: All shares go to Dynamic backend only.
1613
+ distribution = createDynamicOnlyDistribution({
1614
+ allShares: allClientShares
1334
1615
  });
1335
1616
  }
1617
+ await this.backupSharesWithDistribution({
1618
+ accountAddress,
1619
+ password,
1620
+ signedSessionId,
1621
+ distribution
1622
+ });
1336
1623
  } catch (error) {
1337
1624
  logError({
1338
- message: 'Error in reshare',
1625
+ message: 'Error in reshare, resetting wallet to previous state',
1339
1626
  error: error,
1340
1627
  context: {
1341
1628
  accountAddress,
@@ -1346,52 +1633,25 @@ class DynamicWalletClient {
1346
1633
  dynamicRequestId
1347
1634
  }
1348
1635
  });
1349
- throw error;
1350
- }
1351
- }
1352
- async sendKeySharesToDelegatedAccess({ accountAddress, chainName, delegatedKeyShares, environmentId, userId, walletId }) {
1353
- try {
1354
- if (!this.delegatedAccessEndpoint) {
1355
- throw new Error('Cannot send key shares to delegated access because delegated access endpoint is not set');
1356
- }
1357
- const delegatedAccessEndpoint = this.delegatedAccessEndpoint;
1358
- const response = await fetch(delegatedAccessEndpoint, {
1359
- method: 'POST',
1360
- body: JSON.stringify({
1361
- chainName,
1362
- accountAddress,
1363
- delegatedKeyShares,
1364
- environmentId,
1365
- userId,
1366
- walletId
1367
- })
1636
+ // reset user wallet when reshare fails, this would allow the client to recover wallets from an active state
1637
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1638
+ thresholdSignatureScheme: oldThresholdSignatureScheme
1368
1639
  });
1369
- const data = await response.json();
1370
- return data;
1371
- } catch (error) {
1372
- logError({
1373
- message: 'Error in sendKeySharesToDelegatedAccess',
1374
- error: error,
1375
- context: {
1376
- accountAddress,
1377
- chainName
1378
- }
1640
+ await this.setClientKeySharesToLocalStorage({
1641
+ accountAddress,
1642
+ clientKeyShares: [],
1643
+ overwriteOrMerge: 'overwrite'
1379
1644
  });
1380
1645
  throw error;
1381
1646
  }
1382
1647
  }
1383
- async delegateKeyShares({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
1648
+ async performDelegationOperation({ accountAddress, password, signedSessionId, mfaToken, newThresholdSignatureScheme, revokeDelegation = false, operationName }) {
1384
1649
  try {
1650
+ var _this_walletMap_accountAddress;
1385
1651
  const delegateToProjectEnvironment = this.featureFlags && this.featureFlags[core.FEATURE_FLAGS.ENABLE_DELEGATED_KEY_SHARES_FLAG] === true;
1386
1652
  if (!delegateToProjectEnvironment) {
1387
1653
  throw new Error('Delegation is not allowed for this project environment');
1388
1654
  }
1389
- const environmentSettings = await this.apiClient.getEnvironmentSettings();
1390
- const delegatedAccessEndpoint = environmentSettings.sdk.waas.delegatedAccessEndpoint;
1391
- if (!delegatedAccessEndpoint) {
1392
- throw new Error('Cannot delegate key shares because verified access endpoint is not set in the environment settings');
1393
- }
1394
- this.delegatedAccessEndpoint = delegatedAccessEndpoint;
1395
1655
  const wallet = await this.getWallet({
1396
1656
  accountAddress,
1397
1657
  walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
@@ -1402,28 +1662,21 @@ class DynamicWalletClient {
1402
1662
  throw new Error('Delegation is not allowed for SUI');
1403
1663
  }
1404
1664
  const currentThresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
1405
- if (currentThresholdSignatureScheme === core.ThresholdSignatureScheme.TWO_OF_TWO) {
1406
- // Reshare to 2-of-3, which will automatically handle the backup distribution
1407
- await this.reshare({
1408
- chainName: this.walletMap[accountAddress].chainName,
1409
- accountAddress,
1410
- oldThresholdSignatureScheme: currentThresholdSignatureScheme,
1411
- newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
1412
- password,
1413
- signedSessionId,
1414
- backupToGoogleDrive: false,
1415
- delegateToProjectEnvironment: true,
1416
- mfaToken
1417
- });
1418
- const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
1419
- const delegatedKeyShares = backupInfo.backups[core.BackupLocation.EXTERNAL] || [];
1420
- return delegatedKeyShares;
1421
- } else {
1422
- throw new Error('Delegation is not allowed for this threshold signature scheme');
1423
- }
1665
+ await this.reshare({
1666
+ chainName: this.walletMap[accountAddress].chainName,
1667
+ accountAddress,
1668
+ oldThresholdSignatureScheme: currentThresholdSignatureScheme,
1669
+ newThresholdSignatureScheme,
1670
+ password,
1671
+ signedSessionId,
1672
+ backupToGoogleDrive: hasGoogleDriveBackup((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.clientKeySharesBackupInfo),
1673
+ delegateToProjectEnvironment: true,
1674
+ mfaToken,
1675
+ revokeDelegation
1676
+ });
1424
1677
  } catch (error) {
1425
1678
  logError({
1426
- message: 'Error in delegateKeyShares',
1679
+ message: `Error in ${operationName}`,
1427
1680
  error: error,
1428
1681
  context: {
1429
1682
  accountAddress
@@ -1432,7 +1685,31 @@ class DynamicWalletClient {
1432
1685
  throw error;
1433
1686
  }
1434
1687
  }
1435
- async exportKey({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken }) {
1688
+ async delegateKeyShares({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
1689
+ await this.performDelegationOperation({
1690
+ accountAddress,
1691
+ password,
1692
+ signedSessionId,
1693
+ mfaToken,
1694
+ newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
1695
+ operationName: 'delegateKeyShares'
1696
+ });
1697
+ const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
1698
+ const delegatedKeyShares = backupInfo.backups[core.BackupLocation.DELEGATED] || [];
1699
+ return delegatedKeyShares;
1700
+ }
1701
+ async revokeDelegation({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
1702
+ await this.performDelegationOperation({
1703
+ accountAddress,
1704
+ password,
1705
+ signedSessionId,
1706
+ mfaToken,
1707
+ newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_TWO,
1708
+ revokeDelegation: true,
1709
+ operationName: 'revokeDelegation'
1710
+ });
1711
+ }
1712
+ async exportKey({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
1436
1713
  const dynamicRequestId = uuid.v4();
1437
1714
  try {
1438
1715
  const wallet = await this.getWallet({
@@ -1458,24 +1735,24 @@ class DynamicWalletClient {
1458
1735
  dynamicRequestId,
1459
1736
  mfaToken
1460
1737
  });
1461
- this.logger.debug('[DynamicWaasWalletClient] Starting export of private key', {
1738
+ this.logger.debug('[DynamicWaasWalletClient] Starting export of private key', _extends({
1462
1739
  accountAddress,
1463
1740
  chainName,
1464
1741
  walletId: wallet.walletId,
1465
1742
  exportId,
1466
1743
  roomId: data.roomId
1467
- });
1744
+ }, this.getTraceContext(traceContext)));
1468
1745
  const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, clientKeyShares[0], exportId);
1469
1746
  if (!keyExportRaw) {
1470
1747
  throw new Error('Error exporting private key');
1471
1748
  }
1472
- this.logger.debug('[DynamicWaasWalletClient] Completed export of private key', {
1749
+ this.logger.debug('[DynamicWaasWalletClient] Completed export of private key', _extends({
1473
1750
  accountAddress,
1474
1751
  chainName,
1475
1752
  walletId: wallet.walletId,
1476
1753
  exportId,
1477
1754
  roomId: data.roomId
1478
- });
1755
+ }, this.getTraceContext(traceContext)));
1479
1756
  const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
1480
1757
  let derivedPrivateKey;
1481
1758
  if (mpcSigner instanceof web.Ecdsa) {
@@ -1492,11 +1769,11 @@ class DynamicWalletClient {
1492
1769
  logError({
1493
1770
  message: 'Error in exportKey',
1494
1771
  error: error,
1495
- context: {
1772
+ context: _extends({
1496
1773
  accountAddress,
1497
1774
  chainName,
1498
1775
  dynamicRequestId
1499
- }
1776
+ }, this.getTraceContext(traceContext))
1500
1777
  });
1501
1778
  throw error;
1502
1779
  }
@@ -1596,43 +1873,14 @@ class DynamicWalletClient {
1596
1873
  });
1597
1874
  await ((_this_storage = this.storage) == null ? void 0 : _this_storage.setItem(accountAddress, stringifiedClientKeyShares));
1598
1875
  }
1599
- /**
1600
- * Central backup orchestrator that encrypts and stores wallet key shares.
1601
- *
1602
- * This method serves as the main backup coordinator, handling the distribution of encrypted
1603
- * key shares between Dynamic's backend and Google Drive based on the wallet's threshold scheme.
1604
- * It is used by multiple operations including reshare, refresh, and manual backup requests.
1605
- *
1606
- * **Backup Distribution Strategy:**
1607
- * - **Single share wallets**: All shares stored on Dynamic's backend only
1608
- * - **Multi-share wallets (2+)**: When backing up to Google Drive, N-1 shares on Dynamic's backend, 1 share on Google Drive
1609
- * - **Multi-share wallets (2+)**: When not backing up to Google Drive, all shares on Dynamic's backend
1610
- *
1611
- * **Process Flow:**
1612
- * 1. Encrypts all client key shares with the provided password (or environment ID if no password)
1613
- * 2. For multi-share wallets (2+): conditionally distributes N-1 to backend, 1 to Google Drive
1614
- * 3. For other configurations: stores all shares on Dynamic's backend
1615
- * 4. Updates backup metadata and synchronizes wallet state
1616
- * 5. Persists the updated wallet map to local storage
1617
- *
1618
- * @param params - The backup operation parameters
1619
- * @param params.accountAddress - The account address of the wallet to backup
1620
- * @param params.clientKeyShares - Optional specific key shares to backup (uses localStorage if not provided)
1621
- * @param params.password - Optional password for encryption (uses environment ID if not provided)
1622
- * @param params.signedSessionId - Optional signed session ID for authentication
1623
- * @param params.backupToGoogleDrive - Whether to backup to Google Drive (defaults to false)
1624
- * @returns Promise with backup metadata including share locations and IDs
1625
- */ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, backupToGoogleDrive = false }) {
1876
+ async backupSharesWithDistribution({ accountAddress, password, signedSessionId, distribution, preserveDelegatedLocation = false }) {
1626
1877
  const dynamicRequestId = uuid.v4();
1627
1878
  try {
1628
- var _this_walletMap_accountAddress, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_GOOGLE_DRIVE, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress1;
1629
- const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
1630
- accountAddress
1631
- });
1879
+ var _this_walletMap_accountAddress;
1632
1880
  if (!((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.walletId)) {
1633
1881
  const error = new Error(`WalletId not found for accountAddress ${accountAddress}`);
1634
1882
  logError({
1635
- message: 'Error in storeEncryptedBackupByWallet, wallet or walletId not found from the wallet map',
1883
+ message: 'Error in backupSharesWithDistribution, wallet or walletId not found from the wallet map',
1636
1884
  error,
1637
1885
  context: {
1638
1886
  accountAddress,
@@ -1641,55 +1889,73 @@ class DynamicWalletClient {
1641
1889
  });
1642
1890
  throw error;
1643
1891
  }
1644
- // TODO(zfaizal2): throw error if signedSessionId is not provided after service deploy
1645
- let dynamicClientKeyShares = [];
1646
- let googleDriveKeyShares = [];
1647
- const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
1648
- keyShare,
1649
- password
1650
- })));
1651
- const hasExistingGoogleDriveBackup = ((_this_walletMap_accountAddress1 = this.walletMap[accountAddress]) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo = _this_walletMap_accountAddress1.clientKeySharesBackupInfo) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups = _this_walletMap_accountAddress_clientKeySharesBackupInfo.backups) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_GOOGLE_DRIVE = _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups[core.BackupLocation.GOOGLE_DRIVE]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_GOOGLE_DRIVE.length) > 0;
1652
- // Backup to Google Drive if:
1653
- // 1. Explicitly requested via flag, OR
1654
- // 2. User already has Google Drive backups
1655
- const shouldBackupToGoogleDrive = backupToGoogleDrive || hasExistingGoogleDriveBackup;
1656
- if (shouldBackupToGoogleDrive && keySharesToBackup.length >= 2) {
1657
- // For 2 shares: 1 to backend, 1 to Google Drive
1658
- // For 3+ shares: N-1 to backend, 1 to Google Drive
1659
- const googleDriveShareCount = 1;
1660
- dynamicClientKeyShares = encryptedKeyShares.slice(0, -googleDriveShareCount);
1661
- googleDriveKeyShares = encryptedKeyShares.slice(-googleDriveShareCount);
1662
- } else {
1663
- dynamicClientKeyShares = encryptedKeyShares;
1664
- }
1665
- const data = await this.apiClient.storeEncryptedBackupByWallet({
1666
- walletId: this.walletMap[accountAddress].walletId,
1667
- encryptedKeyShares: dynamicClientKeyShares,
1668
- passwordEncrypted: Boolean(password) && password !== this.environmentId,
1669
- encryptionVersion: ENCRYPTION_VERSION_CURRENT,
1670
- signedSessionId,
1671
- authMode: this.authMode,
1672
- requiresSignedSessionId: this.requiresSignedSessionId(),
1673
- dynamicRequestId
1674
- });
1675
- if (data.keyShareIds.length === 0) {
1676
- throw new Error('No key shares were backed up');
1677
- }
1678
- const locations = [
1679
- {
1892
+ const locations = [];
1893
+ if (distribution.dynamicBackendShares.length > 0) {
1894
+ const encryptedDynamicShares = await Promise.all(distribution.dynamicBackendShares.map((keyShare)=>this.encryptKeyShare({
1895
+ keyShare,
1896
+ password
1897
+ })));
1898
+ const data = await this.apiClient.storeEncryptedBackupByWallet({
1899
+ walletId: this.walletMap[accountAddress].walletId,
1900
+ encryptedKeyShares: encryptedDynamicShares,
1901
+ passwordEncrypted: Boolean(password) && password !== this.environmentId,
1902
+ encryptionVersion: ENCRYPTION_VERSION_CURRENT,
1903
+ signedSessionId,
1904
+ authMode: this.authMode,
1905
+ requiresSignedSessionId: this.requiresSignedSessionId(),
1906
+ dynamicRequestId
1907
+ });
1908
+ if (data.keyShareIds.length === 0) {
1909
+ throw new Error('No key shares were backed up to Dynamic backend');
1910
+ }
1911
+ locations.push({
1680
1912
  location: core.BackupLocation.DYNAMIC,
1681
1913
  externalKeyShareId: data.keyShareIds[0]
1682
- }
1683
- ];
1684
- if (googleDriveKeyShares.length > 0) {
1914
+ });
1915
+ }
1916
+ if (distribution.googleDriveShares.length > 0) {
1917
+ const encryptedGoogleDriveShares = await Promise.all(distribution.googleDriveShares.map((keyShare)=>this.encryptKeyShare({
1918
+ keyShare,
1919
+ password
1920
+ })));
1685
1921
  await this.uploadKeySharesToGoogleDrive({
1686
1922
  accountAddress,
1687
- encryptedKeyShares: googleDriveKeyShares
1923
+ encryptedKeyShares: encryptedGoogleDriveShares
1688
1924
  });
1689
1925
  locations.push({
1690
1926
  location: core.BackupLocation.GOOGLE_DRIVE
1691
1927
  });
1692
1928
  }
1929
+ if (distribution.delegatedShare) {
1930
+ var _publicKey_key, _publicKey_key1, _publicKey_key2;
1931
+ const publicKey = await this.apiClient.getDelegatedEncryptionKey({
1932
+ environmentId: this.environmentId
1933
+ });
1934
+ if (!(publicKey == null ? void 0 : (_publicKey_key = publicKey.key) == null ? void 0 : _publicKey_key.publicKeyPemB64)) {
1935
+ throw new Error('Public key not found');
1936
+ }
1937
+ var _publicKey_key_keyId;
1938
+ const encryptedDelegatedKeyShareEnvelope = await encryptDelegatedKeyShare(JSON.stringify(distribution.delegatedShare), publicKey == null ? void 0 : (_publicKey_key1 = publicKey.key) == null ? void 0 : _publicKey_key1.publicKeyPemB64, (_publicKey_key_keyId = publicKey == null ? void 0 : (_publicKey_key2 = publicKey.key) == null ? void 0 : _publicKey_key2.keyId) != null ? _publicKey_key_keyId : publicKey == null ? void 0 : publicKey.keyId);
1939
+ const { status } = await this.apiClient.publishDelegatedKeyShare({
1940
+ walletId: this.walletMap[accountAddress].walletId,
1941
+ encryptedKeyShare: encryptedDelegatedKeyShareEnvelope,
1942
+ signedSessionId,
1943
+ requiresSignedSessionId: this.requiresSignedSessionId(),
1944
+ dynamicRequestId
1945
+ });
1946
+ if (status !== 200) {
1947
+ throw new Error('Failed to publish delegated key share');
1948
+ }
1949
+ locations.push({
1950
+ location: core.BackupLocation.DELEGATED
1951
+ });
1952
+ }
1953
+ // Preserve existing delegated location without re-publishing
1954
+ if (preserveDelegatedLocation && !distribution.delegatedShare) {
1955
+ locations.push({
1956
+ location: core.BackupLocation.DELEGATED
1957
+ });
1958
+ }
1693
1959
  const backupData = await this.apiClient.markKeySharesAsBackedUp({
1694
1960
  walletId: this.walletMap[accountAddress].walletId,
1695
1961
  locations,
@@ -1711,10 +1977,10 @@ class DynamicWalletClient {
1711
1977
  clientKeySharesBackupInfo: updatedBackupInfo
1712
1978
  });
1713
1979
  await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1714
- return data;
1980
+ return backupData;
1715
1981
  } catch (error) {
1716
1982
  logError({
1717
- message: 'Error in storeEncryptedBackupByWallet',
1983
+ message: 'Error in backupSharesWithDistribution',
1718
1984
  error: error,
1719
1985
  context: {
1720
1986
  accountAddress,
@@ -1724,109 +1990,87 @@ class DynamicWalletClient {
1724
1990
  throw error;
1725
1991
  }
1726
1992
  }
1727
- async storeEncryptedBackupByDelegatedWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId }) {
1728
- const dynamicRequestId = uuid.v4();
1729
- try {
1730
- var _this_walletMap_accountAddress, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_EXTERNAL, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress1;
1731
- const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
1732
- accountAddress
1733
- });
1734
- if (!((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.walletId)) {
1735
- const error = new Error(`WalletId not found for accountAddress ${accountAddress}`);
1736
- logError({
1737
- message: 'Error in storeEncryptedBackupByWallet, wallet or walletId not found from the wallet map',
1738
- error,
1739
- context: {
1740
- accountAddress,
1741
- walletMap: this.walletMap
1742
- }
1743
- });
1744
- throw error;
1745
- }
1746
- let dynamicClientKeyShares = [];
1747
- let delegatedKeyShares = [];
1748
- const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
1749
- keyShare,
1750
- password
1751
- })));
1752
- const hasExistingDelegatedBackup = ((_this_walletMap_accountAddress1 = this.walletMap[accountAddress]) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo = _this_walletMap_accountAddress1.clientKeySharesBackupInfo) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups = _this_walletMap_accountAddress_clientKeySharesBackupInfo.backups) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_EXTERNAL = _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups[core.BackupLocation.EXTERNAL]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_EXTERNAL.length) > 0;
1753
- const shouldBackupToDelegated = hasExistingDelegatedBackup || keySharesToBackup.length >= 2;
1754
- if (shouldBackupToDelegated) {
1755
- // For 2 shares: 1 to backend, 1 to delegated
1756
- // For 3+ shares: N-1 to backend, 1 to delegated
1757
- dynamicClientKeyShares = encryptedKeyShares.slice(0, -core.DELEGATED_SHARE_COUNT);
1758
- delegatedKeyShares = encryptedKeyShares.slice(-core.DELEGATED_SHARE_COUNT);
1759
- } else {
1760
- dynamicClientKeyShares = encryptedKeyShares;
1761
- }
1762
- const data = await this.apiClient.storeEncryptedBackupByWallet({
1763
- walletId: this.walletMap[accountAddress].walletId,
1764
- encryptedKeyShares: dynamicClientKeyShares,
1765
- passwordEncrypted: Boolean(password) && password !== this.environmentId,
1766
- encryptionVersion: ENCRYPTION_VERSION_CURRENT,
1767
- signedSessionId,
1768
- authMode: this.authMode,
1769
- requiresSignedSessionId: this.requiresSignedSessionId(),
1770
- dynamicRequestId
1771
- });
1772
- await this.apiClient.markKeySharesAsBackedUp({
1773
- walletId: this.walletMap[accountAddress].walletId,
1774
- dynamicRequestId,
1775
- locations: [
1776
- {
1777
- location: core.BackupLocation.DYNAMIC
1778
- }
1779
- ]
1780
- });
1781
- if (delegatedKeyShares.length > 0) {
1782
- const wallet = this.walletMap[accountAddress];
1783
- var _this_userId;
1784
- const delegatedKeyShareIds = await this.sendKeySharesToDelegatedAccess({
1785
- accountAddress,
1786
- chainName: wallet.chainName,
1787
- environmentId: this.environmentId,
1788
- walletId: wallet.walletId,
1789
- userId: (_this_userId = this.userId) != null ? _this_userId : '',
1790
- delegatedKeyShares: delegatedKeyShares
1791
- });
1792
- data.keyShares.push({
1793
- backupLocation: core.BackupLocation.EXTERNAL,
1794
- id: delegatedKeyShareIds
1795
- });
1796
- //todo: combine with user share service backup once other branch is merged
1797
- await this.apiClient.markKeySharesAsBackedUp({
1798
- walletId: this.walletMap[accountAddress].walletId,
1799
- locations: [
1800
- {
1801
- location: core.BackupLocation.EXTERNAL
1802
- }
1803
- ],
1804
- dynamicRequestId
1805
- });
1806
- }
1807
- const updatedBackupInfo = getClientKeyShareBackupInfo({
1808
- walletProperties: {
1809
- derivationPath: this.walletMap[accountAddress].derivationPath,
1810
- keyShares: data.keyShares,
1811
- thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
1812
- }
1813
- });
1814
- this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1815
- clientKeySharesBackupInfo: updatedBackupInfo
1993
+ /**
1994
+ * Central backup orchestrator that encrypts and stores wallet key shares.
1995
+ *
1996
+ * This method serves as the main backup coordinator, handling the distribution of encrypted
1997
+ * key shares between Dynamic's backend and Google Drive based on the wallet's threshold scheme.
1998
+ * It is used by multiple operations including reshare, refresh, and manual backup requests.
1999
+ *
2000
+ * **Backup Distribution Strategy:**
2001
+ * - **Single share wallets**: All shares stored on Dynamic's backend only
2002
+ * - **Multi-share wallets (2+)**: When backing up to Google Drive, N-1 shares on Dynamic's backend, 1 share on Google Drive
2003
+ * - **Multi-share wallets (2+)**: When not backing up to Google Drive, all shares on Dynamic's backend
2004
+ *
2005
+ * **Process Flow:**
2006
+ * 1. Encrypts all client key shares with the provided password (or environment ID if no password)
2007
+ * 2. For multi-share wallets (2+): conditionally distributes N-1 to backend, 1 to Google Drive
2008
+ * 3. For other configurations: stores all shares on Dynamic's backend
2009
+ * 4. Updates backup metadata and synchronizes wallet state
2010
+ * 5. Persists the updated wallet map to local storage
2011
+ *
2012
+ * **Delegated Key Shares:**
2013
+ * - When delegatedKeyshare is provided, the method will not store the delegated key share but it will mark the delegated share as backed up on Dynamic's backend
2014
+ * - and encrypt the delegated key share to publish it to the webhook
2015
+ *
2016
+ * @param params - The backup operation parameters
2017
+ * @param params.accountAddress - The account address of the wallet to backup
2018
+ * @param params.clientKeyShares - Optional specific key shares to backup (uses localStorage if not provided)
2019
+ * @param params.password - Optional password for encryption (uses environment ID if not provided)
2020
+ * @param params.signedSessionId - Optional signed session ID for authentication
2021
+ * @param params.backupToGoogleDrive - Whether to backup to Google Drive (defaults to false)
2022
+ * @returns Promise with backup metadata including share locations and IDs
2023
+ */ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, backupToGoogleDrive = false, delegatedKeyshare = undefined }) {
2024
+ var _this_walletMap_accountAddress, _this_walletMap_accountAddress1;
2025
+ const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
2026
+ accountAddress
2027
+ });
2028
+ const shouldBackupToGoogleDrive = backupToGoogleDrive || hasGoogleDriveBackup((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.clientKeySharesBackupInfo);
2029
+ const hasExistingDelegation = hasDelegatedBackup((_this_walletMap_accountAddress1 = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress1.clientKeySharesBackupInfo);
2030
+ let distribution;
2031
+ let preserveDelegatedLocation = false;
2032
+ if (delegatedKeyshare && shouldBackupToGoogleDrive) {
2033
+ // NEW delegation + Google Drive: Client's shares back up to both Dynamic and Google Drive.
2034
+ // The delegated share goes to the webhook.
2035
+ distribution = createDelegationWithGoogleDriveDistribution({
2036
+ existingShares: keySharesToBackup,
2037
+ delegatedShare: delegatedKeyshare
2038
+ });
2039
+ } else if (delegatedKeyshare) {
2040
+ // NEW delegation only: Client's shares back up to Dynamic.
2041
+ // The delegated share goes to the webhook. No Google Drive backup.
2042
+ distribution = createDelegationOnlyDistribution({
2043
+ existingShares: keySharesToBackup,
2044
+ delegatedShare: delegatedKeyshare
2045
+ });
2046
+ } else if (hasExistingDelegation && shouldBackupToGoogleDrive) {
2047
+ // ADD Google Drive to EXISTING delegation: Client's share backs up to both Dynamic and GD.
2048
+ // Don't re-publish delegated share, just preserve the location.
2049
+ distribution = createAddGoogleDriveToExistingDelegationDistribution({
2050
+ clientShares: keySharesToBackup
2051
+ });
2052
+ preserveDelegatedLocation = true;
2053
+ } else if (shouldBackupToGoogleDrive && keySharesToBackup.length >= 2) {
2054
+ // Google Drive only (no delegation): Split shares between Dynamic (N-1) and Google Drive (1).
2055
+ distribution = createGoogleDriveOnlyDistribution({
2056
+ allShares: keySharesToBackup
1816
2057
  });
1817
- await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1818
- return data;
1819
- } catch (error) {
1820
- logError({
1821
- message: 'Error in storeEncryptedBackupByDelegatedWallet',
1822
- error: error,
1823
- context: {
1824
- accountAddress,
1825
- dynamicRequestId
1826
- }
2058
+ } else {
2059
+ // No delegation, no Google Drive: All shares go to Dynamic backend only.
2060
+ distribution = createDynamicOnlyDistribution({
2061
+ allShares: keySharesToBackup
1827
2062
  });
1828
- throw error;
1829
2063
  }
2064
+ const backupData = await this.backupSharesWithDistribution({
2065
+ accountAddress,
2066
+ password,
2067
+ signedSessionId,
2068
+ distribution,
2069
+ preserveDelegatedLocation
2070
+ });
2071
+ return _extends({}, backupData, {
2072
+ keyShareIds: backupData.locationsWithKeyShares.map((ks)=>ks.keyShareId)
2073
+ });
1830
2074
  }
1831
2075
  async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password, signedSessionId }) {
1832
2076
  await retryPromise(()=>this.storeEncryptedBackupByWallet({
@@ -2017,14 +2261,18 @@ class DynamicWalletClient {
2017
2261
  return (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : '';
2018
2262
  });
2019
2263
  } else {
2020
- // Already 2-of-3, only call backup
2021
- const data = await this.storeEncryptedBackupByWallet({
2264
+ await this.storeEncryptedBackupByWallet({
2022
2265
  accountAddress,
2023
2266
  password,
2024
2267
  signedSessionId,
2025
2268
  backupToGoogleDrive: true
2026
2269
  });
2027
- return data.keyShareIds;
2270
+ const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
2271
+ const googleDriveShares = backupInfo.backups[core.BackupLocation.GOOGLE_DRIVE] || [];
2272
+ return googleDriveShares.map((ks)=>{
2273
+ var _ks_externalKeyShareId;
2274
+ return (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : '';
2275
+ });
2028
2276
  }
2029
2277
  } catch (error) {
2030
2278
  logError({
@@ -2060,7 +2308,8 @@ class DynamicWalletClient {
2060
2308
  const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
2061
2309
  const fileName = getClientKeyShareExportFileName({
2062
2310
  thresholdSignatureScheme,
2063
- accountAddress
2311
+ accountAddress,
2312
+ isGoogleDrive: true
2064
2313
  });
2065
2314
  const backupData = createBackupData({
2066
2315
  encryptedKeyShares,
@@ -2085,7 +2334,7 @@ class DynamicWalletClient {
2085
2334
  throw error;
2086
2335
  }
2087
2336
  }
2088
- async restoreBackupFromGoogleDrive({ accountAddress, password, signedSessionId }) {
2337
+ async exportClientKeysharesFromGoogleDrive({ accountAddress, password, signedSessionId }) {
2089
2338
  try {
2090
2339
  await this.getWallet({
2091
2340
  accountAddress,
@@ -2096,15 +2345,16 @@ class DynamicWalletClient {
2096
2345
  oauthAccountId
2097
2346
  });
2098
2347
  const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
2099
- const fileName = getClientKeyShareExportFileName({
2348
+ const backupFileName = getClientKeyShareExportFileName({
2100
2349
  thresholdSignatureScheme,
2101
- accountAddress
2350
+ accountAddress,
2351
+ isGoogleDrive: true
2102
2352
  });
2103
2353
  let backupData = null;
2104
2354
  try {
2105
2355
  backupData = await retryPromise(()=>downloadFileFromGoogleDrive({
2106
2356
  accessToken,
2107
- fileName
2357
+ fileName: backupFileName
2108
2358
  }));
2109
2359
  } catch (error) {
2110
2360
  logError({
@@ -2112,7 +2362,7 @@ class DynamicWalletClient {
2112
2362
  error: error,
2113
2363
  context: {
2114
2364
  accountAddress,
2115
- fileName
2365
+ fileName: backupFileName
2116
2366
  }
2117
2367
  });
2118
2368
  throw error;
@@ -2124,7 +2374,7 @@ class DynamicWalletClient {
2124
2374
  error: new Error('No backup file found'),
2125
2375
  context: {
2126
2376
  accountAddress,
2127
- fileName
2377
+ fileName: backupFileName
2128
2378
  }
2129
2379
  });
2130
2380
  throw error;
@@ -2137,7 +2387,7 @@ class DynamicWalletClient {
2137
2387
  error,
2138
2388
  context: {
2139
2389
  accountAddress,
2140
- fileName
2390
+ fileName: backupFileName
2141
2391
  }
2142
2392
  });
2143
2393
  throw error;
@@ -2147,15 +2397,20 @@ class DynamicWalletClient {
2147
2397
  keyShare,
2148
2398
  password
2149
2399
  })));
2150
- await this.setClientKeySharesToLocalStorage({
2400
+ const text = JSON.stringify(decryptedKeyShares, null, 2);
2401
+ const exportFileName = getClientKeyShareExportFileName({
2402
+ thresholdSignatureScheme,
2151
2403
  accountAddress,
2152
- clientKeyShares: decryptedKeyShares,
2153
- overwriteOrMerge: 'merge'
2404
+ isGoogleDrive: true
2405
+ });
2406
+ downloadStringAsFile({
2407
+ filename: exportFileName,
2408
+ content: text,
2409
+ mimeType: 'application/json'
2154
2410
  });
2155
- return decryptedKeyShares;
2156
2411
  } catch (error) {
2157
2412
  logError({
2158
- message: 'Error in restoreBackupFromGoogleDrive',
2413
+ message: 'Error in exportClientKeysharesFromGoogleDrive',
2159
2414
  error: error,
2160
2415
  context: {
2161
2416
  accountAddress
@@ -2182,16 +2437,11 @@ class DynamicWalletClient {
2182
2437
  keyShares: clientKeyShares,
2183
2438
  derivationPath
2184
2439
  });
2185
- const blob = new Blob([
2186
- text
2187
- ], {
2188
- type: 'text/plain'
2440
+ downloadStringAsFile({
2441
+ filename: `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`,
2442
+ content: text,
2443
+ mimeType: 'text/plain'
2189
2444
  });
2190
- const url = URL.createObjectURL(blob);
2191
- const a = document.createElement('a');
2192
- a.href = url;
2193
- a.download = `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`;
2194
- a.click();
2195
2445
  }
2196
2446
  async getClientKeyShares({ accountAddress, password, signedSessionId }) {
2197
2447
  await this.getWallet({
@@ -2491,29 +2741,142 @@ class DynamicWalletClient {
2491
2741
  }
2492
2742
  this.apiClient.syncAuthToken(authToken);
2493
2743
  }
2744
+ async createRooms({ roomType, thresholdSignatureScheme, roomCount = 5 }) {
2745
+ const numberOfParties = this.getNumberOfParties(roomType, thresholdSignatureScheme);
2746
+ try {
2747
+ var _rooms_roomIds;
2748
+ if (DynamicWalletClient.roomsInitializing[numberOfParties]) {
2749
+ return;
2750
+ }
2751
+ DynamicWalletClient.roomsInitializing[numberOfParties] = true;
2752
+ // Check if rooms for this number of parties already exist (from previous restore or creation)
2753
+ const existingRooms = DynamicWalletClient.rooms[numberOfParties];
2754
+ if (existingRooms && existingRooms.length > 0) {
2755
+ DynamicWalletClient.roomsInitializing[numberOfParties] = false;
2756
+ return;
2757
+ }
2758
+ const rooms = await this.apiClient.createRoomsWithoutWalletId({
2759
+ roomType,
2760
+ roomCount,
2761
+ thresholdSignatureScheme
2762
+ });
2763
+ const newRooms = (rooms == null ? void 0 : (_rooms_roomIds = rooms.roomIds) == null ? void 0 : _rooms_roomIds.map((roomId)=>({
2764
+ roomId,
2765
+ createdAt: Date.now(),
2766
+ expiresAt: Date.now() + ROOM_EXPIRATION_TIME
2767
+ }))) || [];
2768
+ const updatedRooms = [
2769
+ ...newRooms,
2770
+ ...existingRooms || []
2771
+ ];
2772
+ await this.setRooms(numberOfParties, updatedRooms);
2773
+ DynamicWalletClient.roomsInitializing[numberOfParties] = false;
2774
+ } catch (error) {
2775
+ DynamicWalletClient.roomsInitializing[numberOfParties] = false;
2776
+ this.logger.error('Error creating rooms', error);
2777
+ throw error;
2778
+ }
2779
+ }
2780
+ async restoreRooms() {
2781
+ const roomData = await this.storage.getItem(`${this.storageKey}-rooms`);
2782
+ if (!roomData) {
2783
+ return {};
2784
+ }
2785
+ const parsedRoomData = JSON.parse(roomData);
2786
+ const now = Date.now();
2787
+ // Filter out expired rooms for all party counts
2788
+ const restoredRooms = {};
2789
+ for (const [partiesStr, rooms] of Object.entries(parsedRoomData)){
2790
+ const numberOfParties = Number(partiesStr);
2791
+ const validRooms = rooms.filter((room)=>room.expiresAt > now);
2792
+ if (validRooms.length > 0) {
2793
+ restoredRooms[numberOfParties] = validRooms;
2794
+ }
2795
+ }
2796
+ // Restore all rooms to the static property
2797
+ DynamicWalletClient.rooms = restoredRooms;
2798
+ return restoredRooms;
2799
+ }
2800
+ async getRooms(roomType, thresholdSignatureScheme) {
2801
+ if (roomType && thresholdSignatureScheme) {
2802
+ const numberOfParties = this.getNumberOfParties(roomType, thresholdSignatureScheme);
2803
+ return DynamicWalletClient.rooms[numberOfParties] || [];
2804
+ }
2805
+ return DynamicWalletClient.rooms;
2806
+ }
2807
+ async setRooms(numberOfParties, rooms) {
2808
+ DynamicWalletClient.rooms[numberOfParties] = rooms;
2809
+ await this.storage.setItem(`${this.storageKey}-rooms`, JSON.stringify(DynamicWalletClient.rooms));
2810
+ }
2811
+ getNumberOfParties(roomType, thresholdSignatureScheme) {
2812
+ const MPC_SCHEME_CONFIG = core.MPC_CONFIG[thresholdSignatureScheme];
2813
+ return roomType === sdkApiCore.RoomTypeEnum.Threshold ? MPC_SCHEME_CONFIG.threshold : MPC_SCHEME_CONFIG.numberOfParties;
2814
+ }
2815
+ async getRoom(roomType, thresholdSignatureScheme) {
2816
+ const numberOfParties = this.getNumberOfParties(roomType, thresholdSignatureScheme);
2817
+ const now = Date.now();
2818
+ const schemeRooms = DynamicWalletClient.rooms[numberOfParties] || [];
2819
+ // Filter out expired rooms
2820
+ const filteredRooms = schemeRooms.filter((room)=>room.expiresAt > now);
2821
+ await this.setRooms(numberOfParties, filteredRooms);
2822
+ const room = filteredRooms.shift();
2823
+ if (room) {
2824
+ await this.setRooms(numberOfParties, filteredRooms);
2825
+ }
2826
+ // If less than or only 1 room left after removing one, trigger async creation for more rooms
2827
+ // Check the length after removing the room
2828
+ const remainingRooms = DynamicWalletClient.rooms[numberOfParties] || [];
2829
+ if (remainingRooms.length <= 1) {
2830
+ this.createRooms({
2831
+ roomType,
2832
+ thresholdSignatureScheme,
2833
+ roomCount: ROOM_CACHE_COUNT
2834
+ });
2835
+ }
2836
+ return room;
2837
+ }
2838
+ /**
2839
+ * Helper method to instrument with automatic properties inclusion
2840
+ */ instrument(message, context) {
2841
+ const defaultContext = {
2842
+ environmentId: context.environmentId || this.environmentId
2843
+ };
2844
+ this.logger.debug(message, _extends({}, defaultContext, context));
2845
+ this.logger.instrument(message, _extends({}, defaultContext, context));
2846
+ }
2847
+ getTraceContext(traceContext) {
2848
+ const now = Date.now();
2849
+ return _extends({
2850
+ now,
2851
+ time: (traceContext == null ? void 0 : traceContext.startTime) ? now - traceContext.startTime : 0
2852
+ }, traceContext);
2853
+ }
2494
2854
  constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, featureFlags, authMode = core.AuthMode.HEADER, authToken = undefined, // Represents the version of the client SDK used by developer
2495
- sdkVersion }){
2855
+ sdkVersion, forwardMPCClient, baseClientKeysharesRelayApiUrl }){
2496
2856
  this.userId = undefined;
2497
2857
  this.sessionId = undefined;
2498
- this.delegatedAccessEndpoint = undefined;
2499
2858
  this.initializePromise = null;
2500
2859
  this.logger = logger;
2501
2860
  this.walletMap = {} // todo: store in session storage
2502
2861
  ;
2503
2862
  this.memoryStorage = null;
2504
2863
  this.iframe = null;
2864
+ this.forwardMPCEnabled = false;
2505
2865
  this.featureFlags = {};
2506
2866
  this.environmentId = environmentId;
2507
2867
  this.storageKey = `${STORAGE_KEY}-${storageKey != null ? storageKey : environmentId}`;
2508
2868
  this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
2509
2869
  this.authMode = authMode;
2510
2870
  this.sdkVersion = sdkVersion;
2871
+ this.baseClientKeysharesRelayApiUrl = baseClientKeysharesRelayApiUrl;
2511
2872
  this.apiClient = new core.DynamicApiClient({
2512
2873
  environmentId,
2513
2874
  authToken,
2514
2875
  baseApiUrl,
2515
2876
  authMode,
2516
- sdkVersion
2877
+ sdkVersion,
2878
+ forwardMPCClient,
2879
+ baseClientKeysharesRelayApiUrl
2517
2880
  });
2518
2881
  this.debug = Boolean(debug);
2519
2882
  this.logger.setLogLevel(this.debug ? logger$1.LogLevel.DEBUG : DEFAULT_LOG_LEVEL);
@@ -2533,8 +2896,15 @@ class DynamicWalletClient {
2533
2896
  if (authMode === core.AuthMode.HEADER && authToken) {
2534
2897
  this.initLoggerContext(authToken);
2535
2898
  }
2899
+ this.forwardMPCEnabled = this.featureFlags && this.featureFlags[core.FEATURE_FLAGS.ENABLE_FORWARD_MPC_CLIENT_FLAG] === true;
2900
+ // if forwardMPCEnabled is true and forwardMPCClient is not provided, initialize the forwardMPCClient
2901
+ if (this.forwardMPCEnabled && !forwardMPCClient) {
2902
+ this.initializeForwardMPCClient();
2903
+ }
2536
2904
  }
2537
2905
  }
2906
+ DynamicWalletClient.rooms = {};
2907
+ DynamicWalletClient.roomsInitializing = {};
2538
2908
 
2539
2909
  const ERROR_KEYGEN_FAILED = '[DynamicWaasWalletClient]: Error with keygen';
2540
2910
  const ERROR_CREATE_WALLET_ACCOUNT = '[DynamicWaasWalletClient]: Error creating wallet account';
@@ -2608,7 +2978,13 @@ exports.ERROR_SIGN_MESSAGE = ERROR_SIGN_MESSAGE;
2608
2978
  exports.ERROR_SIGN_TYPED_DATA = ERROR_SIGN_TYPED_DATA;
2609
2979
  exports.ERROR_VERIFY_MESSAGE_SIGNATURE = ERROR_VERIFY_MESSAGE_SIGNATURE;
2610
2980
  exports.ERROR_VERIFY_TRANSACTION_SIGNATURE = ERROR_VERIFY_TRANSACTION_SIGNATURE;
2981
+ exports.createAddGoogleDriveToExistingDelegationDistribution = createAddGoogleDriveToExistingDelegationDistribution;
2611
2982
  exports.createBackupData = createBackupData;
2983
+ exports.createDelegationOnlyDistribution = createDelegationOnlyDistribution;
2984
+ exports.createDelegationWithGoogleDriveDistribution = createDelegationWithGoogleDriveDistribution;
2985
+ exports.createDynamicOnlyDistribution = createDynamicOnlyDistribution;
2986
+ exports.createGoogleDriveOnlyDistribution = createGoogleDriveOnlyDistribution;
2987
+ exports.downloadStringAsFile = downloadStringAsFile;
2612
2988
  exports.formatEvmMessage = formatEvmMessage;
2613
2989
  exports.formatMessage = formatMessage;
2614
2990
  exports.getClientKeyShareBackupInfo = getClientKeyShareBackupInfo;
@@ -2616,6 +2992,8 @@ exports.getClientKeyShareExportFileName = getClientKeyShareExportFileName;
2616
2992
  exports.getGoogleOAuthAccountId = getGoogleOAuthAccountId;
2617
2993
  exports.getMPCSignatureScheme = getMPCSignatureScheme;
2618
2994
  exports.getMPCSigner = getMPCSigner;
2995
+ exports.hasDelegatedBackup = hasDelegatedBackup;
2996
+ exports.hasGoogleDriveBackup = hasGoogleDriveBackup;
2619
2997
  exports.isBrowser = isBrowser;
2620
2998
  exports.isHexString = isHexString;
2621
2999
  exports.mergeUniqueKeyShares = mergeUniqueKeyShares;