@dynamic-labs-wallet/browser 0.0.0-preview.57 → 0.0.1-paolo-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +2081 -992
- package/index.esm.js +2061 -992
- package/internal/core/bip340.cjs +1 -0
- package/internal/core/common.cjs +1 -0
- package/internal/core/ecdsa.cjs +1 -0
- package/internal/core/ecdsa.d.ts +1 -1
- package/internal/core/ed25519.cjs +1 -0
- package/internal/core/ed25519_exportable.cjs +1 -0
- package/internal/core/ed25519_exportable.d.ts +21 -0
- package/internal/core/index.cjs +1 -0
- package/internal/core/index.d.ts +4 -2
- package/internal/core/native.d.ts +65 -0
- package/internal/core/sr25519.cjs +1 -0
- package/internal/core/sr25519.d.ts +22 -0
- package/internal/core/types.cjs +1 -0
- package/internal/core/types.d.ts +20 -0
- package/internal/web/generated/libmpc_executor.d.ts +60 -99
- package/internal/web/generated/libmpc_executor.js +2 -2
- package/internal/web/generated/libmpc_executor_bg.wasm +0 -0
- package/internal/web/index.d.ts +8 -2
- package/internal/web/index.js +1 -1
- package/package.json +22 -5
- package/src/backup/encryption/argon2.d.ts +10 -0
- package/src/backup/encryption/argon2.d.ts.map +1 -0
- package/src/backup/encryption/config.d.ts +39 -0
- package/src/backup/encryption/config.d.ts.map +1 -0
- package/src/backup/encryption/constants.d.ts +35 -0
- package/src/backup/encryption/constants.d.ts.map +1 -0
- package/src/backup/encryption/core.d.ts +27 -0
- package/src/backup/encryption/core.d.ts.map +1 -0
- package/src/backup/encryption/pbkdf2.d.ts +10 -0
- package/src/backup/encryption/pbkdf2.d.ts.map +1 -0
- package/src/backup/encryption/types.d.ts +46 -0
- package/src/backup/encryption/types.d.ts.map +1 -0
- package/src/backup/encryption/utils.d.ts +9 -0
- package/src/backup/encryption/utils.d.ts.map +1 -0
- package/src/backup/providers/googleDrive.d.ts +4 -4
- package/src/backup/providers/googleDrive.d.ts.map +1 -1
- package/src/backup/utils.d.ts +14 -0
- package/src/backup/utils.d.ts.map +1 -0
- package/src/client.d.ts +213 -127
- package/src/client.d.ts.map +1 -1
- package/src/constants.d.ts +4 -1
- package/src/constants.d.ts.map +1 -1
- package/src/errorConstants.d.ts +10 -0
- package/src/errorConstants.d.ts.map +1 -0
- package/src/index.d.ts +7 -4
- package/src/index.d.ts.map +1 -1
- package/src/mpc/index.d.ts +3 -3
- package/src/mpc/index.d.ts.map +1 -1
- package/src/mpc/mpc.d.ts +3 -3
- package/src/mpc/mpc.d.ts.map +1 -1
- package/src/mpc/types.d.ts +4 -4
- package/src/mpc/types.d.ts.map +1 -1
- package/src/services/axiosErrorResponse.d.ts +4 -0
- package/src/services/axiosErrorResponse.d.ts.map +1 -0
- package/src/services/encryption.d.ts +19 -0
- package/src/services/encryption.d.ts.map +1 -0
- package/src/services/logger.d.ts +13 -0
- package/src/services/logger.d.ts.map +1 -1
- package/src/types.d.ts +30 -4
- package/src/types.d.ts.map +1 -1
- package/src/utils.d.ts +32 -8
- package/src/utils.d.ts.map +1 -1
- package/internal/core/bip340.js +0 -1
- package/internal/core/common.js +0 -1
- package/internal/core/ecdsa.js +0 -1
- package/internal/core/ed25519.js +0 -1
- package/internal/core/index.js +0 -1
- package/internal/core/types.js +0 -1
- package/internal/web/generated/libmpc_executor_bg.wasm.d.ts +0 -64
- package/src/backup/encryption.d.ts +0 -22
- package/src/backup/encryption.d.ts.map +0 -1
- package/src/services/iframeDisplay.d.ts +0 -9
- package/src/services/iframeDisplay.d.ts.map +0 -1
- package/src/services/iframeLocalStorage.d.ts +0 -13
- package/src/services/iframeLocalStorage.d.ts.map +0 -1
- package/src/services/messageTransportBridge.d.ts +0 -3
- package/src/services/messageTransportBridge.d.ts.map +0 -1
- /package/internal/core/{native.js → native.cjs} +0 -0
package/index.cjs.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var core = require('@dynamic-labs-wallet/core');
|
|
4
|
-
var web = require('
|
|
4
|
+
var web = require('#internal/web');
|
|
5
|
+
var semver = require('semver');
|
|
6
|
+
var uuid = require('uuid');
|
|
5
7
|
var logger$1 = require('@dynamic-labs/logger');
|
|
6
|
-
var
|
|
8
|
+
var loadArgon2idWasm = require('argon2id');
|
|
9
|
+
var axios = require('axios');
|
|
10
|
+
var createHttpError = require('http-errors');
|
|
11
|
+
var sdkApiCore = require('@dynamic-labs/sdk-api-core');
|
|
7
12
|
|
|
8
13
|
function _extends() {
|
|
9
14
|
_extends = Object.assign || function assign(target) {
|
|
@@ -21,7 +26,7 @@ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core.MPC_RELAY
|
|
|
21
26
|
case core.SigningAlgorithm.ECDSA:
|
|
22
27
|
return new web.Ecdsa(baseRelayUrl);
|
|
23
28
|
case core.SigningAlgorithm.ED25519:
|
|
24
|
-
return new web.
|
|
29
|
+
return new web.ExportableEd25519(baseRelayUrl);
|
|
25
30
|
case core.SigningAlgorithm.BIP340:
|
|
26
31
|
return new web.BIP340(baseRelayUrl);
|
|
27
32
|
default:
|
|
@@ -37,13 +42,118 @@ const getMPCSigner = ({ chainName, baseRelayUrl })=>{
|
|
|
37
42
|
return signatureScheme;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Encryption version identifiers
|
|
47
|
+
*/ var EncryptionVersion = /*#__PURE__*/ function(EncryptionVersion) {
|
|
48
|
+
EncryptionVersion["V1_LEGACY"] = "v1";
|
|
49
|
+
EncryptionVersion["V2_PBKDF2"] = "v2";
|
|
50
|
+
EncryptionVersion["V3_ARGON2"] = "v3";
|
|
51
|
+
return EncryptionVersion;
|
|
52
|
+
}({});
|
|
53
|
+
/**
|
|
54
|
+
* Current default version for new encryptions
|
|
55
|
+
*/ const ENCRYPTION_VERSION_CURRENT = "v3";
|
|
56
|
+
/**
|
|
57
|
+
* Algorithm constants
|
|
58
|
+
*/ const PBKDF2_ALGORITHM = 'PBKDF2';
|
|
59
|
+
const HASH_ALGORITHM = 'SHA-256'; // Generic hash algorithm constant
|
|
60
|
+
const ARGON2_ALGORITHM = 'Argon2id';
|
|
61
|
+
const AES_GCM_ALGORITHM = 'AES-GCM';
|
|
62
|
+
const AES_GCM_LENGTH = 256;
|
|
63
|
+
/**
|
|
64
|
+
* Argon2 configuration constants, values were chosen based on RFC (https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice)
|
|
65
|
+
* taking into account that this runs in the client, possibly in smartphones with limited resources
|
|
66
|
+
*/ const ARGON2_MEMORY_SIZE = 65536; // 64 MB in KiB
|
|
67
|
+
const ARGON2_ITERATIONS = 3;
|
|
68
|
+
const ARGON2_PARALLELISM = 2;
|
|
69
|
+
const ARGON2_HASH_LENGTH = 32;
|
|
70
|
+
/**
|
|
71
|
+
* PBKDF2 configuration constants
|
|
72
|
+
*/ const PBKDF2_ITERATIONS_V1 = 100000;
|
|
73
|
+
const PBKDF2_ITERATIONS_V2 = 1000000;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Derives a key using Argon2id algorithm
|
|
77
|
+
* @param params - Key derivation parameters
|
|
78
|
+
* @param encryptionConfig - Encryption configuration
|
|
79
|
+
* @returns Promise<CryptoKey>
|
|
80
|
+
*/ const deriveArgon2Key = async ({ password, salt }, encryptionConfig)=>{
|
|
81
|
+
const argon2id = await loadArgon2idWasm();
|
|
82
|
+
const argon2Config = encryptionConfig;
|
|
83
|
+
const passwordBytes = new TextEncoder().encode(password);
|
|
84
|
+
const hash = argon2id({
|
|
85
|
+
password: passwordBytes,
|
|
86
|
+
salt: salt,
|
|
87
|
+
parallelism: argon2Config.parallelism || ARGON2_PARALLELISM,
|
|
88
|
+
passes: argon2Config.iterations,
|
|
89
|
+
memorySize: argon2Config.memorySize || ARGON2_MEMORY_SIZE,
|
|
90
|
+
tagLength: argon2Config.hashLength || ARGON2_HASH_LENGTH
|
|
91
|
+
});
|
|
92
|
+
return crypto.subtle.importKey('raw', new Uint8Array(hash), {
|
|
93
|
+
name: encryptionConfig.algorithm,
|
|
94
|
+
length: encryptionConfig.algorithmLength
|
|
95
|
+
}, false, [
|
|
96
|
+
'encrypt',
|
|
97
|
+
'decrypt'
|
|
98
|
+
]);
|
|
99
|
+
};
|
|
43
100
|
|
|
44
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Encryption configuration for each version
|
|
103
|
+
*/ const ENCRYPTION_VERSIONS = {
|
|
104
|
+
[EncryptionVersion.V1_LEGACY]: {
|
|
105
|
+
version: EncryptionVersion.V1_LEGACY,
|
|
106
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
107
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
108
|
+
iterations: PBKDF2_ITERATIONS_V1,
|
|
109
|
+
hashAlgorithm: HASH_ALGORITHM,
|
|
110
|
+
algorithmLength: AES_GCM_LENGTH
|
|
111
|
+
},
|
|
112
|
+
[EncryptionVersion.V2_PBKDF2]: {
|
|
113
|
+
version: EncryptionVersion.V2_PBKDF2,
|
|
114
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
115
|
+
keyDerivation: PBKDF2_ALGORITHM,
|
|
116
|
+
iterations: PBKDF2_ITERATIONS_V2,
|
|
117
|
+
hashAlgorithm: HASH_ALGORITHM,
|
|
118
|
+
algorithmLength: AES_GCM_LENGTH
|
|
119
|
+
},
|
|
120
|
+
[EncryptionVersion.V3_ARGON2]: {
|
|
121
|
+
version: EncryptionVersion.V3_ARGON2,
|
|
122
|
+
algorithm: AES_GCM_ALGORITHM,
|
|
123
|
+
keyDerivation: ARGON2_ALGORITHM,
|
|
124
|
+
iterations: ARGON2_ITERATIONS,
|
|
125
|
+
hashAlgorithm: ARGON2_ALGORITHM,
|
|
126
|
+
algorithmLength: AES_GCM_LENGTH,
|
|
127
|
+
memorySize: ARGON2_MEMORY_SIZE,
|
|
128
|
+
parallelism: ARGON2_PARALLELISM,
|
|
129
|
+
hashLength: ARGON2_HASH_LENGTH
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Helper function to get encryption configuration by version string
|
|
134
|
+
* @param version - The version string (e.g., 'v1', 'v2', 'v3')
|
|
135
|
+
* @returns The encryption configuration for the specified version
|
|
136
|
+
*/ const getEncryptionConfig = (version)=>{
|
|
137
|
+
// If no version specified, use legacy for backward compatibility
|
|
138
|
+
if (!version) {
|
|
139
|
+
return ENCRYPTION_VERSIONS[EncryptionVersion.V1_LEGACY];
|
|
140
|
+
}
|
|
141
|
+
const config = ENCRYPTION_VERSIONS[version];
|
|
142
|
+
if (!config) {
|
|
143
|
+
throw new Error(`Unsupported encryption version: ${version}`);
|
|
144
|
+
}
|
|
145
|
+
return config;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Check if a configuration uses Argon2
|
|
149
|
+
*/ const isArgon2Config = (config)=>{
|
|
150
|
+
return config.keyDerivation === ARGON2_ALGORITHM;
|
|
151
|
+
};
|
|
45
152
|
|
|
46
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Utility functions for encryption operations
|
|
155
|
+
* These functions are separated to avoid circular dependencies
|
|
156
|
+
*/ const bytesToBase64 = (arr)=>{
|
|
47
157
|
return btoa(Array.from(arr, (b)=>String.fromCharCode(b)).join(''));
|
|
48
158
|
};
|
|
49
159
|
const stringToBytes = (str)=>{
|
|
@@ -56,93 +166,13 @@ const base64ToBytes = (base64)=>{
|
|
|
56
166
|
const ensureBase64Padding = (str)=>{
|
|
57
167
|
return str.padEnd(Math.ceil(str.length / 4) * 4, '=');
|
|
58
168
|
};
|
|
59
|
-
const isBrowser = ()=>typeof window !== 'undefined';
|
|
60
|
-
const isHexString = (str)=>{
|
|
61
|
-
// Remove 0x prefix if present
|
|
62
|
-
const hex = str.startsWith('0x') ? str.slice(2) : str;
|
|
63
|
-
// Check if string contains only hex characters
|
|
64
|
-
return /^[0-9A-Fa-f]+$/.test(hex);
|
|
65
|
-
};
|
|
66
|
-
const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress })=>{
|
|
67
|
-
return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}.json`;
|
|
68
|
-
};
|
|
69
|
-
const getClientKeyShareBackupInfo = (params)=>{
|
|
70
|
-
var _params_walletProperties, _params_walletProperties_keyShares_;
|
|
71
|
-
const backups = {
|
|
72
|
-
[core.BackupLocation.DYNAMIC]: [],
|
|
73
|
-
[core.BackupLocation.GOOGLE_DRIVE]: [],
|
|
74
|
-
[core.BackupLocation.ICLOUD]: [],
|
|
75
|
-
[core.BackupLocation.USER]: [],
|
|
76
|
-
[core.BackupLocation.EXTERNAL]: []
|
|
77
|
-
};
|
|
78
|
-
if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
|
|
79
|
-
return {
|
|
80
|
-
backups,
|
|
81
|
-
passwordEncrypted: false
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
params.walletProperties.keyShares.forEach((keyShare)=>{
|
|
85
|
-
if (backups[keyShare.backupLocation]) {
|
|
86
|
-
backups[keyShare.backupLocation].push(keyShare.id);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
|
|
90
|
-
return {
|
|
91
|
-
backups,
|
|
92
|
-
passwordEncrypted
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
|
|
97
|
-
* @param existingKeyShares - Array of existing keyshares
|
|
98
|
-
* @param newKeyShares - Array of new keyshares to merge
|
|
99
|
-
* @returns Array of merged unique keyshares
|
|
100
|
-
*/ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
|
|
101
|
-
const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
|
|
102
|
-
if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
|
|
103
|
-
return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
|
|
104
|
-
}));
|
|
105
|
-
return [
|
|
106
|
-
...existingKeyShares,
|
|
107
|
-
...uniqueKeyShares
|
|
108
|
-
];
|
|
109
|
-
};
|
|
110
|
-
const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
|
|
111
|
-
return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* Generic helper function to retry a promise-based operations
|
|
115
|
-
*
|
|
116
|
-
* @param operation - The async operation to retry
|
|
117
|
-
* @param config - Configuration options for retry behavior
|
|
118
|
-
* @returns Promise with the operation result
|
|
119
|
-
* @throws Last error encountered after all retries are exhausted
|
|
120
|
-
*/ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
|
|
121
|
-
let attempts = 0;
|
|
122
|
-
while(attempts < maxAttempts){
|
|
123
|
-
try {
|
|
124
|
-
return await operation();
|
|
125
|
-
} catch (error) {
|
|
126
|
-
attempts++;
|
|
127
|
-
if (attempts === maxAttempts) {
|
|
128
|
-
logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
|
|
129
|
-
error
|
|
130
|
-
}));
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
await new Promise((resolve)=>setTimeout(resolve, retryInterval));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// TypeScript needs this even though it's unreachable
|
|
137
|
-
throw new Error('Unreachable code');
|
|
138
|
-
}
|
|
139
169
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
170
|
+
/**
|
|
171
|
+
* Derives a key using PBKDF2 algorithm
|
|
172
|
+
* @param params - Key derivation parameters
|
|
173
|
+
* @param encryptionConfig - Encryption configuration
|
|
174
|
+
* @returns Promise<CryptoKey>
|
|
175
|
+
*/ const derivePBKDF2Key = async ({ password, salt }, encryptionConfig)=>{
|
|
146
176
|
const passwordBytes = stringToBytes(password);
|
|
147
177
|
const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
|
|
148
178
|
name: 'PBKDF2'
|
|
@@ -150,19 +180,34 @@ const getKey = async ({ password, salt })=>{
|
|
|
150
180
|
'deriveKey'
|
|
151
181
|
]);
|
|
152
182
|
return crypto.subtle.deriveKey({
|
|
153
|
-
name:
|
|
154
|
-
salt,
|
|
155
|
-
iterations:
|
|
156
|
-
hash:
|
|
183
|
+
name: encryptionConfig.keyDerivation,
|
|
184
|
+
salt: salt,
|
|
185
|
+
iterations: encryptionConfig.iterations,
|
|
186
|
+
hash: encryptionConfig.hashAlgorithm
|
|
157
187
|
}, initialKey, {
|
|
158
|
-
name:
|
|
159
|
-
length:
|
|
188
|
+
name: encryptionConfig.algorithm,
|
|
189
|
+
length: encryptionConfig.algorithmLength
|
|
160
190
|
}, false, [
|
|
161
191
|
'encrypt',
|
|
162
192
|
'decrypt'
|
|
163
193
|
]);
|
|
164
194
|
};
|
|
165
|
-
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get the appropriate key derivation function based on the encryption config
|
|
198
|
+
*/ const getKey = async (params, encryptionConfig)=>{
|
|
199
|
+
// Use Argon2 for v3, PBKDF2 for v1 and v2
|
|
200
|
+
if (encryptionConfig.keyDerivation === ARGON2_ALGORITHM) {
|
|
201
|
+
return deriveArgon2Key(params, encryptionConfig);
|
|
202
|
+
} else {
|
|
203
|
+
return derivePBKDF2Key(params, encryptionConfig);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Encrypts data using the specified encryption version.
|
|
208
|
+
* Always uses the latest encryption configuration for new encryptions by default.
|
|
209
|
+
*/ const encryptData = async ({ data, password, version = ENCRYPTION_VERSION_CURRENT })=>{
|
|
210
|
+
const encryptionConfig = getEncryptionConfig(version);
|
|
166
211
|
try {
|
|
167
212
|
// Generate a random salt and IV
|
|
168
213
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
@@ -170,7 +215,7 @@ const encryptData = async ({ data, password })=>{
|
|
|
170
215
|
const key = await getKey({
|
|
171
216
|
password,
|
|
172
217
|
salt
|
|
173
|
-
});
|
|
218
|
+
}, encryptionConfig);
|
|
174
219
|
// Convert the input string to bytes
|
|
175
220
|
const dataBytes = new TextEncoder().encode(data);
|
|
176
221
|
// Encrypt the data
|
|
@@ -182,34 +227,86 @@ const encryptData = async ({ data, password })=>{
|
|
|
182
227
|
return {
|
|
183
228
|
salt: bytesToBase64(salt),
|
|
184
229
|
iv: bytesToBase64(iv),
|
|
185
|
-
cipher: bytesToBase64(new Uint8Array(encryptedData))
|
|
230
|
+
cipher: bytesToBase64(new Uint8Array(encryptedData)),
|
|
231
|
+
version
|
|
186
232
|
};
|
|
187
233
|
} catch (error) {
|
|
188
|
-
|
|
234
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
235
|
+
throw new Error(`Error encrypting data: ${errorMessage} (version: ${version})`);
|
|
189
236
|
}
|
|
190
237
|
};
|
|
191
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Decrypts data with version-based configuration.
|
|
240
|
+
* Uses the version field from the data to determine encryption parameters.
|
|
241
|
+
* Falls back to legacy version for backward compatibility if no version is specified.
|
|
242
|
+
* For v3 (Argon2), retries with parallelism=1 if an OperationError occurs.
|
|
243
|
+
*/ const decryptData = async ({ data, password })=>{
|
|
244
|
+
const { salt, iv, cipher, version } = data;
|
|
245
|
+
// Ensure proper base64 padding for all values
|
|
246
|
+
const paddedSalt = ensureBase64Padding(salt);
|
|
247
|
+
const paddedIv = ensureBase64Padding(iv);
|
|
248
|
+
const paddedCipher = ensureBase64Padding(cipher);
|
|
249
|
+
const saltBytes = base64ToBytes(paddedSalt);
|
|
250
|
+
const ivBytes = base64ToBytes(paddedIv);
|
|
251
|
+
const cipherBytes = base64ToBytes(paddedCipher);
|
|
252
|
+
// Determine which encryption configuration to use
|
|
253
|
+
const encryptionConfig = getEncryptionConfig(version);
|
|
192
254
|
try {
|
|
193
|
-
const { salt, iv, cipher } = data;
|
|
194
|
-
// Ensure proper base64 padding for all values
|
|
195
|
-
const paddedSalt = ensureBase64Padding(salt);
|
|
196
|
-
const paddedIv = ensureBase64Padding(iv);
|
|
197
|
-
const paddedCipher = ensureBase64Padding(cipher);
|
|
198
|
-
const saltBytes = base64ToBytes(paddedSalt);
|
|
199
|
-
const ivBytes = base64ToBytes(paddedIv);
|
|
200
|
-
const cipherBytes = base64ToBytes(paddedCipher);
|
|
201
255
|
const key = await getKey({
|
|
202
256
|
password,
|
|
203
257
|
salt: saltBytes
|
|
204
|
-
});
|
|
258
|
+
}, encryptionConfig);
|
|
205
259
|
const decryptedData = await crypto.subtle.decrypt({
|
|
206
260
|
name: AES_GCM_ALGORITHM,
|
|
207
261
|
iv: ivBytes
|
|
208
262
|
}, key, cipherBytes);
|
|
209
263
|
return new TextDecoder().decode(decryptedData);
|
|
210
264
|
} catch (error) {
|
|
211
|
-
|
|
265
|
+
// For a short period of time we lowered the parallelism for v3 (Argon2) to 1 to try to fix issues
|
|
266
|
+
// for users with limited resources, however this introduced a new issue that the decryption would fail
|
|
267
|
+
// for existing users with v3 (Argon2) encryption, this is a fallback for a few users.
|
|
268
|
+
if (error instanceof Error && error.name === 'OperationError' && version === 'v3' && isArgon2Config(encryptionConfig)) {
|
|
269
|
+
try {
|
|
270
|
+
// Create a modified config with parallelism=1
|
|
271
|
+
const modifiedConfig = _extends({}, encryptionConfig, {
|
|
272
|
+
parallelism: 1
|
|
273
|
+
});
|
|
274
|
+
const key = await getKey({
|
|
275
|
+
password,
|
|
276
|
+
salt: saltBytes
|
|
277
|
+
}, modifiedConfig);
|
|
278
|
+
const decryptedData = await crypto.subtle.decrypt({
|
|
279
|
+
name: AES_GCM_ALGORITHM,
|
|
280
|
+
iv: ivBytes
|
|
281
|
+
}, key, cipherBytes);
|
|
282
|
+
return new TextDecoder().decode(decryptedData);
|
|
283
|
+
} catch (retryError) {
|
|
284
|
+
// If retry also fails, throw the original error with additional context
|
|
285
|
+
throw new Error(`Decryption failed after retry with parallelism=1: ${retryError}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
throw new Error('Decryption failed: ' + error);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* Gets encryption metadata for a specific version.
|
|
293
|
+
* Used when we need to include metadata in legacy systems or APIs that require it.
|
|
294
|
+
*/ const getEncryptionMetadataForVersion = (version)=>{
|
|
295
|
+
const encryptionConfig = getEncryptionConfig(version);
|
|
296
|
+
const metadata = {
|
|
297
|
+
algorithm: encryptionConfig.algorithm,
|
|
298
|
+
keyDerivation: encryptionConfig.keyDerivation,
|
|
299
|
+
iterations: encryptionConfig.iterations,
|
|
300
|
+
hashAlgorithm: encryptionConfig.hashAlgorithm,
|
|
301
|
+
algorithmLength: encryptionConfig.algorithmLength
|
|
302
|
+
};
|
|
303
|
+
// Add Argon2-specific metadata if applicable
|
|
304
|
+
if (isArgon2Config(encryptionConfig)) {
|
|
305
|
+
metadata.memorySize = encryptionConfig.memorySize;
|
|
306
|
+
metadata.parallelism = encryptionConfig.parallelism;
|
|
307
|
+
metadata.hashLength = encryptionConfig.hashLength;
|
|
212
308
|
}
|
|
309
|
+
return metadata;
|
|
213
310
|
};
|
|
214
311
|
|
|
215
312
|
const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
|
|
@@ -263,9 +360,9 @@ const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parent
|
|
|
263
360
|
const result = await response.json();
|
|
264
361
|
return result; // Return file metadata, including file ID
|
|
265
362
|
};
|
|
266
|
-
const listFilesFromGoogleDrive = async ({ accessToken,
|
|
363
|
+
const listFilesFromGoogleDrive = async ({ accessToken, fileName })=>{
|
|
267
364
|
// Step 1: List all files inside `appDataFolder` with the specified backup filename
|
|
268
|
-
const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(`name='${
|
|
365
|
+
const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(`name='${fileName}'`)}&spaces=appDataFolder&orderBy=createdTime desc`, {
|
|
269
366
|
headers: {
|
|
270
367
|
Authorization: `Bearer ${accessToken}`
|
|
271
368
|
}
|
|
@@ -278,10 +375,10 @@ const listFilesFromGoogleDrive = async ({ accessToken, name })=>{
|
|
|
278
375
|
const files = data.files;
|
|
279
376
|
return files;
|
|
280
377
|
};
|
|
281
|
-
const downloadFileFromGoogleDrive = async ({ accessToken,
|
|
378
|
+
const downloadFileFromGoogleDrive = async ({ accessToken, fileName })=>{
|
|
282
379
|
const files = await listFilesFromGoogleDrive({
|
|
283
380
|
accessToken,
|
|
284
|
-
|
|
381
|
+
fileName
|
|
285
382
|
});
|
|
286
383
|
if (!files || files.length === 0) {
|
|
287
384
|
return null;
|
|
@@ -308,382 +405,580 @@ const downloadFileFromGoogleDrive = async ({ accessToken, name })=>{
|
|
|
308
405
|
}
|
|
309
406
|
};
|
|
310
407
|
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const randomKey = `lswt-${Math.random()}${Math.random()}`;
|
|
333
|
-
try {
|
|
334
|
-
globalThis.localStorage.setItem(randomKey, randomKey);
|
|
335
|
-
globalThis.localStorage.removeItem(randomKey);
|
|
336
|
-
localStorageWriteTest.tested = true;
|
|
337
|
-
localStorageWriteTest.writable = true;
|
|
338
|
-
} catch (e) {
|
|
339
|
-
// localStorage can't be written to
|
|
340
|
-
localStorageWriteTest.tested = true;
|
|
341
|
-
localStorageWriteTest.writable = false;
|
|
342
|
-
}
|
|
343
|
-
return localStorageWriteTest.writable;
|
|
344
|
-
};
|
|
345
|
-
/**
|
|
346
|
-
* Provides safe access to the globalThis.localStorage property.
|
|
347
|
-
*/ const localStorageAdapter = {
|
|
348
|
-
getItem: (key)=>{
|
|
349
|
-
if (!supportsLocalStorage()) {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
return globalThis.localStorage.getItem(key);
|
|
353
|
-
},
|
|
354
|
-
removeItem: (key)=>{
|
|
355
|
-
if (!supportsLocalStorage()) {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
globalThis.localStorage.removeItem(key);
|
|
359
|
-
},
|
|
360
|
-
setItem: (key, value)=>{
|
|
361
|
-
if (!supportsLocalStorage()) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
globalThis.localStorage.setItem(key, value);
|
|
408
|
+
const handleAxiosError = (error, message, context)=>{
|
|
409
|
+
var _error_response, _error_response1, _error_response2;
|
|
410
|
+
logger.error('[DynamicWaasWalletClient] Axios error: ', {
|
|
411
|
+
message,
|
|
412
|
+
error: (_error_response = error.response) == null ? void 0 : _error_response.data,
|
|
413
|
+
status: (_error_response1 = error.response) == null ? void 0 : _error_response1.status,
|
|
414
|
+
context
|
|
415
|
+
});
|
|
416
|
+
switch((_error_response2 = error.response) == null ? void 0 : _error_response2.status){
|
|
417
|
+
case 400:
|
|
418
|
+
throw createHttpError(400, 'Invalid request');
|
|
419
|
+
case 401:
|
|
420
|
+
throw createHttpError(401, 'Authorization header or cookie is required');
|
|
421
|
+
case 403:
|
|
422
|
+
throw createHttpError(403, 'Forbidden');
|
|
423
|
+
case 422:
|
|
424
|
+
throw createHttpError(422, 'Unprocessable content');
|
|
425
|
+
case 500:
|
|
426
|
+
throw createHttpError(500, 'Internal server error');
|
|
427
|
+
default:
|
|
428
|
+
throw createHttpError(500, 'Internal server error');
|
|
365
429
|
}
|
|
366
430
|
};
|
|
367
|
-
/**
|
|
368
|
-
* Returns a localStorage-like object that stores the key-value pairs in
|
|
369
|
-
* memory.
|
|
370
|
-
*/ const memoryLocalStorageAdapter = (store = {})=>({
|
|
371
|
-
getItem: (key)=>store[key] || null,
|
|
372
|
-
removeItem: (key)=>{
|
|
373
|
-
delete store[key];
|
|
374
|
-
},
|
|
375
|
-
setItem: (key, value)=>{
|
|
376
|
-
store[key] = value;
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
431
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const stringifiedValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
393
|
-
return this.requestChannel.request('setItem', {
|
|
394
|
-
source: 'localStorage',
|
|
395
|
-
key,
|
|
396
|
-
data: stringifiedValue
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
async removeItem(key) {
|
|
400
|
-
return this.requestChannel.request('deleteItem', {
|
|
401
|
-
source: 'localStorage',
|
|
402
|
-
key
|
|
432
|
+
const logger = new logger$1.Logger('DynamicWaasWalletClient', logger$1.LogLevel.DEBUG);
|
|
433
|
+
const setLoggerContext = ({ environmentId, authMode = core.AuthMode.HEADER, sessionId = undefined, userId = undefined })=>{
|
|
434
|
+
try {
|
|
435
|
+
logger$1.Logger.setEnvironmentId(environmentId);
|
|
436
|
+
logger$1.Logger.globalMetaData.set('sid', sessionId);
|
|
437
|
+
logger$1.Logger.globalMetaData.set('user_id', userId);
|
|
438
|
+
logger$1.Logger.globalMetaData.set('auth_mode', authMode);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
logError({
|
|
441
|
+
message: '[DynamicWaasWalletClient] Error setting logger context',
|
|
442
|
+
error: error,
|
|
443
|
+
context: {}
|
|
403
444
|
});
|
|
404
445
|
}
|
|
405
|
-
|
|
406
|
-
|
|
446
|
+
};
|
|
447
|
+
const logError = ({ message, error, context })=>{
|
|
448
|
+
if (error instanceof axios.AxiosError) {
|
|
449
|
+
handleAxiosError(error, message, context);
|
|
407
450
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (!(iframe == null ? void 0 : iframe.contentWindow)) {
|
|
412
|
-
throw new Error('Iframe or contentWindow not available');
|
|
413
|
-
}
|
|
414
|
-
const logger = new logger$1.Logger('debug');
|
|
415
|
-
messageTransport$1.on((message)=>{
|
|
416
|
-
// Forward the message to webview via postMessage
|
|
417
|
-
if (message.origin === 'host') {
|
|
418
|
-
var _iframe_contentWindow;
|
|
419
|
-
logger.debug(`[host bridge] host --> bridge --> iframe`, message);
|
|
420
|
-
iframe == null ? void 0 : (_iframe_contentWindow = iframe.contentWindow) == null ? void 0 : _iframe_contentWindow.postMessage(message, iframeOrigin);
|
|
421
|
-
}
|
|
451
|
+
logger.error('[DynamicWaasWalletClient] Error in browser client', {
|
|
452
|
+
error: error instanceof Error ? error.message : String(error),
|
|
453
|
+
context
|
|
422
454
|
});
|
|
423
|
-
const handleIncomingMessage = (message)=>{
|
|
424
|
-
const { data } = message;
|
|
425
|
-
if (!data) return;
|
|
426
|
-
if ((data == null ? void 0 : data.origin) !== 'webview') {
|
|
427
|
-
logger.debug(`skipped message: ${JSON.stringify(data)}: origin is not host`);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (typeof data !== 'object') {
|
|
431
|
-
logger.debug(`skipped message: ${JSON.stringify(data)}: data is not an object`);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
try {
|
|
435
|
-
const message = messageTransport.parseMessageTransportData(data);
|
|
436
|
-
logger.debug(`[host bridge] iframe --> bridge --> host`, message);
|
|
437
|
-
messageTransport$1.emit(message);
|
|
438
|
-
} catch (error) {
|
|
439
|
-
if (!(error instanceof SyntaxError)) {
|
|
440
|
-
logger.error('Error handling incoming message:', error);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
/**
|
|
445
|
-
* Handle incoming message from android client
|
|
446
|
-
*/ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
447
|
-
// @ts-ignore
|
|
448
|
-
document.addEventListener('message', handleIncomingMessage);
|
|
449
|
-
/**
|
|
450
|
-
* Handle incoming message from iOS client
|
|
451
|
-
*/ window.addEventListener('message', handleIncomingMessage);
|
|
452
455
|
};
|
|
453
456
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
}
|
|
457
|
+
const DEFAULT_LOG_LEVEL = logger$1.LogLevel.DEBUG; //todo: change back to info when done debugging
|
|
458
|
+
const STORAGE_KEY = 'dynamic-waas-wallet-client';
|
|
459
|
+
const CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX = 'dynamicWalletKeyShareBackup';
|
|
460
|
+
const SIGNED_SESSION_ID_MIN_VERSION = '4.25.4';
|
|
461
|
+
// Namespace-specific version requirements
|
|
462
|
+
const SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE = {
|
|
463
|
+
WalletKit: '4.25.4',
|
|
464
|
+
ClientSDK: '0.1.0-alpha.0'
|
|
465
|
+
};
|
|
465
466
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
467
|
+
const isBrowser = ()=>typeof window !== 'undefined';
|
|
468
|
+
const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress, isGoogleDrive = false })=>{
|
|
469
|
+
const suffix = isGoogleDrive ? '-google-drive' : '';
|
|
470
|
+
return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}${suffix}.json`;
|
|
471
|
+
};
|
|
472
|
+
const getClientKeyShareBackupInfo = (params)=>{
|
|
473
|
+
var _params_walletProperties, _params_walletProperties_keyShares_;
|
|
474
|
+
const backups = {
|
|
475
|
+
[core.BackupLocation.DYNAMIC]: [],
|
|
476
|
+
[core.BackupLocation.GOOGLE_DRIVE]: [],
|
|
477
|
+
[core.BackupLocation.ICLOUD]: [],
|
|
478
|
+
[core.BackupLocation.USER]: [],
|
|
479
|
+
[core.BackupLocation.EXTERNAL]: [],
|
|
480
|
+
[core.BackupLocation.DELEGATED]: []
|
|
481
|
+
};
|
|
482
|
+
if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
|
|
483
|
+
return {
|
|
484
|
+
backups,
|
|
485
|
+
passwordEncrypted: false
|
|
486
|
+
};
|
|
476
487
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
* iframeDisplay: IframeDisplayChannelAdapter;
|
|
484
|
-
* cleanup: () => void;
|
|
485
|
-
* }>} A promise that resolves when the iframe is loaded.
|
|
486
|
-
*/ async initializeIframeDisplayForContainer({ container }) {
|
|
487
|
-
try {
|
|
488
|
-
const iframe = await this.loadIframeForContainer(container);
|
|
489
|
-
const transport = messageTransport.applyDefaultMessageOrigin({
|
|
490
|
-
defaultOrigin: 'host',
|
|
491
|
-
messageTransport: messageTransport.createMessageTransport()
|
|
488
|
+
params.walletProperties.keyShares.forEach((keyShare)=>{
|
|
489
|
+
if (backups[keyShare.backupLocation]) {
|
|
490
|
+
backups[keyShare.backupLocation].push({
|
|
491
|
+
location: keyShare.backupLocation,
|
|
492
|
+
keyShareId: keyShare.id,
|
|
493
|
+
externalKeyShareId: keyShare == null ? void 0 : keyShare.externalKeyShareId
|
|
492
494
|
});
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
|
|
498
|
+
return {
|
|
499
|
+
backups,
|
|
500
|
+
passwordEncrypted
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
|
|
505
|
+
* @param existingKeyShares - Array of existing keyshares
|
|
506
|
+
* @param newKeyShares - Array of new keyshares to merge
|
|
507
|
+
* @returns Array of merged unique keyshares
|
|
508
|
+
*/ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
|
|
509
|
+
const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
|
|
510
|
+
if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
|
|
511
|
+
return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
|
|
512
|
+
}));
|
|
513
|
+
return [
|
|
514
|
+
...existingKeyShares,
|
|
515
|
+
...uniqueKeyShares
|
|
516
|
+
];
|
|
517
|
+
};
|
|
518
|
+
const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
|
|
519
|
+
return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Generic helper function to retry a promise-based operations
|
|
523
|
+
*
|
|
524
|
+
* @param operation - The async operation to retry
|
|
525
|
+
* @param config - Configuration options for retry behavior
|
|
526
|
+
* @returns Promise with the operation result
|
|
527
|
+
* @throws Last error encountered after all retries are exhausted
|
|
528
|
+
*/ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
|
|
529
|
+
let attempts = 0;
|
|
530
|
+
while(attempts < maxAttempts){
|
|
531
|
+
try {
|
|
532
|
+
return await operation();
|
|
533
|
+
} catch (error) {
|
|
534
|
+
var _error_response;
|
|
535
|
+
attempts++;
|
|
536
|
+
logger.warn(`Failed to execute ${operationName} on attempt ${attempts}`, _extends({}, logContext, {
|
|
537
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
538
|
+
axiosError: error instanceof axios.AxiosError ? (_error_response = error.response) == null ? void 0 : _error_response.data : undefined
|
|
539
|
+
}));
|
|
540
|
+
if (attempts === maxAttempts) {
|
|
541
|
+
var _error_response1;
|
|
542
|
+
logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
|
|
543
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
544
|
+
axiosError: error instanceof axios.AxiosError ? (_error_response1 = error.response) == null ? void 0 : _error_response1.data : undefined
|
|
545
|
+
}));
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
// Calculate exponential backoff delay
|
|
549
|
+
const exponentialDelay = retryInterval * 2 ** (attempts - 1);
|
|
550
|
+
await new Promise((resolve)=>setTimeout(resolve, exponentialDelay));
|
|
527
551
|
}
|
|
528
552
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
reject(new Error('Iframe load timeout'));
|
|
537
|
-
}, 10000);
|
|
538
|
-
iframe.style.display = 'none';
|
|
539
|
-
iframe.setAttribute('title', 'Dynamic Wallet Iframe');
|
|
540
|
-
iframe.style.position = 'fixed';
|
|
541
|
-
iframe.style.top = '0';
|
|
542
|
-
iframe.style.left = '0';
|
|
543
|
-
iframe.style.width = '0';
|
|
544
|
-
iframe.style.height = '0';
|
|
545
|
-
iframe.style.border = 'none';
|
|
546
|
-
iframe.style.pointerEvents = 'none';
|
|
547
|
-
const params = new URLSearchParams({
|
|
548
|
-
instanceId: this.instanceId,
|
|
549
|
-
hostOrigin: window.location.origin
|
|
550
|
-
});
|
|
551
|
-
iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
|
|
552
|
-
this.logger.debug('Creating iframe with src:', iframe.src);
|
|
553
|
-
document.body.appendChild(iframe);
|
|
554
|
-
iframe.onload = ()=>{
|
|
555
|
-
clearTimeout(iframeTimeoutId);
|
|
556
|
-
this.logger.debug('Iframe loaded successfully');
|
|
557
|
-
this.iframe = iframe;
|
|
558
|
-
resolve();
|
|
559
|
-
};
|
|
560
|
-
iframe.onerror = (error)=>{
|
|
561
|
-
clearTimeout(iframeTimeoutId);
|
|
562
|
-
this.logger.error('Iframe failed to load:', error);
|
|
563
|
-
reject(new Error('Failed to load iframe'));
|
|
564
|
-
};
|
|
565
|
-
});
|
|
553
|
+
// TypeScript needs this even though it's unreachable
|
|
554
|
+
throw new Error('Unreachable code');
|
|
555
|
+
}
|
|
556
|
+
const formatEvmMessage = (message)=>{
|
|
557
|
+
if (typeof message === 'string' && message.startsWith('0x')) {
|
|
558
|
+
const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
|
|
559
|
+
return web.MessageHash.keccak256(serializedTxBytes);
|
|
566
560
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
561
|
+
return web.MessageHash.keccak256(message);
|
|
562
|
+
};
|
|
563
|
+
const isHexString = (str)=>{
|
|
564
|
+
// Remove 0x prefix if present
|
|
565
|
+
const hex = str.startsWith('0x') ? str.slice(2) : str;
|
|
566
|
+
// Check if string contains only hex characters
|
|
567
|
+
return /^[0-9A-Fa-f]+$/.test(hex);
|
|
568
|
+
};
|
|
569
|
+
const formatSolanaMessage = (message)=>{
|
|
570
|
+
if (typeof message === 'string') {
|
|
571
|
+
if (!isHexString(message)) {
|
|
572
|
+
return Buffer.from(message).toString('hex');
|
|
573
|
+
} else {
|
|
574
|
+
return new Uint8Array(Buffer.from(message, 'hex'));
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
return message;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
const formatMessage = (chainName, message)=>{
|
|
581
|
+
switch(chainName){
|
|
582
|
+
case 'EVM':
|
|
583
|
+
return formatEvmMessage(message);
|
|
584
|
+
case 'SVM':
|
|
585
|
+
return formatSolanaMessage(message);
|
|
586
|
+
case 'SUI':
|
|
587
|
+
return message;
|
|
588
|
+
default:
|
|
589
|
+
throw new Error('Unsupported chain name');
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
const getGoogleOAuthAccountId = (verifiedCredentials)=>{
|
|
593
|
+
const googleVerifiedCredential = verifiedCredentials == null ? void 0 : verifiedCredentials.find((credential)=>credential.oauthProvider === sdkApiCore.ProviderEnum.Google);
|
|
594
|
+
return googleVerifiedCredential == null ? void 0 : googleVerifiedCredential.id;
|
|
595
|
+
};
|
|
596
|
+
const createBackupData = ({ encryptedKeyShares, accountAddress, thresholdSignatureScheme, hasPassword = true })=>{
|
|
597
|
+
return {
|
|
598
|
+
keyShares: encryptedKeyShares,
|
|
599
|
+
metadata: {
|
|
600
|
+
createdAt: new Date().toISOString(),
|
|
601
|
+
accountAddress,
|
|
602
|
+
thresholdSignatureScheme,
|
|
603
|
+
hasPassword,
|
|
604
|
+
encryption: getEncryptionMetadataForVersion(ENCRYPTION_VERSION_CURRENT),
|
|
605
|
+
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
606
|
+
shareCount: encryptedKeyShares.length
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
};
|
|
610
|
+
const downloadStringAsFile = ({ filename, content, mimeType = 'application/json' })=>{
|
|
611
|
+
const blob = new Blob([
|
|
612
|
+
content
|
|
613
|
+
], {
|
|
614
|
+
type: mimeType
|
|
615
|
+
});
|
|
616
|
+
const url = URL.createObjectURL(blob);
|
|
617
|
+
const a = document.createElement('a');
|
|
618
|
+
a.href = url;
|
|
619
|
+
a.download = filename;
|
|
620
|
+
a.click();
|
|
621
|
+
URL.revokeObjectURL(url);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Uploads a backup to Google Drive App
|
|
626
|
+
* @param accessToken - The access token for the Google Drive API
|
|
627
|
+
* @param fileName - The name of the file to upload
|
|
628
|
+
* @param backupData - The data to upload
|
|
629
|
+
* @param accountAddress - The account address associated with the backup
|
|
630
|
+
*/ const uploadBackupToGoogleDrive = async ({ accessToken, fileName, backupData, accountAddress })=>{
|
|
631
|
+
const uploadPromises = [
|
|
632
|
+
retryPromise(()=>uploadFileToGoogleDriveAppStorage({
|
|
633
|
+
accessToken,
|
|
634
|
+
fileName,
|
|
635
|
+
jsonData: backupData
|
|
636
|
+
})),
|
|
637
|
+
retryPromise(()=>uploadFileToGoogleDrivePersonal({
|
|
638
|
+
accessToken,
|
|
639
|
+
fileName,
|
|
640
|
+
jsonData: backupData
|
|
641
|
+
}))
|
|
642
|
+
];
|
|
643
|
+
const results = await Promise.allSettled(uploadPromises);
|
|
644
|
+
const errors = [];
|
|
645
|
+
// Check App Storage result
|
|
646
|
+
if (results[0].status === 'rejected') {
|
|
647
|
+
const error = results[0].reason;
|
|
648
|
+
logger.error('[DynamicWaasWalletClient] Failed to upload keyshares to Google Drive App Storage', {
|
|
649
|
+
accountAddress,
|
|
650
|
+
error
|
|
601
651
|
});
|
|
652
|
+
errors.push(`Failed to backup keyshares to Google Drive App Storage: ${error instanceof Error ? error.message : String(error)}`);
|
|
602
653
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
654
|
+
// Check Personal Drive result
|
|
655
|
+
if (results[1].status === 'rejected') {
|
|
656
|
+
const error = results[1].reason;
|
|
657
|
+
logger.error('[DynamicWaasWalletClient] Failed to upload keyshares to Google Drive Personal', {
|
|
658
|
+
accountAddress,
|
|
659
|
+
error
|
|
609
660
|
});
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
661
|
+
errors.push(`Failed to backup keyshares to Google Drive Personal: ${error instanceof Error ? error.message : String(error)}`);
|
|
662
|
+
}
|
|
663
|
+
// Throw if any uploads failed
|
|
664
|
+
if (errors.length > 0) {
|
|
665
|
+
throw new Error(`[DynamicWaasWalletClient] ${errors.join('; ')}`);
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const ALG_LABEL_RSA = 'HYBRID-RSA-AES-256';
|
|
670
|
+
/**
|
|
671
|
+
* Convert base64 to base64url encoding
|
|
672
|
+
*/ const toBase64Url = (buffer)=>{
|
|
673
|
+
const base64 = Buffer.from(buffer).toString('base64');
|
|
674
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
675
|
+
};
|
|
676
|
+
/**
|
|
677
|
+
* Convert ArrayBuffer to base64url
|
|
678
|
+
*/ const arrayBufferToBase64Url = (buffer)=>{
|
|
679
|
+
return toBase64Url(buffer);
|
|
680
|
+
};
|
|
681
|
+
/**
|
|
682
|
+
* Import RSA public key from PEM format
|
|
683
|
+
*/ const importRSAPublicKey = async (publicKeyPem)=>{
|
|
684
|
+
// Remove PEM headers and decode base64
|
|
685
|
+
const pemHeader = '-----BEGIN PUBLIC KEY-----';
|
|
686
|
+
const pemFooter = '-----END PUBLIC KEY-----';
|
|
687
|
+
const pemContents = publicKeyPem.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, '');
|
|
688
|
+
const binaryDer = Buffer.from(pemContents, 'base64').toString('binary');
|
|
689
|
+
const keyData = new Uint8Array(binaryDer.length);
|
|
690
|
+
for(let i = 0; i < binaryDer.length; i++){
|
|
691
|
+
keyData[i] = binaryDer.charCodeAt(i);
|
|
692
|
+
}
|
|
693
|
+
return await crypto.subtle.importKey('spki', keyData, {
|
|
694
|
+
name: 'RSA-OAEP',
|
|
695
|
+
hash: 'SHA-256'
|
|
696
|
+
}, false, [
|
|
697
|
+
'encrypt'
|
|
698
|
+
]);
|
|
699
|
+
};
|
|
700
|
+
// encodedEnvelopeBytes intentionally omitted; alg/ct/ek/iv/tag/kid is sufficient
|
|
701
|
+
/**
|
|
702
|
+
* Encrypts data using HYBRID-RSA-AES-256 encryption scheme with Web Crypto API.
|
|
703
|
+
* 1. Generate random AES-256 key
|
|
704
|
+
* 2. Encrypt AES key with RSA public key
|
|
705
|
+
* 3. Encrypt data with AES-256-GCM
|
|
706
|
+
*/ const encryptDelegatedKeyShare = async (data, publicKeyPem, keyId)=>{
|
|
707
|
+
try {
|
|
708
|
+
// Step 1: Generate a random AES-256 key and 16-byte IV
|
|
709
|
+
const aesKey = await crypto.subtle.generateKey({
|
|
710
|
+
name: 'AES-GCM',
|
|
711
|
+
length: 256
|
|
712
|
+
}, true, [
|
|
713
|
+
'encrypt'
|
|
714
|
+
]);
|
|
715
|
+
const iv = crypto.getRandomValues(new Uint8Array(16)); // 128-bit IV for GCM
|
|
716
|
+
// Step 2: Encrypt the data with AES-256-GCM
|
|
717
|
+
const plaintext = new TextEncoder().encode(data);
|
|
718
|
+
const encryptedData = await crypto.subtle.encrypt({
|
|
719
|
+
name: 'AES-GCM',
|
|
720
|
+
iv: iv
|
|
721
|
+
}, aesKey, plaintext);
|
|
722
|
+
// Extract the auth tag from the encrypted data (last 16 bytes)
|
|
723
|
+
const encryptedDataArray = new Uint8Array(encryptedData);
|
|
724
|
+
const authTag = encryptedDataArray.slice(-16);
|
|
725
|
+
const ciphertext = encryptedDataArray.slice(0, -16);
|
|
726
|
+
// Step 3: Encrypt the AES key with RSA public key
|
|
727
|
+
const rsaPublicKey = await importRSAPublicKey(publicKeyPem);
|
|
728
|
+
// Export the AES key to encrypt it
|
|
729
|
+
const aesKeyData = await crypto.subtle.exportKey('raw', aesKey);
|
|
730
|
+
const encryptedAesKey = await crypto.subtle.encrypt({
|
|
731
|
+
name: 'RSA-OAEP'
|
|
732
|
+
}, rsaPublicKey, aesKeyData);
|
|
733
|
+
return _extends({
|
|
734
|
+
alg: ALG_LABEL_RSA,
|
|
735
|
+
iv: arrayBufferToBase64Url(iv.buffer),
|
|
736
|
+
ct: arrayBufferToBase64Url(ciphertext.buffer),
|
|
737
|
+
tag: arrayBufferToBase64Url(authTag.buffer),
|
|
738
|
+
ek: arrayBufferToBase64Url(encryptedAesKey)
|
|
739
|
+
}, keyId ? {
|
|
740
|
+
kid: keyId
|
|
741
|
+
} : {});
|
|
742
|
+
} catch (error) {
|
|
743
|
+
throw new Error(`Encryption failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const localStorageWriteTest = {
|
|
748
|
+
tested: false,
|
|
749
|
+
writable: false
|
|
750
|
+
};
|
|
751
|
+
/**
|
|
752
|
+
* Checks whether localStorage is supported on this browser.
|
|
753
|
+
*/ const supportsLocalStorage = ()=>{
|
|
754
|
+
if (!isBrowser()) {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
if (typeof globalThis.localStorage !== 'object') {
|
|
759
|
+
return false;
|
|
613
760
|
}
|
|
614
|
-
|
|
761
|
+
} catch (e) {
|
|
762
|
+
// DOM exception when accessing `localStorage`
|
|
763
|
+
return false;
|
|
615
764
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
765
|
+
if (localStorageWriteTest.tested) {
|
|
766
|
+
return localStorageWriteTest.writable;
|
|
767
|
+
}
|
|
768
|
+
const randomKey = `lswt-${Math.random()}${Math.random()}`;
|
|
769
|
+
try {
|
|
770
|
+
globalThis.localStorage.setItem(randomKey, randomKey);
|
|
771
|
+
globalThis.localStorage.removeItem(randomKey);
|
|
772
|
+
localStorageWriteTest.tested = true;
|
|
773
|
+
localStorageWriteTest.writable = true;
|
|
774
|
+
} catch (e) {
|
|
775
|
+
// localStorage can't be written to
|
|
776
|
+
localStorageWriteTest.tested = true;
|
|
777
|
+
localStorageWriteTest.writable = false;
|
|
778
|
+
}
|
|
779
|
+
return localStorageWriteTest.writable;
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Provides safe access to the globalThis.localStorage property.
|
|
783
|
+
*/ const localStorageAdapter = {
|
|
784
|
+
getItem: (key)=>{
|
|
785
|
+
if (!supportsLocalStorage()) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
return globalThis.localStorage.getItem(key);
|
|
789
|
+
},
|
|
790
|
+
removeItem: (key)=>{
|
|
791
|
+
if (!supportsLocalStorage()) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
globalThis.localStorage.removeItem(key);
|
|
795
|
+
},
|
|
796
|
+
setItem: (key, value)=>{
|
|
797
|
+
if (!supportsLocalStorage()) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
globalThis.localStorage.setItem(key, value);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
/**
|
|
804
|
+
* Returns a localStorage-like object that stores the key-value pairs in
|
|
805
|
+
* memory.
|
|
806
|
+
*/ const memoryLocalStorageAdapter = (store = {})=>({
|
|
807
|
+
getItem: (key)=>store[key] || null,
|
|
808
|
+
removeItem: (key)=>{
|
|
809
|
+
delete store[key];
|
|
810
|
+
},
|
|
811
|
+
setItem: (key, value)=>{
|
|
812
|
+
store[key] = value;
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
const createDelegationWithGoogleDriveDistribution = ({ existingShares, delegatedShare })=>({
|
|
817
|
+
dynamicBackendShares: existingShares,
|
|
818
|
+
googleDriveShares: existingShares,
|
|
819
|
+
delegatedShare
|
|
820
|
+
});
|
|
821
|
+
const createDelegationOnlyDistribution = ({ existingShares, delegatedShare })=>({
|
|
822
|
+
dynamicBackendShares: existingShares,
|
|
823
|
+
googleDriveShares: [],
|
|
824
|
+
delegatedShare
|
|
825
|
+
});
|
|
826
|
+
const createGoogleDriveOnlyDistribution = ({ allShares })=>({
|
|
827
|
+
dynamicBackendShares: allShares.slice(0, -1),
|
|
828
|
+
googleDriveShares: allShares.slice(-1)
|
|
829
|
+
});
|
|
830
|
+
const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
831
|
+
dynamicBackendShares: allShares,
|
|
832
|
+
googleDriveShares: []
|
|
833
|
+
});
|
|
834
|
+
const hasGoogleDriveBackup = (backupInfo)=>{
|
|
835
|
+
var _backupInfo_backups_BackupLocation_GOOGLE_DRIVE, _backupInfo_backups;
|
|
836
|
+
var _backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length;
|
|
837
|
+
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;
|
|
838
|
+
};
|
|
839
|
+
const hasDelegatedBackup = (backupInfo)=>{
|
|
840
|
+
var _backupInfo_backups_BackupLocation_DELEGATED, _backupInfo_backups;
|
|
841
|
+
var _backupInfo_backups_BackupLocation_DELEGATED_length;
|
|
842
|
+
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;
|
|
843
|
+
};
|
|
844
|
+
/**
|
|
845
|
+
* Distribution for adding Google Drive backup to an existing delegation.
|
|
846
|
+
* Client's shares go to both Dynamic and Google Drive.
|
|
847
|
+
* delegatedShare is undefined - we don't re-publish, but preserve the location.
|
|
848
|
+
*/ const createAddGoogleDriveToExistingDelegationDistribution = ({ clientShares })=>({
|
|
849
|
+
dynamicBackendShares: clientShares,
|
|
850
|
+
googleDriveShares: clientShares
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
class DynamicWalletClient {
|
|
854
|
+
async initializeForwardMPCClient() {
|
|
855
|
+
try {
|
|
856
|
+
await this.apiClient.forwardMPCClient.connect();
|
|
857
|
+
this.logger.info('Connected to ForwardMPC enclave websocket. Instance: ' + this.instanceId);
|
|
858
|
+
try {
|
|
859
|
+
await this.apiClient.forwardMPCClient.handshake();
|
|
860
|
+
this.logger.debug('Handshaked with ForwardMPC enclave websocket. Instance: ' + this.instanceId);
|
|
861
|
+
} catch (error) {
|
|
862
|
+
this.logger.error('Error handshaking with ForwardMPC enclave websocket. Instance: ' + this.instanceId, error);
|
|
863
|
+
}
|
|
864
|
+
} catch (error) {
|
|
865
|
+
this.logger.error('Error connecting to ForwardMPC enclave websocket. Instance: ' + this.instanceId, {
|
|
866
|
+
error,
|
|
867
|
+
environmentId: this.environmentId
|
|
868
|
+
});
|
|
621
869
|
}
|
|
622
|
-
|
|
870
|
+
}
|
|
871
|
+
getAuthMode() {
|
|
872
|
+
return this.authMode;
|
|
623
873
|
}
|
|
624
874
|
/**
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
875
|
+
* Check if the SDK version meets the requirement for signed session ID
|
|
876
|
+
* Uses namespace-specific version requirements when available
|
|
877
|
+
* @returns boolean indicating if requireSignedSessionId should be set to true
|
|
878
|
+
*/ requiresSignedSessionId() {
|
|
879
|
+
if (!this.sdkVersion) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
try {
|
|
883
|
+
const parsedVersion = core.parseNamespacedVersion(this.sdkVersion);
|
|
884
|
+
if (!parsedVersion) {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
const { namespace, version } = parsedVersion;
|
|
888
|
+
// Check if we have a namespace-specific version requirement
|
|
889
|
+
const namespaceMinVersion = SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE[namespace];
|
|
890
|
+
if (namespaceMinVersion) {
|
|
891
|
+
return semver.gte(version, namespaceMinVersion);
|
|
892
|
+
}
|
|
893
|
+
// Fall back to default version requirement
|
|
894
|
+
return semver.gte(version, SIGNED_SESSION_ID_MIN_VERSION);
|
|
895
|
+
} catch (error) {
|
|
896
|
+
logger.warn(`[DynamicWaasWalletClient] Error checking if requiresSignedSessionId should be set to true for version ${this.sdkVersion}`, error);
|
|
897
|
+
return false;
|
|
637
898
|
}
|
|
638
|
-
|
|
639
|
-
|
|
899
|
+
}
|
|
900
|
+
async initLoggerContext(authToken) {
|
|
901
|
+
// only decode jwt token in header auth mode
|
|
902
|
+
if (this.authMode === 'cookie') {
|
|
903
|
+
setLoggerContext({
|
|
904
|
+
environmentId: this.environmentId,
|
|
905
|
+
authMode: this.authMode
|
|
906
|
+
});
|
|
907
|
+
return;
|
|
640
908
|
}
|
|
641
|
-
|
|
642
|
-
|
|
909
|
+
try {
|
|
910
|
+
// Split the JWT token into its parts (header.payload.signature)
|
|
911
|
+
const parts = authToken.split('.');
|
|
912
|
+
if (parts.length !== 3) {
|
|
913
|
+
logger.debug('[DynamicWaasWalletClient] Invalid JWT token when initializing logger context');
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// Decode the payload (second part)
|
|
917
|
+
const base64Payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
918
|
+
const jsonPayload = decodeURIComponent(Buffer.from(base64Payload, 'base64').toString('utf-8'));
|
|
919
|
+
const payload = JSON.parse(jsonPayload);
|
|
920
|
+
if (!payload || typeof payload !== 'object') {
|
|
921
|
+
logger.debug('[DynamicWaasWalletClient] Invalid JWT payload when initializing logger context');
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const userId = payload['sub'] || 'unknown';
|
|
925
|
+
const sessionId = payload['sid'] || 'unknown';
|
|
926
|
+
setLoggerContext({
|
|
927
|
+
environmentId: this.environmentId,
|
|
928
|
+
authMode: this.authMode,
|
|
929
|
+
sessionId,
|
|
930
|
+
userId
|
|
931
|
+
});
|
|
932
|
+
this.sessionId = sessionId;
|
|
933
|
+
} catch (error) {
|
|
934
|
+
logError({
|
|
935
|
+
message: 'Error initializing logger context',
|
|
936
|
+
error: error,
|
|
937
|
+
context: {}
|
|
938
|
+
});
|
|
939
|
+
throw error;
|
|
643
940
|
}
|
|
644
|
-
return this.iframe;
|
|
645
941
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
* Subsequent calls will return the same storage instance immediately.
|
|
650
|
-
*
|
|
651
|
-
* @throws {Error} If iframe storage initialization fails
|
|
652
|
-
* @returns {Promise<SupportedStorage>} The initialized iframe storage instance
|
|
653
|
-
*/ async getIframeStorage() {
|
|
654
|
-
await this.initializeIframeCommunication();
|
|
655
|
-
if (!this.iframeStorage) {
|
|
656
|
-
throw new Error('Failed to initialize iframe storage');
|
|
942
|
+
async initialize(traceContext) {
|
|
943
|
+
if (this.initializePromise) {
|
|
944
|
+
return await this.initializePromise;
|
|
657
945
|
}
|
|
658
|
-
|
|
946
|
+
this.logger.debug('[DynamicWaasWalletClient] Initializing Dynamic Waas Wallet SDK');
|
|
947
|
+
this.initializePromise = this._initialize(traceContext);
|
|
948
|
+
const result = await this.initializePromise;
|
|
949
|
+
this.logger.debug('[DynamicWaasWalletClient] Dynamic Waas Wallet SDK initialized');
|
|
950
|
+
return result;
|
|
659
951
|
}
|
|
660
952
|
/**
|
|
661
953
|
* Client initialization logic
|
|
662
|
-
*/ async _initialize() {
|
|
954
|
+
*/ async _initialize(traceContext) {
|
|
663
955
|
try {
|
|
664
956
|
const initializePromises = [
|
|
665
|
-
this.restoreWallets()
|
|
666
|
-
this.initializeIframeCommunication()
|
|
957
|
+
this.restoreWallets()
|
|
667
958
|
];
|
|
668
959
|
await Promise.all(initializePromises);
|
|
669
960
|
return {
|
|
670
|
-
error: null
|
|
961
|
+
error: null,
|
|
962
|
+
traceContext
|
|
671
963
|
};
|
|
672
964
|
} catch (error) {
|
|
673
965
|
return {
|
|
674
|
-
error
|
|
966
|
+
error,
|
|
967
|
+
traceContext
|
|
675
968
|
};
|
|
676
969
|
}
|
|
677
970
|
}
|
|
678
|
-
async serverInitializeKeyGen({ chainName, clientKeygenIds, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
971
|
+
async serverInitializeKeyGen({ chainName, clientKeygenIds, dynamicRequestId, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
679
972
|
// Initialize keygen, create room, and create the wallet account on the server
|
|
680
973
|
const data = await this.apiClient.createWalletAccount({
|
|
681
974
|
chainName,
|
|
682
975
|
clientKeygenIds,
|
|
976
|
+
dynamicRequestId,
|
|
683
977
|
thresholdSignatureScheme,
|
|
684
978
|
onError,
|
|
685
979
|
onCeremonyComplete
|
|
686
980
|
});
|
|
981
|
+
this.logger.debug(`[DynamicWaasWalletClient] Initialized client key generation with IDs: ${clientKeygenIds.join(', ')} for chain: ${chainName} using scheme: ${thresholdSignatureScheme}`);
|
|
687
982
|
return data;
|
|
688
983
|
}
|
|
689
984
|
async clientInitializeKeyGen({ chainName, thresholdSignatureScheme }) {
|
|
@@ -704,8 +999,8 @@ class DynamicWalletClient {
|
|
|
704
999
|
let publicKey;
|
|
705
1000
|
if (mpcSigner instanceof web.Ecdsa) {
|
|
706
1001
|
publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
|
|
707
|
-
} else if (mpcSigner instanceof web.
|
|
708
|
-
publicKey = await mpcSigner.
|
|
1002
|
+
} else if (mpcSigner instanceof web.ExportableEd25519) {
|
|
1003
|
+
publicKey = await mpcSigner.getPubkey(keyShare);
|
|
709
1004
|
}
|
|
710
1005
|
return publicKey;
|
|
711
1006
|
}
|
|
@@ -726,7 +1021,13 @@ class DynamicWalletClient {
|
|
|
726
1021
|
...serverKeygenIds,
|
|
727
1022
|
...otherClientKeygenIds
|
|
728
1023
|
];
|
|
729
|
-
|
|
1024
|
+
if (!(mpcSigner instanceof web.ExportableEd25519)) {
|
|
1025
|
+
return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
|
|
1026
|
+
} else {
|
|
1027
|
+
// One party joins the keygen room using acting as the sampler: (wallet-service)
|
|
1028
|
+
// The remaining parties join the key sampling ceremony using: (browser)
|
|
1029
|
+
return mpcSigner.receiveKey(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
|
|
1030
|
+
}
|
|
730
1031
|
}));
|
|
731
1032
|
// only need one client keygen result to derive the public key
|
|
732
1033
|
const [clientKeygenResult] = clientKeygenResults;
|
|
@@ -742,19 +1043,35 @@ class DynamicWalletClient {
|
|
|
742
1043
|
clientKeygenResults
|
|
743
1044
|
};
|
|
744
1045
|
}
|
|
745
|
-
async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
1046
|
+
async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete, traceContext }) {
|
|
1047
|
+
const dynamicRequestId = uuid.v4();
|
|
746
1048
|
try {
|
|
747
1049
|
const clientKeygenInitResults = await this.clientInitializeKeyGen({
|
|
748
1050
|
chainName,
|
|
749
1051
|
thresholdSignatureScheme
|
|
750
1052
|
});
|
|
751
1053
|
const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
|
|
1054
|
+
this.instrument('[DynamicWaasWalletClient] Initialized client key generation', _extends({
|
|
1055
|
+
clientKeygenIds: clientKeygenIds.join(', '),
|
|
1056
|
+
chainName,
|
|
1057
|
+
thresholdSignatureScheme,
|
|
1058
|
+
key: 'keyGen'
|
|
1059
|
+
}, this.getTraceContext(traceContext)));
|
|
752
1060
|
const { roomId, serverKeygenIds } = await this.serverInitializeKeyGen({
|
|
753
1061
|
chainName,
|
|
754
1062
|
clientKeygenIds,
|
|
1063
|
+
dynamicRequestId,
|
|
755
1064
|
thresholdSignatureScheme,
|
|
756
1065
|
onCeremonyComplete
|
|
757
1066
|
});
|
|
1067
|
+
this.instrument('[DynamicWaasWalletClient] Server key generation initialized', _extends({
|
|
1068
|
+
roomId,
|
|
1069
|
+
clientKeygenIds: clientKeygenIds.join(', '),
|
|
1070
|
+
serverKeygenIds: serverKeygenIds.join(', '),
|
|
1071
|
+
chainName,
|
|
1072
|
+
key: 'keyGen',
|
|
1073
|
+
operation: 'serverKeyGen'
|
|
1074
|
+
}, this.getTraceContext(traceContext)));
|
|
758
1075
|
const { rawPublicKey, clientKeygenResults: clientKeyShares } = await this.clientKeyGen({
|
|
759
1076
|
chainName,
|
|
760
1077
|
roomId,
|
|
@@ -762,172 +1079,351 @@ class DynamicWalletClient {
|
|
|
762
1079
|
clientKeygenInitResults,
|
|
763
1080
|
thresholdSignatureScheme
|
|
764
1081
|
});
|
|
1082
|
+
this.instrument('[DynamicWaasWalletClient] Client key generation completed', _extends({
|
|
1083
|
+
roomId,
|
|
1084
|
+
serverKeygenIds: serverKeygenIds.join(', '),
|
|
1085
|
+
clientKeygenIds: clientKeygenIds.join(', '),
|
|
1086
|
+
chainName,
|
|
1087
|
+
thresholdSignatureScheme,
|
|
1088
|
+
rawPublicKey,
|
|
1089
|
+
clientKeySharesCount: clientKeyShares.length,
|
|
1090
|
+
key: 'keyGen',
|
|
1091
|
+
operation: 'clientKeyGen'
|
|
1092
|
+
}, this.getTraceContext(traceContext)));
|
|
765
1093
|
return {
|
|
766
1094
|
rawPublicKey,
|
|
767
1095
|
clientKeyShares
|
|
768
1096
|
};
|
|
769
1097
|
} catch (error) {
|
|
770
|
-
|
|
771
|
-
|
|
1098
|
+
logError({
|
|
1099
|
+
message: 'Error in keyGen',
|
|
1100
|
+
error: error,
|
|
1101
|
+
context: _extends({
|
|
1102
|
+
chainName,
|
|
1103
|
+
thresholdSignatureScheme,
|
|
1104
|
+
dynamicRequestId
|
|
1105
|
+
}, this.getTraceContext(traceContext))
|
|
1106
|
+
});
|
|
1107
|
+
throw error;
|
|
772
1108
|
}
|
|
773
1109
|
}
|
|
774
|
-
async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete }) {
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1110
|
+
async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete, traceContext }) {
|
|
1111
|
+
const dynamicRequestId = uuid.v4();
|
|
1112
|
+
try {
|
|
1113
|
+
const mpcSigner = getMPCSigner({
|
|
1114
|
+
chainName,
|
|
1115
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1116
|
+
});
|
|
1117
|
+
const clientKeygenInitResults = await this.clientInitializeKeyGen({
|
|
1118
|
+
chainName,
|
|
1119
|
+
thresholdSignatureScheme
|
|
1120
|
+
});
|
|
1121
|
+
const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
|
|
1122
|
+
this.instrument('[DynamicWaasWalletClient] Client key generation initialized', _extends({
|
|
1123
|
+
clientKeygenIds: clientKeygenIds.join(', '),
|
|
1124
|
+
chainName,
|
|
1125
|
+
thresholdSignatureScheme,
|
|
1126
|
+
key: 'importRawPrivateKey',
|
|
1127
|
+
operation: 'clientKeyGen'
|
|
1128
|
+
}, this.getTraceContext(traceContext)));
|
|
1129
|
+
const { roomId, serverKeygenIds } = await this.apiClient.importPrivateKey({
|
|
1130
|
+
chainName,
|
|
1131
|
+
clientKeygenIds,
|
|
1132
|
+
dynamicRequestId,
|
|
1133
|
+
thresholdSignatureScheme,
|
|
1134
|
+
onError,
|
|
1135
|
+
onCeremonyComplete
|
|
1136
|
+
});
|
|
1137
|
+
this.instrument('[DynamicWaasWalletClient] Server key generation initialized', _extends({
|
|
1138
|
+
roomId,
|
|
1139
|
+
clientKeygenIds: clientKeygenIds.join(', '),
|
|
1140
|
+
serverKeygenIds: serverKeygenIds.join(', '),
|
|
1141
|
+
chainName,
|
|
1142
|
+
key: 'importRawPrivateKey',
|
|
1143
|
+
operation: 'serverImportPrivateKey'
|
|
1144
|
+
}, this.getTraceContext(traceContext)));
|
|
1145
|
+
const { threshold } = core.getTSSConfig(thresholdSignatureScheme);
|
|
1146
|
+
const clientKeygenResults = await Promise.all(clientKeygenInitResults.map(async (currentInit, index)=>{
|
|
1147
|
+
const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
|
|
1148
|
+
if (index === 0) {
|
|
1149
|
+
const otherKeyGenIds = [
|
|
1150
|
+
...serverKeygenIds,
|
|
1151
|
+
...otherClientKeygenIds
|
|
1152
|
+
];
|
|
1153
|
+
const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
|
|
1154
|
+
return importerKeygenResult;
|
|
1155
|
+
} else {
|
|
1156
|
+
const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
|
|
1157
|
+
...serverKeygenIds,
|
|
1158
|
+
...otherClientKeygenIds
|
|
1159
|
+
]);
|
|
1160
|
+
return recipientKeygenResult;
|
|
1161
|
+
}
|
|
1162
|
+
}));
|
|
1163
|
+
const [clientKeygenResult] = clientKeygenResults;
|
|
1164
|
+
const rawPublicKey = await this.derivePublicKey({
|
|
1165
|
+
chainName,
|
|
1166
|
+
keyShare: clientKeygenResult,
|
|
1167
|
+
derivationPath: undefined
|
|
1168
|
+
});
|
|
1169
|
+
this.instrument('[DynamicWaasWalletClient] Completed import of raw private key', _extends({
|
|
1170
|
+
rawPublicKey,
|
|
1171
|
+
chainName,
|
|
1172
|
+
thresholdSignatureScheme,
|
|
1173
|
+
roomId,
|
|
1174
|
+
serverKeygenIds: serverKeygenIds.join(', '),
|
|
1175
|
+
clientKeygenIds: clientKeygenIds.join(', '),
|
|
1176
|
+
key: 'importRawPrivateKey',
|
|
1177
|
+
operation: 'importRawPrivateKey'
|
|
1178
|
+
}, this.getTraceContext(traceContext)));
|
|
1179
|
+
return {
|
|
1180
|
+
rawPublicKey,
|
|
1181
|
+
clientKeyShares: clientKeygenResults
|
|
1182
|
+
};
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
logError({
|
|
1185
|
+
message: 'Error in importRawPrivateKey',
|
|
1186
|
+
error: error,
|
|
1187
|
+
context: _extends({
|
|
1188
|
+
chainName,
|
|
1189
|
+
thresholdSignatureScheme,
|
|
1190
|
+
dynamicRequestId
|
|
1191
|
+
}, this.getTraceContext(traceContext))
|
|
1192
|
+
});
|
|
1193
|
+
throw error;
|
|
1194
|
+
}
|
|
819
1195
|
}
|
|
820
|
-
async serverSign({ walletId, message }) {
|
|
1196
|
+
async serverSign({ walletId, message, isFormatted, mfaToken, context, onError, dynamicRequestId, traceContext }) {
|
|
821
1197
|
// Create the room and sign the message
|
|
822
1198
|
if (typeof message !== 'string') {
|
|
823
|
-
message =
|
|
1199
|
+
message = `0x${Buffer.from(message).toString('hex')}`;
|
|
824
1200
|
}
|
|
825
1201
|
const data = await this.apiClient.signMessage({
|
|
826
1202
|
walletId,
|
|
827
|
-
message
|
|
1203
|
+
message,
|
|
1204
|
+
isFormatted,
|
|
1205
|
+
dynamicRequestId,
|
|
1206
|
+
mfaToken,
|
|
1207
|
+
context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined,
|
|
1208
|
+
onError,
|
|
1209
|
+
forwardMPCClientEnabled: this.forwardMPCEnabled,
|
|
1210
|
+
traceContext
|
|
828
1211
|
});
|
|
829
1212
|
return data;
|
|
830
1213
|
}
|
|
831
|
-
async
|
|
1214
|
+
async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext }) {
|
|
1215
|
+
try {
|
|
1216
|
+
if (!this.apiClient.forwardMPCClient.connected) {
|
|
1217
|
+
await this.initializeForwardMPCClient();
|
|
1218
|
+
}
|
|
1219
|
+
const messageForForwardMPC = core.serializeMessageForForwardMPC({
|
|
1220
|
+
message,
|
|
1221
|
+
isFormatted,
|
|
1222
|
+
chainName
|
|
1223
|
+
});
|
|
1224
|
+
this.logger.info('Forward MPC enabled, signing message with forward MPC', this.getTraceContext(traceContext));
|
|
1225
|
+
const signature = await this.apiClient.forwardMPCClient.signMessage({
|
|
1226
|
+
keyshare: keyShare,
|
|
1227
|
+
message: chainName === 'SVM' ? formattedMessage : messageForForwardMPC,
|
|
1228
|
+
relayDomain: this.baseMPCRelayApiUrl || '',
|
|
1229
|
+
signingAlgo: chainName === 'EVM' ? 'ECDSA' : 'ED25519',
|
|
1230
|
+
hashAlgo: chainName === 'EVM' && !isFormatted ? 'keccak256' : undefined,
|
|
1231
|
+
derivationPath: derivationPath,
|
|
1232
|
+
roomUuid: roomId
|
|
1233
|
+
});
|
|
1234
|
+
const signatureBytes = signature.data.signature;
|
|
1235
|
+
if (!(signatureBytes instanceof Uint8Array)) {
|
|
1236
|
+
throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
|
|
1237
|
+
}
|
|
1238
|
+
// Convert to EcdsaSignature
|
|
1239
|
+
if (chainName === 'EVM') {
|
|
1240
|
+
const ecdsaSignature = web.EcdsaSignature.fromBuffer(signatureBytes);
|
|
1241
|
+
return ecdsaSignature;
|
|
1242
|
+
} else {
|
|
1243
|
+
return signatureBytes;
|
|
1244
|
+
}
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
this.logger.error('Error signing message with forward MPC client', {
|
|
1247
|
+
error,
|
|
1248
|
+
environmentId: this.environmentId,
|
|
1249
|
+
userId: this.userId,
|
|
1250
|
+
dynamicRequestId
|
|
1251
|
+
});
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
async clientSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext }) {
|
|
832
1256
|
try {
|
|
833
1257
|
const mpcSigner = getMPCSigner({
|
|
834
1258
|
chainName,
|
|
835
1259
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
836
1260
|
});
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1261
|
+
const formattedMessage = isFormatted ? new web.MessageHash(message) : formatMessage(chainName, message);
|
|
1262
|
+
this.logger.debug('[DynamicWaasWalletClient] Starting client sign', _extends({
|
|
1263
|
+
chainName,
|
|
1264
|
+
message,
|
|
1265
|
+
roomId,
|
|
1266
|
+
derivationPath,
|
|
1267
|
+
isFormatted
|
|
1268
|
+
}, this.getTraceContext(traceContext)));
|
|
1269
|
+
if (this.forwardMPCEnabled) {
|
|
1270
|
+
return this.forwardMPCClientSign({
|
|
1271
|
+
chainName,
|
|
1272
|
+
message,
|
|
1273
|
+
roomId,
|
|
1274
|
+
keyShare,
|
|
1275
|
+
derivationPath,
|
|
1276
|
+
formattedMessage,
|
|
1277
|
+
dynamicRequestId,
|
|
1278
|
+
isFormatted,
|
|
1279
|
+
traceContext
|
|
1280
|
+
});
|
|
855
1281
|
}
|
|
856
1282
|
const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
|
|
857
1283
|
return signature;
|
|
858
1284
|
} catch (error) {
|
|
859
|
-
|
|
1285
|
+
logError({
|
|
1286
|
+
message: 'Error in clientSign',
|
|
1287
|
+
error: error,
|
|
1288
|
+
context: _extends({
|
|
1289
|
+
chainName,
|
|
1290
|
+
roomId,
|
|
1291
|
+
derivationPath,
|
|
1292
|
+
isFormatted,
|
|
1293
|
+
dynamicRequestId
|
|
1294
|
+
}, this.getTraceContext(traceContext))
|
|
1295
|
+
});
|
|
860
1296
|
throw error;
|
|
861
1297
|
}
|
|
862
1298
|
}
|
|
863
1299
|
//todo: need to modify with imported flag
|
|
864
|
-
async sign({ accountAddress, message, chainName, password = undefined }) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1300
|
+
async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError, traceContext }) {
|
|
1301
|
+
const dynamicRequestId = uuid.v4();
|
|
1302
|
+
try {
|
|
1303
|
+
await this.verifyPassword({
|
|
1304
|
+
accountAddress,
|
|
1305
|
+
password,
|
|
1306
|
+
walletOperation: core.WalletOperation.SIGN_MESSAGE,
|
|
1307
|
+
signedSessionId
|
|
1308
|
+
});
|
|
1309
|
+
const wallet = await this.getWallet({
|
|
1310
|
+
accountAddress,
|
|
1311
|
+
password,
|
|
1312
|
+
walletOperation: core.WalletOperation.SIGN_MESSAGE,
|
|
1313
|
+
signedSessionId
|
|
1314
|
+
});
|
|
1315
|
+
// Perform the server sign
|
|
1316
|
+
const data = await this.serverSign({
|
|
1317
|
+
walletId: wallet.walletId,
|
|
1318
|
+
message,
|
|
1319
|
+
isFormatted,
|
|
1320
|
+
mfaToken,
|
|
1321
|
+
context,
|
|
1322
|
+
onError,
|
|
1323
|
+
dynamicRequestId,
|
|
1324
|
+
traceContext
|
|
1325
|
+
});
|
|
1326
|
+
this.instrument('[DynamicWaasWalletClient] Server sign completed', _extends({
|
|
1327
|
+
message,
|
|
1328
|
+
accountAddress,
|
|
1329
|
+
walletId: wallet.walletId,
|
|
1330
|
+
roomId: data.roomId,
|
|
1331
|
+
dynamicRequestId,
|
|
1332
|
+
key: 'sign',
|
|
1333
|
+
operation: 'serverSign'
|
|
1334
|
+
}, this.getTraceContext(traceContext)));
|
|
1335
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
1336
|
+
// Perform the client sign and return the signature
|
|
1337
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1338
|
+
accountAddress
|
|
1339
|
+
});
|
|
1340
|
+
const signature = await this.clientSign({
|
|
1341
|
+
chainName,
|
|
1342
|
+
message,
|
|
1343
|
+
roomId: data.roomId,
|
|
1344
|
+
keyShare: clientKeyShares[0],
|
|
1345
|
+
derivationPath,
|
|
1346
|
+
isFormatted,
|
|
1347
|
+
dynamicRequestId,
|
|
1348
|
+
traceContext
|
|
1349
|
+
});
|
|
1350
|
+
this.instrument('[DynamicWaasWalletClient] Client sign completed', _extends({
|
|
1351
|
+
accountAddress,
|
|
1352
|
+
chainName,
|
|
1353
|
+
message,
|
|
1354
|
+
roomId: data.roomId,
|
|
1355
|
+
derivationPath,
|
|
1356
|
+
isFormatted,
|
|
1357
|
+
key: 'sign',
|
|
1358
|
+
operation: 'clientSign'
|
|
1359
|
+
}, this.getTraceContext(traceContext)));
|
|
1360
|
+
return signature;
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
logError({
|
|
1363
|
+
message: 'Error in sign',
|
|
1364
|
+
error: error,
|
|
1365
|
+
context: _extends({
|
|
1366
|
+
accountAddress,
|
|
1367
|
+
chainName,
|
|
1368
|
+
isFormatted: isFormatted ? 'true' : 'false',
|
|
1369
|
+
dynamicRequestId
|
|
1370
|
+
}, this.getTraceContext(traceContext))
|
|
1371
|
+
});
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
893
1374
|
}
|
|
894
|
-
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined }) {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1375
|
+
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
|
|
1376
|
+
const dynamicRequestId = uuid.v4();
|
|
1377
|
+
try {
|
|
1378
|
+
await this.verifyPassword({
|
|
1379
|
+
accountAddress,
|
|
1380
|
+
password,
|
|
1381
|
+
walletOperation: core.WalletOperation.REFRESH,
|
|
1382
|
+
signedSessionId
|
|
1383
|
+
});
|
|
1384
|
+
const wallet = await this.getWallet({
|
|
1385
|
+
accountAddress,
|
|
1386
|
+
walletOperation: core.WalletOperation.NO_OPERATION,
|
|
1387
|
+
password,
|
|
1388
|
+
signedSessionId
|
|
1389
|
+
});
|
|
1390
|
+
const mpcSigner = getMPCSigner({
|
|
1391
|
+
chainName,
|
|
1392
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1393
|
+
});
|
|
1394
|
+
// Create the room and refresh the shares
|
|
1395
|
+
const data = await this.apiClient.refreshWalletAccountShares({
|
|
1396
|
+
dynamicRequestId,
|
|
1397
|
+
walletId: wallet.walletId,
|
|
1398
|
+
mfaToken
|
|
1399
|
+
});
|
|
1400
|
+
const roomId = data.roomId;
|
|
1401
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1402
|
+
accountAddress
|
|
1403
|
+
});
|
|
1404
|
+
const refreshResults = await Promise.all(clientKeyShares.map((clientKeyShare)=>mpcSigner.refresh(roomId, clientKeyShare)));
|
|
1405
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1406
|
+
accountAddress,
|
|
1407
|
+
clientKeyShares: refreshResults,
|
|
1408
|
+
overwriteOrMerge: 'overwrite'
|
|
1409
|
+
});
|
|
1410
|
+
await this.storeEncryptedBackupByWallet({
|
|
1411
|
+
accountAddress,
|
|
1412
|
+
password: password != null ? password : this.environmentId,
|
|
1413
|
+
signedSessionId
|
|
1414
|
+
});
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
logError({
|
|
1417
|
+
message: 'Error in refreshWalletAccountShares',
|
|
1418
|
+
error: error,
|
|
1419
|
+
context: _extends({
|
|
1420
|
+
accountAddress,
|
|
1421
|
+
chainName,
|
|
1422
|
+
dynamicRequestId
|
|
1423
|
+
}, this.getTraceContext(traceContext))
|
|
1424
|
+
});
|
|
1425
|
+
throw error;
|
|
1426
|
+
}
|
|
931
1427
|
}
|
|
932
1428
|
async getExportId({ chainName, clientKeyShare }) {
|
|
933
1429
|
const mpcSigner = getMPCSigner({
|
|
@@ -980,105 +1476,277 @@ class DynamicWalletClient {
|
|
|
980
1476
|
existingClientKeyShares
|
|
981
1477
|
};
|
|
982
1478
|
}
|
|
983
|
-
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined }) {
|
|
984
|
-
|
|
1479
|
+
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, backupToGoogleDrive = false, delegateToProjectEnvironment = false, mfaToken, revokeDelegation = false }) {
|
|
1480
|
+
const dynamicRequestId = uuid.v4();
|
|
1481
|
+
try {
|
|
1482
|
+
await this.verifyPassword({
|
|
1483
|
+
accountAddress,
|
|
1484
|
+
password,
|
|
1485
|
+
walletOperation: core.WalletOperation.RESHARE,
|
|
1486
|
+
signedSessionId
|
|
1487
|
+
});
|
|
1488
|
+
const { existingClientShareCount } = core.getReshareConfig({
|
|
1489
|
+
oldThresholdSignatureScheme,
|
|
1490
|
+
newThresholdSignatureScheme
|
|
1491
|
+
});
|
|
1492
|
+
const wallet = await this.getWallet({
|
|
1493
|
+
accountAddress,
|
|
1494
|
+
walletOperation: core.WalletOperation.NO_OPERATION,
|
|
1495
|
+
shareCount: existingClientShareCount,
|
|
1496
|
+
password,
|
|
1497
|
+
signedSessionId
|
|
1498
|
+
});
|
|
1499
|
+
const { newClientInitKeygenResults, newClientKeygenIds, existingClientKeygenIds, existingClientKeyShares } = await this.reshareStrategy({
|
|
1500
|
+
chainName,
|
|
1501
|
+
accountAddress,
|
|
1502
|
+
wallet,
|
|
1503
|
+
oldThresholdSignatureScheme,
|
|
1504
|
+
newThresholdSignatureScheme
|
|
1505
|
+
});
|
|
1506
|
+
const clientKeygenIds = [
|
|
1507
|
+
...newClientKeygenIds,
|
|
1508
|
+
...existingClientKeygenIds
|
|
1509
|
+
];
|
|
1510
|
+
// Server to create the room and complete the server reshare logics
|
|
1511
|
+
const data = await this.apiClient.reshare({
|
|
1512
|
+
walletId: wallet.walletId,
|
|
1513
|
+
clientKeygenIds: clientKeygenIds,
|
|
1514
|
+
oldThresholdSignatureScheme,
|
|
1515
|
+
newThresholdSignatureScheme,
|
|
1516
|
+
dynamicRequestId,
|
|
1517
|
+
delegateToProjectEnvironment,
|
|
1518
|
+
mfaToken,
|
|
1519
|
+
revokeDelegation
|
|
1520
|
+
});
|
|
1521
|
+
const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
|
|
1522
|
+
// Get the MPC config for the threshold signature scheme
|
|
1523
|
+
const oldMpcConfig = core.MPC_CONFIG[oldThresholdSignatureScheme];
|
|
1524
|
+
const newMpcConfig = core.MPC_CONFIG[newThresholdSignatureScheme];
|
|
1525
|
+
const allPartyKeygenIds = [
|
|
1526
|
+
...clientKeygenIds,
|
|
1527
|
+
...serverKeygenIds,
|
|
1528
|
+
...newServerKeygenIds
|
|
1529
|
+
];
|
|
1530
|
+
const mpcSigner = getMPCSigner({
|
|
1531
|
+
chainName,
|
|
1532
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1533
|
+
});
|
|
1534
|
+
const existingResharePromises = existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds));
|
|
1535
|
+
const newResharePromises = newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds));
|
|
1536
|
+
// Run both share parties in parallel by group
|
|
1537
|
+
const [existingReshareResults, newReshareResults] = await Promise.all([
|
|
1538
|
+
Promise.all(existingResharePromises),
|
|
1539
|
+
Promise.all(newResharePromises)
|
|
1540
|
+
]);
|
|
1541
|
+
const clientKeysharesToLocalStorage = delegateToProjectEnvironment ? [
|
|
1542
|
+
...existingReshareResults
|
|
1543
|
+
] : [
|
|
1544
|
+
...existingReshareResults,
|
|
1545
|
+
...newReshareResults
|
|
1546
|
+
];
|
|
1547
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1548
|
+
thresholdSignatureScheme: newThresholdSignatureScheme
|
|
1549
|
+
});
|
|
1550
|
+
// store client key shares to localStorage
|
|
1551
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1552
|
+
accountAddress,
|
|
1553
|
+
clientKeyShares: clientKeysharesToLocalStorage,
|
|
1554
|
+
overwriteOrMerge: 'overwrite'
|
|
1555
|
+
});
|
|
1556
|
+
const allClientShares = [
|
|
1557
|
+
...existingReshareResults,
|
|
1558
|
+
...newReshareResults
|
|
1559
|
+
];
|
|
1560
|
+
let distribution;
|
|
1561
|
+
if (delegateToProjectEnvironment && backupToGoogleDrive) {
|
|
1562
|
+
// Delegation + Google Drive: Client's existing share backs up to both Dynamic and Google Drive.
|
|
1563
|
+
// The new share goes to the webhook for delegation.
|
|
1564
|
+
distribution = createDelegationWithGoogleDriveDistribution({
|
|
1565
|
+
existingShares: existingReshareResults,
|
|
1566
|
+
delegatedShare: newReshareResults[0]
|
|
1567
|
+
});
|
|
1568
|
+
} else if (delegateToProjectEnvironment) {
|
|
1569
|
+
// Delegation only: Client's existing share backs up to Dynamic.
|
|
1570
|
+
// The new share goes to the webhook for delegation. No Google Drive backup.
|
|
1571
|
+
distribution = createDelegationOnlyDistribution({
|
|
1572
|
+
existingShares: existingReshareResults,
|
|
1573
|
+
delegatedShare: newReshareResults[0]
|
|
1574
|
+
});
|
|
1575
|
+
} else if (backupToGoogleDrive) {
|
|
1576
|
+
// Google Drive only: Split shares between Dynamic (N-1) and Google Drive (1).
|
|
1577
|
+
// The last share (new share) goes to Google Drive.
|
|
1578
|
+
distribution = createGoogleDriveOnlyDistribution({
|
|
1579
|
+
allShares: allClientShares
|
|
1580
|
+
});
|
|
1581
|
+
} else {
|
|
1582
|
+
// No delegation, no Google Drive: All shares go to Dynamic backend only.
|
|
1583
|
+
distribution = createDynamicOnlyDistribution({
|
|
1584
|
+
allShares: allClientShares
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
await this.backupSharesWithDistribution({
|
|
1588
|
+
accountAddress,
|
|
1589
|
+
password,
|
|
1590
|
+
signedSessionId,
|
|
1591
|
+
distribution
|
|
1592
|
+
});
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
logError({
|
|
1595
|
+
message: 'Error in reshare, resetting wallet to previous state',
|
|
1596
|
+
error: error,
|
|
1597
|
+
context: {
|
|
1598
|
+
accountAddress,
|
|
1599
|
+
chainName,
|
|
1600
|
+
oldThresholdSignatureScheme,
|
|
1601
|
+
newThresholdSignatureScheme,
|
|
1602
|
+
backupToGoogleDrive,
|
|
1603
|
+
dynamicRequestId
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
// reset user wallet when reshare fails, this would allow the client to recover wallets from an active state
|
|
1607
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1608
|
+
thresholdSignatureScheme: oldThresholdSignatureScheme
|
|
1609
|
+
});
|
|
1610
|
+
await this.setClientKeySharesToLocalStorage({
|
|
1611
|
+
accountAddress,
|
|
1612
|
+
clientKeyShares: [],
|
|
1613
|
+
overwriteOrMerge: 'overwrite'
|
|
1614
|
+
});
|
|
1615
|
+
throw error;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
async performDelegationOperation({ accountAddress, password, signedSessionId, mfaToken, newThresholdSignatureScheme, revokeDelegation = false, operationName }) {
|
|
1619
|
+
try {
|
|
1620
|
+
var _this_walletMap_accountAddress;
|
|
1621
|
+
const delegateToProjectEnvironment = this.featureFlags && this.featureFlags[core.FEATURE_FLAGS.ENABLE_DELEGATED_KEY_SHARES_FLAG] === true;
|
|
1622
|
+
if (!delegateToProjectEnvironment) {
|
|
1623
|
+
throw new Error('Delegation is not allowed for this project environment');
|
|
1624
|
+
}
|
|
1625
|
+
const wallet = await this.getWallet({
|
|
1626
|
+
accountAddress,
|
|
1627
|
+
walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
|
|
1628
|
+
password,
|
|
1629
|
+
signedSessionId
|
|
1630
|
+
});
|
|
1631
|
+
if (wallet.chainName === 'SUI') {
|
|
1632
|
+
throw new Error('Delegation is not allowed for SUI');
|
|
1633
|
+
}
|
|
1634
|
+
const currentThresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
1635
|
+
await this.reshare({
|
|
1636
|
+
chainName: this.walletMap[accountAddress].chainName,
|
|
1637
|
+
accountAddress,
|
|
1638
|
+
oldThresholdSignatureScheme: currentThresholdSignatureScheme,
|
|
1639
|
+
newThresholdSignatureScheme,
|
|
1640
|
+
password,
|
|
1641
|
+
signedSessionId,
|
|
1642
|
+
backupToGoogleDrive: hasGoogleDriveBackup((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.clientKeySharesBackupInfo),
|
|
1643
|
+
delegateToProjectEnvironment: true,
|
|
1644
|
+
mfaToken,
|
|
1645
|
+
revokeDelegation
|
|
1646
|
+
});
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
logError({
|
|
1649
|
+
message: `Error in ${operationName}`,
|
|
1650
|
+
error: error,
|
|
1651
|
+
context: {
|
|
1652
|
+
accountAddress
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
throw error;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
async delegateKeyShares({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
|
|
1659
|
+
await this.performDelegationOperation({
|
|
985
1660
|
accountAddress,
|
|
986
1661
|
password,
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
newThresholdSignatureScheme
|
|
992
|
-
});
|
|
993
|
-
const wallet = await this.getWallet({
|
|
994
|
-
accountAddress,
|
|
995
|
-
walletOperation: core.WalletOperation.NO_OPERATION,
|
|
996
|
-
shareCount: existingClientShareCount,
|
|
997
|
-
password
|
|
998
|
-
});
|
|
999
|
-
const { newClientInitKeygenResults, newClientKeygenIds, existingClientKeygenIds, existingClientKeyShares } = await this.reshareStrategy({
|
|
1000
|
-
chainName,
|
|
1001
|
-
accountAddress,
|
|
1002
|
-
wallet,
|
|
1003
|
-
oldThresholdSignatureScheme,
|
|
1004
|
-
newThresholdSignatureScheme
|
|
1005
|
-
});
|
|
1006
|
-
const clientKeygenIds = [
|
|
1007
|
-
...newClientKeygenIds,
|
|
1008
|
-
...existingClientKeygenIds
|
|
1009
|
-
];
|
|
1010
|
-
// Server to create the room and complete the server reshare logics
|
|
1011
|
-
const data = await this.apiClient.reshare({
|
|
1012
|
-
walletId: wallet.walletId,
|
|
1013
|
-
clientKeygenIds: clientKeygenIds,
|
|
1014
|
-
oldThresholdSignatureScheme,
|
|
1015
|
-
newThresholdSignatureScheme
|
|
1016
|
-
});
|
|
1017
|
-
const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
|
|
1018
|
-
// Get the MPC config for the threshold signature scheme
|
|
1019
|
-
const oldMpcConfig = core.MPC_CONFIG[oldThresholdSignatureScheme];
|
|
1020
|
-
const newMpcConfig = core.MPC_CONFIG[newThresholdSignatureScheme];
|
|
1021
|
-
const allPartyKeygenIds = [
|
|
1022
|
-
...clientKeygenIds,
|
|
1023
|
-
...serverKeygenIds,
|
|
1024
|
-
...newServerKeygenIds
|
|
1025
|
-
];
|
|
1026
|
-
const mpcSigner = getMPCSigner({
|
|
1027
|
-
chainName,
|
|
1028
|
-
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1029
|
-
});
|
|
1030
|
-
const reshareResults = await Promise.all([
|
|
1031
|
-
...newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
|
|
1032
|
-
...existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
|
|
1033
|
-
]);
|
|
1034
|
-
await this.setClientKeySharesToLocalStorage({
|
|
1035
|
-
accountAddress,
|
|
1036
|
-
clientKeyShares: reshareResults,
|
|
1037
|
-
overwriteOrMerge: 'overwrite'
|
|
1662
|
+
signedSessionId,
|
|
1663
|
+
mfaToken,
|
|
1664
|
+
newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
|
|
1665
|
+
operationName: 'delegateKeyShares'
|
|
1038
1666
|
});
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
});
|
|
1043
|
-
return reshareResults;
|
|
1667
|
+
const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
1668
|
+
const delegatedKeyShares = backupInfo.backups[core.BackupLocation.DELEGATED] || [];
|
|
1669
|
+
return delegatedKeyShares;
|
|
1044
1670
|
}
|
|
1045
|
-
async
|
|
1046
|
-
|
|
1671
|
+
async revokeDelegation({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
|
|
1672
|
+
await this.performDelegationOperation({
|
|
1047
1673
|
accountAddress,
|
|
1048
1674
|
password,
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
});
|
|
1055
|
-
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1056
|
-
accountAddress
|
|
1675
|
+
signedSessionId,
|
|
1676
|
+
mfaToken,
|
|
1677
|
+
newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_TWO,
|
|
1678
|
+
revokeDelegation: true,
|
|
1679
|
+
operationName: 'revokeDelegation'
|
|
1057
1680
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1681
|
+
}
|
|
1682
|
+
async exportKey({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
|
|
1683
|
+
const dynamicRequestId = uuid.v4();
|
|
1684
|
+
try {
|
|
1685
|
+
const wallet = await this.getWallet({
|
|
1686
|
+
accountAddress,
|
|
1687
|
+
password,
|
|
1688
|
+
walletOperation: core.WalletOperation.EXPORT_PRIVATE_KEY,
|
|
1689
|
+
signedSessionId
|
|
1690
|
+
});
|
|
1691
|
+
const mpcSigner = getMPCSigner({
|
|
1692
|
+
chainName,
|
|
1693
|
+
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1694
|
+
});
|
|
1695
|
+
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1696
|
+
accountAddress
|
|
1697
|
+
});
|
|
1698
|
+
const exportId = await this.getExportId({
|
|
1699
|
+
chainName,
|
|
1700
|
+
clientKeyShare: clientKeyShares[0]
|
|
1701
|
+
});
|
|
1702
|
+
const data = await this.apiClient.exportKey({
|
|
1703
|
+
walletId: wallet.walletId,
|
|
1704
|
+
exportId,
|
|
1705
|
+
dynamicRequestId,
|
|
1706
|
+
mfaToken
|
|
1707
|
+
});
|
|
1708
|
+
this.logger.debug('[DynamicWaasWalletClient] Starting export of private key', _extends({
|
|
1709
|
+
accountAddress,
|
|
1710
|
+
chainName,
|
|
1711
|
+
walletId: wallet.walletId,
|
|
1712
|
+
exportId,
|
|
1713
|
+
roomId: data.roomId
|
|
1714
|
+
}, this.getTraceContext(traceContext)));
|
|
1715
|
+
const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, clientKeyShares[0], exportId);
|
|
1716
|
+
if (!keyExportRaw) {
|
|
1717
|
+
throw new Error('Error exporting private key');
|
|
1718
|
+
}
|
|
1719
|
+
this.logger.debug('[DynamicWaasWalletClient] Completed export of private key', _extends({
|
|
1720
|
+
accountAddress,
|
|
1721
|
+
chainName,
|
|
1722
|
+
walletId: wallet.walletId,
|
|
1723
|
+
exportId,
|
|
1724
|
+
roomId: data.roomId
|
|
1725
|
+
}, this.getTraceContext(traceContext)));
|
|
1726
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
1727
|
+
let derivedPrivateKey;
|
|
1728
|
+
if (mpcSigner instanceof web.Ecdsa) {
|
|
1729
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
1730
|
+
} else if (mpcSigner instanceof web.ExportableEd25519) {
|
|
1731
|
+
derivedPrivateKey = keyExportRaw;
|
|
1732
|
+
} else if (mpcSigner instanceof web.BIP340) {
|
|
1733
|
+
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
|
|
1734
|
+
}
|
|
1735
|
+
return {
|
|
1736
|
+
derivedPrivateKey
|
|
1737
|
+
};
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
logError({
|
|
1740
|
+
message: 'Error in exportKey',
|
|
1741
|
+
error: error,
|
|
1742
|
+
context: _extends({
|
|
1743
|
+
accountAddress,
|
|
1744
|
+
chainName,
|
|
1745
|
+
dynamicRequestId
|
|
1746
|
+
}, this.getTraceContext(traceContext))
|
|
1747
|
+
});
|
|
1748
|
+
throw error;
|
|
1078
1749
|
}
|
|
1079
|
-
return {
|
|
1080
|
-
derivedPrivateKey
|
|
1081
|
-
};
|
|
1082
1750
|
}
|
|
1083
1751
|
async offlineExportKey({ chainName, keyShares, derivationPath }) {
|
|
1084
1752
|
try {
|
|
@@ -1090,7 +1758,7 @@ class DynamicWalletClient {
|
|
|
1090
1758
|
baseRelayUrl: this.baseMPCRelayApiUrl
|
|
1091
1759
|
});
|
|
1092
1760
|
const walletKeyShares = keyShares.map((keyShare)=>{
|
|
1093
|
-
return mpcSigner instanceof web.Ecdsa ? new web.EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof web.
|
|
1761
|
+
return mpcSigner instanceof web.Ecdsa ? new web.EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof web.ExportableEd25519 ? new web.ExportableEd25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new web.BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
|
|
1094
1762
|
});
|
|
1095
1763
|
const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
|
|
1096
1764
|
if (!keyExportRaw) {
|
|
@@ -1101,7 +1769,7 @@ class DynamicWalletClient {
|
|
|
1101
1769
|
let derivedPrivateKey;
|
|
1102
1770
|
if (mpcSigner instanceof web.Ecdsa) {
|
|
1103
1771
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
1104
|
-
} else if (mpcSigner instanceof web.
|
|
1772
|
+
} else if (mpcSigner instanceof web.ExportableEd25519) {
|
|
1105
1773
|
derivedPrivateKey = keyExportRaw;
|
|
1106
1774
|
} else if (mpcSigner instanceof web.BIP340) {
|
|
1107
1775
|
derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
|
|
@@ -1116,7 +1784,13 @@ class DynamicWalletClient {
|
|
|
1116
1784
|
rawPublicKey
|
|
1117
1785
|
};
|
|
1118
1786
|
} catch (error) {
|
|
1119
|
-
|
|
1787
|
+
logError({
|
|
1788
|
+
message: 'Error in offlineExportKey',
|
|
1789
|
+
error: error,
|
|
1790
|
+
context: {
|
|
1791
|
+
chainName
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1120
1794
|
throw error;
|
|
1121
1795
|
}
|
|
1122
1796
|
}
|
|
@@ -1133,117 +1807,265 @@ class DynamicWalletClient {
|
|
|
1133
1807
|
/**
|
|
1134
1808
|
* helper function to store encrypted backup by wallet from iframe local storage
|
|
1135
1809
|
*/ async getClientKeySharesFromLocalStorage({ accountAddress }) {
|
|
1136
|
-
var
|
|
1137
|
-
await this.
|
|
1138
|
-
const walletObject = await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.getItem(accountAddress));
|
|
1810
|
+
var _this_storage;
|
|
1811
|
+
const walletObject = await ((_this_storage = this.storage) == null ? void 0 : _this_storage.getItem(accountAddress));
|
|
1139
1812
|
if (!walletObject) {
|
|
1140
|
-
this.logger.debug(`No item found in iframe local storage for accountAddress: ${accountAddress}`);
|
|
1813
|
+
this.logger.debug(`[DynamicWaasWalletClient] No item found in iframe local storage for accountAddress: ${accountAddress}`);
|
|
1141
1814
|
return [];
|
|
1142
1815
|
}
|
|
1143
1816
|
try {
|
|
1144
|
-
|
|
1817
|
+
let parsedWalletObject;
|
|
1818
|
+
if (typeof walletObject === 'string') {
|
|
1819
|
+
parsedWalletObject = JSON.parse(walletObject);
|
|
1820
|
+
} else {
|
|
1821
|
+
parsedWalletObject = walletObject;
|
|
1822
|
+
}
|
|
1823
|
+
return (parsedWalletObject == null ? void 0 : parsedWalletObject.clientKeyShares) || [];
|
|
1145
1824
|
} catch (error) {
|
|
1146
|
-
|
|
1825
|
+
logError({
|
|
1826
|
+
message: `Error parsing clientKeyShares: Error for accountAddress:`,
|
|
1827
|
+
error: error,
|
|
1828
|
+
context: {
|
|
1829
|
+
accountAddress
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1147
1832
|
return [];
|
|
1148
1833
|
}
|
|
1149
1834
|
}
|
|
1150
1835
|
/**
|
|
1151
1836
|
* helper function to store encrypted backup by wallet from iframe local storage
|
|
1152
1837
|
*/ async setClientKeySharesToLocalStorage({ accountAddress, clientKeyShares, overwriteOrMerge = 'merge' }) {
|
|
1153
|
-
var
|
|
1154
|
-
|
|
1155
|
-
await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.setItem(accountAddress, {
|
|
1838
|
+
var _this_storage;
|
|
1839
|
+
const stringifiedClientKeyShares = JSON.stringify({
|
|
1156
1840
|
clientKeyShares: overwriteOrMerge === 'overwrite' ? clientKeyShares : mergeUniqueKeyShares(await this.getClientKeySharesFromLocalStorage({
|
|
1157
1841
|
accountAddress
|
|
1158
1842
|
}), clientKeyShares)
|
|
1159
|
-
})
|
|
1843
|
+
});
|
|
1844
|
+
await ((_this_storage = this.storage) == null ? void 0 : _this_storage.setItem(accountAddress, stringifiedClientKeyShares));
|
|
1845
|
+
}
|
|
1846
|
+
async backupSharesWithDistribution({ accountAddress, password, signedSessionId, distribution, preserveDelegatedLocation = false }) {
|
|
1847
|
+
const dynamicRequestId = uuid.v4();
|
|
1848
|
+
try {
|
|
1849
|
+
var _this_walletMap_accountAddress;
|
|
1850
|
+
if (!((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.walletId)) {
|
|
1851
|
+
const error = new Error(`WalletId not found for accountAddress ${accountAddress}`);
|
|
1852
|
+
logError({
|
|
1853
|
+
message: 'Error in backupSharesWithDistribution, wallet or walletId not found from the wallet map',
|
|
1854
|
+
error,
|
|
1855
|
+
context: {
|
|
1856
|
+
accountAddress,
|
|
1857
|
+
walletMap: this.walletMap
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
throw error;
|
|
1861
|
+
}
|
|
1862
|
+
const locations = [];
|
|
1863
|
+
if (distribution.dynamicBackendShares.length > 0) {
|
|
1864
|
+
const encryptedDynamicShares = await Promise.all(distribution.dynamicBackendShares.map((keyShare)=>this.encryptKeyShare({
|
|
1865
|
+
keyShare,
|
|
1866
|
+
password
|
|
1867
|
+
})));
|
|
1868
|
+
const data = await this.apiClient.storeEncryptedBackupByWallet({
|
|
1869
|
+
walletId: this.walletMap[accountAddress].walletId,
|
|
1870
|
+
encryptedKeyShares: encryptedDynamicShares,
|
|
1871
|
+
passwordEncrypted: Boolean(password) && password !== this.environmentId,
|
|
1872
|
+
encryptionVersion: ENCRYPTION_VERSION_CURRENT,
|
|
1873
|
+
signedSessionId,
|
|
1874
|
+
authMode: this.authMode,
|
|
1875
|
+
requiresSignedSessionId: this.requiresSignedSessionId(),
|
|
1876
|
+
dynamicRequestId
|
|
1877
|
+
});
|
|
1878
|
+
if (data.keyShareIds.length === 0) {
|
|
1879
|
+
throw new Error('No key shares were backed up to Dynamic backend');
|
|
1880
|
+
}
|
|
1881
|
+
locations.push({
|
|
1882
|
+
location: core.BackupLocation.DYNAMIC,
|
|
1883
|
+
externalKeyShareId: data.keyShareIds[0]
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
if (distribution.googleDriveShares.length > 0) {
|
|
1887
|
+
const encryptedGoogleDriveShares = await Promise.all(distribution.googleDriveShares.map((keyShare)=>this.encryptKeyShare({
|
|
1888
|
+
keyShare,
|
|
1889
|
+
password
|
|
1890
|
+
})));
|
|
1891
|
+
await this.uploadKeySharesToGoogleDrive({
|
|
1892
|
+
accountAddress,
|
|
1893
|
+
encryptedKeyShares: encryptedGoogleDriveShares
|
|
1894
|
+
});
|
|
1895
|
+
locations.push({
|
|
1896
|
+
location: core.BackupLocation.GOOGLE_DRIVE
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
if (distribution.delegatedShare) {
|
|
1900
|
+
var _publicKey_key, _publicKey_key1, _publicKey_key2;
|
|
1901
|
+
const publicKey = await this.apiClient.getDelegatedEncryptionKey({
|
|
1902
|
+
environmentId: this.environmentId
|
|
1903
|
+
});
|
|
1904
|
+
if (!(publicKey == null ? void 0 : (_publicKey_key = publicKey.key) == null ? void 0 : _publicKey_key.publicKeyPemB64)) {
|
|
1905
|
+
throw new Error('Public key not found');
|
|
1906
|
+
}
|
|
1907
|
+
var _publicKey_key_keyId;
|
|
1908
|
+
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);
|
|
1909
|
+
const { status } = await this.apiClient.publishDelegatedKeyShare({
|
|
1910
|
+
walletId: this.walletMap[accountAddress].walletId,
|
|
1911
|
+
encryptedKeyShare: encryptedDelegatedKeyShareEnvelope,
|
|
1912
|
+
signedSessionId,
|
|
1913
|
+
requiresSignedSessionId: this.requiresSignedSessionId(),
|
|
1914
|
+
dynamicRequestId
|
|
1915
|
+
});
|
|
1916
|
+
if (status !== 200) {
|
|
1917
|
+
throw new Error('Failed to publish delegated key share');
|
|
1918
|
+
}
|
|
1919
|
+
locations.push({
|
|
1920
|
+
location: core.BackupLocation.DELEGATED
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
// Preserve existing delegated location without re-publishing
|
|
1924
|
+
if (preserveDelegatedLocation && !distribution.delegatedShare) {
|
|
1925
|
+
locations.push({
|
|
1926
|
+
location: core.BackupLocation.DELEGATED
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
const backupData = await this.apiClient.markKeySharesAsBackedUp({
|
|
1930
|
+
walletId: this.walletMap[accountAddress].walletId,
|
|
1931
|
+
locations,
|
|
1932
|
+
dynamicRequestId
|
|
1933
|
+
});
|
|
1934
|
+
const updatedBackupInfo = getClientKeyShareBackupInfo({
|
|
1935
|
+
walletProperties: {
|
|
1936
|
+
derivationPath: this.walletMap[accountAddress].derivationPath,
|
|
1937
|
+
keyShares: backupData.locationsWithKeyShares.map((ks)=>({
|
|
1938
|
+
id: ks.keyShareId,
|
|
1939
|
+
backupLocation: ks.location,
|
|
1940
|
+
externalKeyShareId: ks.externalKeyShareId,
|
|
1941
|
+
passwordEncrypted: Boolean(password) && password !== this.environmentId
|
|
1942
|
+
})),
|
|
1943
|
+
thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1947
|
+
clientKeySharesBackupInfo: updatedBackupInfo
|
|
1948
|
+
});
|
|
1949
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1950
|
+
return backupData;
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
logError({
|
|
1953
|
+
message: 'Error in backupSharesWithDistribution',
|
|
1954
|
+
error: error,
|
|
1955
|
+
context: {
|
|
1956
|
+
accountAddress,
|
|
1957
|
+
dynamicRequestId
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1960
|
+
throw error;
|
|
1961
|
+
}
|
|
1160
1962
|
}
|
|
1161
1963
|
/**
|
|
1162
|
-
*
|
|
1964
|
+
* Central backup orchestrator that encrypts and stores wallet key shares.
|
|
1163
1965
|
*
|
|
1164
|
-
* This method
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1966
|
+
* This method serves as the main backup coordinator, handling the distribution of encrypted
|
|
1967
|
+
* key shares between Dynamic's backend and Google Drive based on the wallet's threshold scheme.
|
|
1968
|
+
* It is used by multiple operations including reshare, refresh, and manual backup requests.
|
|
1167
1969
|
*
|
|
1168
|
-
*
|
|
1970
|
+
* **Backup Distribution Strategy:**
|
|
1971
|
+
* - **Single share wallets**: All shares stored on Dynamic's backend only
|
|
1972
|
+
* - **Multi-share wallets (2+)**: When backing up to Google Drive, N-1 shares on Dynamic's backend, 1 share on Google Drive
|
|
1973
|
+
* - **Multi-share wallets (2+)**: When not backing up to Google Drive, all shares on Dynamic's backend
|
|
1974
|
+
*
|
|
1975
|
+
* **Process Flow:**
|
|
1169
1976
|
* 1. Encrypts all client key shares with the provided password (or environment ID if no password)
|
|
1170
|
-
* 2.
|
|
1171
|
-
* 3.
|
|
1172
|
-
* 4. Updates
|
|
1173
|
-
* 5. Persists the updated wallet map to storage
|
|
1977
|
+
* 2. For multi-share wallets (2+): conditionally distributes N-1 to backend, 1 to Google Drive
|
|
1978
|
+
* 3. For other configurations: stores all shares on Dynamic's backend
|
|
1979
|
+
* 4. Updates backup metadata and synchronizes wallet state
|
|
1980
|
+
* 5. Persists the updated wallet map to local storage
|
|
1981
|
+
*
|
|
1982
|
+
* **Delegated Key Shares:**
|
|
1983
|
+
* - 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
|
|
1984
|
+
* - and encrypt the delegated key share to publish it to the webhook
|
|
1174
1985
|
*
|
|
1175
|
-
* @param
|
|
1176
|
-
* @param
|
|
1177
|
-
* @param
|
|
1178
|
-
|
|
1986
|
+
* @param params - The backup operation parameters
|
|
1987
|
+
* @param params.accountAddress - The account address of the wallet to backup
|
|
1988
|
+
* @param params.clientKeyShares - Optional specific key shares to backup (uses localStorage if not provided)
|
|
1989
|
+
* @param params.password - Optional password for encryption (uses environment ID if not provided)
|
|
1990
|
+
* @param params.signedSessionId - Optional signed session ID for authentication
|
|
1991
|
+
* @param params.backupToGoogleDrive - Whether to backup to Google Drive (defaults to false)
|
|
1992
|
+
* @returns Promise with backup metadata including share locations and IDs
|
|
1993
|
+
*/ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, backupToGoogleDrive = false, delegatedKeyshare = undefined }) {
|
|
1994
|
+
var _this_walletMap_accountAddress, _this_walletMap_accountAddress1;
|
|
1179
1995
|
const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
|
|
1180
1996
|
accountAddress
|
|
1181
1997
|
});
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
if (
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
passwordEncrypted: Boolean(password) && password !== this.environmentId
|
|
1193
|
-
});
|
|
1194
|
-
const hasGoogleDriveBackup = this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[core.BackupLocation.GOOGLE_DRIVE].length > 0;
|
|
1195
|
-
if (hasGoogleDriveBackup) {
|
|
1196
|
-
var _user_verifiedCredentials_find;
|
|
1197
|
-
const user = await this.apiClient.getUser();
|
|
1198
|
-
const oauthAccountId = user == null ? void 0 : (_user_verifiedCredentials_find = user.verifiedCredentials.find((credential)=>credential.oauthProvider === 'google')) == null ? void 0 : _user_verifiedCredentials_find.id;
|
|
1199
|
-
const googleDriveKeyShareIds = await this.backupKeySharesToGoogleDrive({
|
|
1200
|
-
accountAddress,
|
|
1201
|
-
password,
|
|
1202
|
-
oauthAccountId
|
|
1998
|
+
const shouldBackupToGoogleDrive = backupToGoogleDrive || hasGoogleDriveBackup((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.clientKeySharesBackupInfo);
|
|
1999
|
+
const hasExistingDelegation = hasDelegatedBackup((_this_walletMap_accountAddress1 = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress1.clientKeySharesBackupInfo);
|
|
2000
|
+
let distribution;
|
|
2001
|
+
let preserveDelegatedLocation = false;
|
|
2002
|
+
if (delegatedKeyshare && shouldBackupToGoogleDrive) {
|
|
2003
|
+
// NEW delegation + Google Drive: Client's shares back up to both Dynamic and Google Drive.
|
|
2004
|
+
// The delegated share goes to the webhook.
|
|
2005
|
+
distribution = createDelegationWithGoogleDriveDistribution({
|
|
2006
|
+
existingShares: keySharesToBackup,
|
|
2007
|
+
delegatedShare: delegatedKeyshare
|
|
1203
2008
|
});
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
2009
|
+
} else if (delegatedKeyshare) {
|
|
2010
|
+
// NEW delegation only: Client's shares back up to Dynamic.
|
|
2011
|
+
// The delegated share goes to the webhook. No Google Drive backup.
|
|
2012
|
+
distribution = createDelegationOnlyDistribution({
|
|
2013
|
+
existingShares: keySharesToBackup,
|
|
2014
|
+
delegatedShare: delegatedKeyshare
|
|
2015
|
+
});
|
|
2016
|
+
} else if (hasExistingDelegation && shouldBackupToGoogleDrive) {
|
|
2017
|
+
// ADD Google Drive to EXISTING delegation: Client's share backs up to both Dynamic and GD.
|
|
2018
|
+
// Don't re-publish delegated share, just preserve the location.
|
|
2019
|
+
distribution = createAddGoogleDriveToExistingDelegationDistribution({
|
|
2020
|
+
clientShares: keySharesToBackup
|
|
2021
|
+
});
|
|
2022
|
+
preserveDelegatedLocation = true;
|
|
2023
|
+
} else if (shouldBackupToGoogleDrive && keySharesToBackup.length >= 2) {
|
|
2024
|
+
// Google Drive only (no delegation): Split shares between Dynamic (N-1) and Google Drive (1).
|
|
2025
|
+
distribution = createGoogleDriveOnlyDistribution({
|
|
2026
|
+
allShares: keySharesToBackup
|
|
2027
|
+
});
|
|
2028
|
+
} else {
|
|
2029
|
+
// No delegation, no Google Drive: All shares go to Dynamic backend only.
|
|
2030
|
+
distribution = createDynamicOnlyDistribution({
|
|
2031
|
+
allShares: keySharesToBackup
|
|
1207
2032
|
});
|
|
1208
2033
|
}
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
2034
|
+
const backupData = await this.backupSharesWithDistribution({
|
|
2035
|
+
accountAddress,
|
|
2036
|
+
password,
|
|
2037
|
+
signedSessionId,
|
|
2038
|
+
distribution,
|
|
2039
|
+
preserveDelegatedLocation
|
|
1215
2040
|
});
|
|
1216
|
-
|
|
1217
|
-
|
|
2041
|
+
return _extends({}, backupData, {
|
|
2042
|
+
keyShareIds: backupData.locationsWithKeyShares.map((ks)=>ks.keyShareId)
|
|
1218
2043
|
});
|
|
1219
|
-
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1220
|
-
return data;
|
|
1221
2044
|
}
|
|
1222
|
-
async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password }) {
|
|
2045
|
+
async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password, signedSessionId }) {
|
|
1223
2046
|
await retryPromise(()=>this.storeEncryptedBackupByWallet({
|
|
1224
2047
|
accountAddress,
|
|
1225
2048
|
clientKeyShares,
|
|
1226
|
-
password
|
|
2049
|
+
password,
|
|
2050
|
+
signedSessionId
|
|
1227
2051
|
}), {
|
|
1228
2052
|
operationName: 'store encrypted backup',
|
|
1229
2053
|
logContext: {
|
|
1230
|
-
walletAddress: accountAddress
|
|
1231
|
-
keyShares: clientKeyShares == null ? void 0 : clientKeyShares.map((keyShare)=>this.encryptKeyShare({
|
|
1232
|
-
keyShare,
|
|
1233
|
-
password
|
|
1234
|
-
}))
|
|
2054
|
+
walletAddress: accountAddress
|
|
1235
2055
|
}
|
|
1236
2056
|
});
|
|
1237
2057
|
}
|
|
1238
|
-
async updatePassword({ accountAddress, existingPassword, newPassword }) {
|
|
2058
|
+
async updatePassword({ accountAddress, existingPassword, newPassword, signedSessionId }) {
|
|
1239
2059
|
await this.getWallet({
|
|
1240
2060
|
accountAddress,
|
|
1241
2061
|
password: existingPassword,
|
|
1242
|
-
walletOperation: core.WalletOperation.REACH_ALL_PARTIES
|
|
2062
|
+
walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
|
|
2063
|
+
signedSessionId
|
|
1243
2064
|
});
|
|
1244
2065
|
await this.storeEncryptedBackupByWallet({
|
|
1245
2066
|
accountAddress,
|
|
1246
|
-
password: newPassword
|
|
2067
|
+
password: newPassword,
|
|
2068
|
+
signedSessionId
|
|
1247
2069
|
});
|
|
1248
2070
|
}
|
|
1249
2071
|
async decryptKeyShare({ keyShare, password }) {
|
|
@@ -1256,6 +2078,41 @@ class DynamicWalletClient {
|
|
|
1256
2078
|
return deserializedKeyShare;
|
|
1257
2079
|
}
|
|
1258
2080
|
/**
|
|
2081
|
+
* Helper function to get Google OAuth Account ID or throw an error if not found.
|
|
2082
|
+
* @param accountAddress - The account address for logging purposes
|
|
2083
|
+
* @returns The Google OAuth Account ID
|
|
2084
|
+
* @throws Error if no Google OAuth account ID is found
|
|
2085
|
+
*/ async getGoogleOauthAccountIdOrThrow(accountAddress) {
|
|
2086
|
+
const dynamicRequestId = uuid.v4();
|
|
2087
|
+
try {
|
|
2088
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2089
|
+
const oauthAccountId = getGoogleOAuthAccountId(user == null ? void 0 : user.verifiedCredentials);
|
|
2090
|
+
if (!oauthAccountId) {
|
|
2091
|
+
const error = new Error('No Google OAuth account ID found');
|
|
2092
|
+
logError({
|
|
2093
|
+
message: 'No Google OAuth account ID found',
|
|
2094
|
+
error,
|
|
2095
|
+
context: {
|
|
2096
|
+
user,
|
|
2097
|
+
accountAddress
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
throw error;
|
|
2101
|
+
}
|
|
2102
|
+
return oauthAccountId;
|
|
2103
|
+
} catch (error) {
|
|
2104
|
+
logError({
|
|
2105
|
+
message: 'Error in getGoogleOauthAccountIdOrThrow',
|
|
2106
|
+
error: error,
|
|
2107
|
+
context: {
|
|
2108
|
+
accountAddress,
|
|
2109
|
+
dynamicRequestId
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
throw error;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
1259
2116
|
* Helper function to determine keyshare recovery strategy for dynamic shares.
|
|
1260
2117
|
* For REFRESH operations, retrieves enough shares to meet the client threshold.
|
|
1261
2118
|
* For all other operations, retrieves just 1 share.
|
|
@@ -1277,39 +2134,58 @@ class DynamicWalletClient {
|
|
|
1277
2134
|
const dynamicShares = backups[core.BackupLocation.DYNAMIC].slice(0, requiredShareCount);
|
|
1278
2135
|
return {
|
|
1279
2136
|
shares: {
|
|
1280
|
-
[core.BackupLocation.DYNAMIC]: dynamicShares
|
|
2137
|
+
[core.BackupLocation.DYNAMIC]: dynamicShares.map((ks)=>{
|
|
2138
|
+
var _ks_externalKeyShareId, _ref;
|
|
2139
|
+
return (_ref = (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : ks.keyShareId) != null ? _ref : '';
|
|
2140
|
+
})
|
|
1281
2141
|
},
|
|
1282
2142
|
requiredShareCount
|
|
1283
2143
|
};
|
|
1284
2144
|
}
|
|
1285
|
-
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, storeRecoveredShares = true }) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
const { dynamic: dynamicKeyShareIds } = shares;
|
|
1295
|
-
const data = await this.apiClient.recoverEncryptedBackupByWallet({
|
|
1296
|
-
walletId: wallet.walletId,
|
|
1297
|
-
keyShareIds: dynamicKeyShareIds
|
|
1298
|
-
});
|
|
1299
|
-
const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === core.BackupLocation.DYNAMIC);
|
|
1300
|
-
const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
|
|
1301
|
-
keyShare: keyShare.encryptedAccountCredential,
|
|
1302
|
-
password: password != null ? password : this.environmentId
|
|
1303
|
-
})));
|
|
1304
|
-
if (storeRecoveredShares) {
|
|
1305
|
-
await this.setClientKeySharesToLocalStorage({
|
|
1306
|
-
accountAddress,
|
|
1307
|
-
clientKeyShares: decryptedKeyShares,
|
|
1308
|
-
overwriteOrMerge: 'merge'
|
|
2145
|
+
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, signedSessionId, shareCount = undefined, storeRecoveredShares = true, mfaToken }) {
|
|
2146
|
+
try {
|
|
2147
|
+
const wallet = this.walletMap[accountAddress];
|
|
2148
|
+
this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
|
|
2149
|
+
const { shares } = this.recoverStrategy({
|
|
2150
|
+
clientKeyShareBackupInfo: wallet.clientKeySharesBackupInfo,
|
|
2151
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme,
|
|
2152
|
+
walletOperation,
|
|
2153
|
+
shareCount
|
|
1309
2154
|
});
|
|
1310
|
-
|
|
2155
|
+
const { dynamic: dynamicKeyShareIds } = shares;
|
|
2156
|
+
const data = await this.apiClient.recoverEncryptedBackupByWallet({
|
|
2157
|
+
walletId: wallet.walletId,
|
|
2158
|
+
keyShareIds: dynamicKeyShareIds,
|
|
2159
|
+
signedSessionId,
|
|
2160
|
+
mfaToken,
|
|
2161
|
+
requiresSignedSessionId: this.requiresSignedSessionId()
|
|
2162
|
+
});
|
|
2163
|
+
const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === core.BackupLocation.DYNAMIC);
|
|
2164
|
+
const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
|
|
2165
|
+
keyShare: keyShare.encryptedAccountCredential,
|
|
2166
|
+
password: password != null ? password : this.environmentId
|
|
2167
|
+
})));
|
|
2168
|
+
if (storeRecoveredShares) {
|
|
2169
|
+
await this.setClientKeySharesToLocalStorage({
|
|
2170
|
+
accountAddress,
|
|
2171
|
+
clientKeyShares: decryptedKeyShares,
|
|
2172
|
+
overwriteOrMerge: 'merge'
|
|
2173
|
+
});
|
|
2174
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
2175
|
+
}
|
|
2176
|
+
return decryptedKeyShares;
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
logError({
|
|
2179
|
+
message: 'Error in recoverEncryptedBackupByWallet',
|
|
2180
|
+
error: error,
|
|
2181
|
+
context: {
|
|
2182
|
+
accountAddress,
|
|
2183
|
+
walletOperation,
|
|
2184
|
+
shareCount
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
throw error;
|
|
1311
2188
|
}
|
|
1312
|
-
return decryptedKeyShares;
|
|
1313
2189
|
}
|
|
1314
2190
|
async restoreWallets() {
|
|
1315
2191
|
const wallets = await this.storage.getItem(this.storageKey);
|
|
@@ -1318,114 +2194,207 @@ class DynamicWalletClient {
|
|
|
1318
2194
|
}
|
|
1319
2195
|
this.walletMap = JSON.parse(wallets);
|
|
1320
2196
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
keyShare,
|
|
1335
|
-
password
|
|
1336
|
-
})));
|
|
1337
|
-
const accessToken = await this.apiClient.getAccessToken({
|
|
1338
|
-
oauthAccountId
|
|
1339
|
-
});
|
|
1340
|
-
const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
1341
|
-
const suggestedFileName = getClientKeyShareExportFileName({
|
|
1342
|
-
thresholdSignatureScheme,
|
|
1343
|
-
accountAddress
|
|
1344
|
-
});
|
|
1345
|
-
const backupData = {
|
|
1346
|
-
keyShares: encryptedKeyShares,
|
|
1347
|
-
metadata: {
|
|
1348
|
-
version: '1.0',
|
|
1349
|
-
createdAt: new Date().toISOString(),
|
|
2197
|
+
/**
|
|
2198
|
+
* This method handles the complete flow for ensuring wallet key shares are backed up to Google Drive:
|
|
2199
|
+
* - For 2-of-2 wallets: Automatically reshares to 2-of-3 threshold, then distributes shares (1 to backend, 1 to Google Drive)
|
|
2200
|
+
* - For 2-of-3 wallets: Call storeEncryptedBackupByWallet to backup for backend and Google Drive
|
|
2201
|
+
*
|
|
2202
|
+
* @param params - The backup parameters
|
|
2203
|
+
* @param params.accountAddress - The wallet account address to backup
|
|
2204
|
+
* @param params.password - Optional password for encryption (uses environment ID if not provided)
|
|
2205
|
+
* @param params.signedSessionId - Optional signed session ID for authentication
|
|
2206
|
+
* @returns Promise<string[]> - Array of Google Drive key share IDs that were backed up
|
|
2207
|
+
*/ async backupKeySharesToGoogleDrive({ accountAddress, password, signedSessionId }) {
|
|
2208
|
+
try {
|
|
2209
|
+
await this.getWallet({
|
|
1350
2210
|
accountAddress,
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
2211
|
+
walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
|
|
2212
|
+
password,
|
|
2213
|
+
signedSessionId
|
|
2214
|
+
});
|
|
2215
|
+
const currentThresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
2216
|
+
if (currentThresholdSignatureScheme === core.ThresholdSignatureScheme.TWO_OF_TWO) {
|
|
2217
|
+
// Reshare to 2-of-3, which will automatically handle the backup distribution
|
|
2218
|
+
await this.reshare({
|
|
2219
|
+
chainName: this.walletMap[accountAddress].chainName,
|
|
2220
|
+
accountAddress,
|
|
2221
|
+
oldThresholdSignatureScheme: currentThresholdSignatureScheme,
|
|
2222
|
+
newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
|
|
2223
|
+
password,
|
|
2224
|
+
signedSessionId,
|
|
2225
|
+
backupToGoogleDrive: true
|
|
2226
|
+
});
|
|
2227
|
+
const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
2228
|
+
const googleDriveShares = backupInfo.backups[core.BackupLocation.GOOGLE_DRIVE] || [];
|
|
2229
|
+
return googleDriveShares.map((ks)=>{
|
|
2230
|
+
var _ks_externalKeyShareId;
|
|
2231
|
+
return (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : '';
|
|
2232
|
+
});
|
|
2233
|
+
} else {
|
|
2234
|
+
await this.storeEncryptedBackupByWallet({
|
|
2235
|
+
accountAddress,
|
|
2236
|
+
password,
|
|
2237
|
+
signedSessionId,
|
|
2238
|
+
backupToGoogleDrive: true
|
|
2239
|
+
});
|
|
2240
|
+
const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
2241
|
+
const googleDriveShares = backupInfo.backups[core.BackupLocation.GOOGLE_DRIVE] || [];
|
|
2242
|
+
return googleDriveShares.map((ks)=>{
|
|
2243
|
+
var _ks_externalKeyShareId;
|
|
2244
|
+
return (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : '';
|
|
2245
|
+
});
|
|
1361
2246
|
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
})
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
fileName: fileName != null ? fileName : suggestedFileName,
|
|
1373
|
-
jsonData: backupData
|
|
1374
|
-
})
|
|
1375
|
-
]);
|
|
1376
|
-
const data = await this.apiClient.markKeySharesAsBackedUpGoogleDrive({
|
|
1377
|
-
walletId: this.walletMap[accountAddress].walletId
|
|
1378
|
-
});
|
|
1379
|
-
const ids = data.keyShares.map((keyShare)=>keyShare.id);
|
|
1380
|
-
this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[core.BackupLocation.GOOGLE_DRIVE] = ids;
|
|
1381
|
-
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
1382
|
-
return ids;
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
logError({
|
|
2249
|
+
message: 'Error in backupKeySharesToGoogleDrive',
|
|
2250
|
+
error: error,
|
|
2251
|
+
context: {
|
|
2252
|
+
accountAddress
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2255
|
+
throw error;
|
|
2256
|
+
}
|
|
1383
2257
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
2258
|
+
/**
|
|
2259
|
+
* This method handles only the Google Drive upload mechanics without any reshare logic.
|
|
2260
|
+
* It encrypts the provided key shares, uploads them to Google Drive, and updates the
|
|
2261
|
+
* backup metadata. This method is intended for internal use by storeEncryptedBackupByWallet
|
|
2262
|
+
* and should not be called directly from external code.
|
|
2263
|
+
*
|
|
2264
|
+
* @param params - The upload parameters
|
|
2265
|
+
* @param params.accountAddress - The wallet account address
|
|
2266
|
+
* @param params.password - Optional password for encryption (uses environment ID if not provided)
|
|
2267
|
+
* @param params.encryptedKeyShares - The specific key shares to upload to Google Drive
|
|
2268
|
+
* @returns Promise<string[]> - Array of Google Drive key share IDs that were uploaded
|
|
2269
|
+
*/ async uploadKeySharesToGoogleDrive({ accountAddress, encryptedKeyShares }) {
|
|
2270
|
+
try {
|
|
2271
|
+
if (encryptedKeyShares.length === 0) {
|
|
2272
|
+
throw new Error('No key shares found');
|
|
2273
|
+
}
|
|
2274
|
+
const oauthAccountId = await this.getGoogleOauthAccountIdOrThrow(accountAddress);
|
|
2275
|
+
const accessToken = await this.apiClient.getAccessToken({
|
|
2276
|
+
oauthAccountId
|
|
2277
|
+
});
|
|
2278
|
+
const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
2279
|
+
const fileName = getClientKeyShareExportFileName({
|
|
2280
|
+
thresholdSignatureScheme,
|
|
2281
|
+
accountAddress,
|
|
2282
|
+
isGoogleDrive: true
|
|
2283
|
+
});
|
|
2284
|
+
const backupData = createBackupData({
|
|
2285
|
+
encryptedKeyShares,
|
|
2286
|
+
accountAddress,
|
|
2287
|
+
thresholdSignatureScheme
|
|
2288
|
+
});
|
|
2289
|
+
await uploadBackupToGoogleDrive({
|
|
2290
|
+
accessToken,
|
|
2291
|
+
fileName,
|
|
2292
|
+
backupData,
|
|
2293
|
+
accountAddress
|
|
2294
|
+
});
|
|
2295
|
+
return;
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
logError({
|
|
2298
|
+
message: 'Error in backupKeySharesToGoogleDrive',
|
|
2299
|
+
error: error,
|
|
2300
|
+
context: {
|
|
2301
|
+
accountAddress
|
|
2302
|
+
}
|
|
2303
|
+
});
|
|
2304
|
+
throw error;
|
|
1402
2305
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
2306
|
+
}
|
|
2307
|
+
async exportClientKeysharesFromGoogleDrive({ accountAddress, password, signedSessionId }) {
|
|
2308
|
+
try {
|
|
2309
|
+
await this.getWallet({
|
|
2310
|
+
accountAddress,
|
|
2311
|
+
signedSessionId
|
|
2312
|
+
});
|
|
2313
|
+
const oauthAccountId = await this.getGoogleOauthAccountIdOrThrow(accountAddress);
|
|
2314
|
+
const accessToken = await this.apiClient.getAccessToken({
|
|
2315
|
+
oauthAccountId
|
|
2316
|
+
});
|
|
2317
|
+
const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
|
|
2318
|
+
const backupFileName = getClientKeyShareExportFileName({
|
|
2319
|
+
thresholdSignatureScheme,
|
|
2320
|
+
accountAddress,
|
|
2321
|
+
isGoogleDrive: true
|
|
2322
|
+
});
|
|
2323
|
+
let backupData = null;
|
|
2324
|
+
try {
|
|
2325
|
+
backupData = await retryPromise(()=>downloadFileFromGoogleDrive({
|
|
2326
|
+
accessToken,
|
|
2327
|
+
fileName: backupFileName
|
|
2328
|
+
}));
|
|
2329
|
+
} catch (error) {
|
|
2330
|
+
logError({
|
|
2331
|
+
message: 'Failed to download backup from Google Drive',
|
|
2332
|
+
error: error,
|
|
2333
|
+
context: {
|
|
2334
|
+
accountAddress,
|
|
2335
|
+
fileName: backupFileName
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
throw error;
|
|
2339
|
+
}
|
|
2340
|
+
if (!backupData) {
|
|
2341
|
+
const error = new Error('No backup file found');
|
|
2342
|
+
logError({
|
|
2343
|
+
message: 'No backup file found',
|
|
2344
|
+
error: new Error('No backup file found'),
|
|
2345
|
+
context: {
|
|
2346
|
+
accountAddress,
|
|
2347
|
+
fileName: backupFileName
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
throw error;
|
|
2351
|
+
}
|
|
2352
|
+
// Validate the backup data structure
|
|
2353
|
+
if (!backupData.keyShares || !backupData.metadata) {
|
|
2354
|
+
const error = new Error('Invalid backup format: missing keyShares or metadata');
|
|
2355
|
+
logError({
|
|
2356
|
+
message: 'Invalid backup format: missing keyShares or metadata',
|
|
2357
|
+
error,
|
|
2358
|
+
context: {
|
|
2359
|
+
accountAddress,
|
|
2360
|
+
fileName: backupFileName
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
throw error;
|
|
2364
|
+
}
|
|
2365
|
+
const { keyShares } = backupData;
|
|
2366
|
+
const decryptedKeyShares = await Promise.all(keyShares.map((keyShare)=>this.decryptKeyShare({
|
|
2367
|
+
keyShare,
|
|
2368
|
+
password
|
|
2369
|
+
})));
|
|
2370
|
+
const text = JSON.stringify(decryptedKeyShares, null, 2);
|
|
2371
|
+
const exportFileName = getClientKeyShareExportFileName({
|
|
2372
|
+
thresholdSignatureScheme,
|
|
2373
|
+
accountAddress,
|
|
2374
|
+
isGoogleDrive: true
|
|
2375
|
+
});
|
|
2376
|
+
downloadStringAsFile({
|
|
2377
|
+
filename: exportFileName,
|
|
2378
|
+
content: text,
|
|
2379
|
+
mimeType: 'application/json'
|
|
2380
|
+
});
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
logError({
|
|
2383
|
+
message: 'Error in exportClientKeysharesFromGoogleDrive',
|
|
2384
|
+
error: error,
|
|
2385
|
+
context: {
|
|
2386
|
+
accountAddress
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
throw error;
|
|
1406
2390
|
}
|
|
1407
|
-
const { keyShares } = backupData;
|
|
1408
|
-
const decryptedKeyShares = await Promise.all(keyShares.map((keyShare)=>this.decryptKeyShare({
|
|
1409
|
-
keyShare,
|
|
1410
|
-
password
|
|
1411
|
-
})));
|
|
1412
|
-
await this.setClientKeySharesToLocalStorage({
|
|
1413
|
-
accountAddress,
|
|
1414
|
-
clientKeyShares: decryptedKeyShares,
|
|
1415
|
-
overwriteOrMerge: 'merge'
|
|
1416
|
-
});
|
|
1417
|
-
// Display the client shares in the container via iframe
|
|
1418
|
-
const { iframeDisplay } = await this.initializeIframeDisplayForContainer({
|
|
1419
|
-
container: displayContainer
|
|
1420
|
-
});
|
|
1421
|
-
iframeDisplay.displayClientShares(accountAddress);
|
|
1422
|
-
return decryptedKeyShares;
|
|
1423
2391
|
}
|
|
1424
|
-
async exportClientKeyshares({ accountAddress, password }) {
|
|
2392
|
+
async exportClientKeyshares({ accountAddress, password, signedSessionId }) {
|
|
1425
2393
|
await this.verifyPassword({
|
|
1426
2394
|
accountAddress,
|
|
1427
2395
|
password,
|
|
1428
|
-
walletOperation: core.WalletOperation.REACH_ALL_PARTIES
|
|
2396
|
+
walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
|
|
2397
|
+
signedSessionId
|
|
1429
2398
|
});
|
|
1430
2399
|
const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
1431
2400
|
accountAddress
|
|
@@ -1438,22 +2407,18 @@ class DynamicWalletClient {
|
|
|
1438
2407
|
keyShares: clientKeyShares,
|
|
1439
2408
|
derivationPath
|
|
1440
2409
|
});
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
2410
|
+
downloadStringAsFile({
|
|
2411
|
+
filename: `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`,
|
|
2412
|
+
content: text,
|
|
2413
|
+
mimeType: 'text/plain'
|
|
1445
2414
|
});
|
|
1446
|
-
const url = URL.createObjectURL(blob);
|
|
1447
|
-
const a = document.createElement('a');
|
|
1448
|
-
a.href = url;
|
|
1449
|
-
a.download = `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`;
|
|
1450
|
-
a.click();
|
|
1451
2415
|
}
|
|
1452
|
-
async getClientKeyShares({ accountAddress, password }) {
|
|
2416
|
+
async getClientKeyShares({ accountAddress, password, signedSessionId }) {
|
|
1453
2417
|
await this.getWallet({
|
|
1454
2418
|
accountAddress,
|
|
1455
2419
|
password,
|
|
1456
|
-
walletOperation: core.WalletOperation.REACH_THRESHOLD
|
|
2420
|
+
walletOperation: core.WalletOperation.REACH_THRESHOLD,
|
|
2421
|
+
signedSessionId
|
|
1457
2422
|
});
|
|
1458
2423
|
return this.getClientKeySharesFromLocalStorage({
|
|
1459
2424
|
accountAddress
|
|
@@ -1506,11 +2471,12 @@ class DynamicWalletClient {
|
|
|
1506
2471
|
* verifyPassword attempts to recover and decrypt a single client key share using the provided password.
|
|
1507
2472
|
* If successful, the key share is encrypted with the new password. This method solely performs the recovery
|
|
1508
2473
|
* and decryption without storing the restored key shares. If unsuccessful, it throws an error.
|
|
1509
|
-
*/ async verifyPassword({ accountAddress, password = undefined, walletOperation = core.WalletOperation.NO_OPERATION }) {
|
|
2474
|
+
*/ async verifyPassword({ accountAddress, password = undefined, walletOperation = core.WalletOperation.NO_OPERATION, signedSessionId }) {
|
|
1510
2475
|
await this.getWallet({
|
|
1511
2476
|
accountAddress,
|
|
1512
2477
|
password,
|
|
1513
|
-
walletOperation
|
|
2478
|
+
walletOperation,
|
|
2479
|
+
signedSessionId
|
|
1514
2480
|
});
|
|
1515
2481
|
if (await this.requiresPasswordForOperation({
|
|
1516
2482
|
accountAddress,
|
|
@@ -1530,16 +2496,24 @@ class DynamicWalletClient {
|
|
|
1530
2496
|
throw new Error('No dynamic key shares found');
|
|
1531
2497
|
}
|
|
1532
2498
|
try {
|
|
2499
|
+
// TODO(zfaizal2): throw error if signedSessionId is not provided after service deploy
|
|
1533
2500
|
await this.recoverEncryptedBackupByWallet({
|
|
1534
2501
|
accountAddress,
|
|
1535
2502
|
password,
|
|
1536
2503
|
walletOperation,
|
|
1537
2504
|
shareCount: 1,
|
|
1538
|
-
storeRecoveredShares: false
|
|
2505
|
+
storeRecoveredShares: false,
|
|
2506
|
+
signedSessionId
|
|
1539
2507
|
});
|
|
1540
2508
|
} catch (error) {
|
|
1541
|
-
|
|
1542
|
-
|
|
2509
|
+
logError({
|
|
2510
|
+
message: 'Error in verifyPassword',
|
|
2511
|
+
error: error,
|
|
2512
|
+
context: {
|
|
2513
|
+
accountAddress
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
2516
|
+
throw error;
|
|
1543
2517
|
}
|
|
1544
2518
|
}
|
|
1545
2519
|
async isPasswordEncrypted({ accountAddress }) {
|
|
@@ -1585,127 +2559,204 @@ class DynamicWalletClient {
|
|
|
1585
2559
|
return true;
|
|
1586
2560
|
}
|
|
1587
2561
|
async getWalletClientKeyShareBackupInfo({ accountAddress }) {
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
2562
|
+
const dynamicRequestId = uuid.v4();
|
|
2563
|
+
try {
|
|
2564
|
+
var _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress, _user_verifiedCredentials;
|
|
2565
|
+
// Return existing backup info if it exists
|
|
2566
|
+
if (((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo = _this_walletMap_accountAddress.clientKeySharesBackupInfo) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups = _this_walletMap_accountAddress_clientKeySharesBackupInfo.backups) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC = _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups[core.BackupLocation.DYNAMIC]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC.length) > 0) {
|
|
2567
|
+
return this.walletMap[accountAddress].clientKeySharesBackupInfo;
|
|
2568
|
+
}
|
|
2569
|
+
// Get backup info from server
|
|
2570
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2571
|
+
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
2572
|
+
return getClientKeyShareBackupInfo({
|
|
2573
|
+
walletProperties: wallet == null ? void 0 : wallet.walletProperties
|
|
2574
|
+
});
|
|
2575
|
+
} catch (error) {
|
|
2576
|
+
logError({
|
|
2577
|
+
message: 'Error in getWalletClientKeyShareBackupInfo',
|
|
2578
|
+
error: error,
|
|
2579
|
+
context: {
|
|
2580
|
+
accountAddress,
|
|
2581
|
+
dynamicRequestId
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2584
|
+
throw error;
|
|
1592
2585
|
}
|
|
1593
|
-
// Get backup info from server
|
|
1594
|
-
const user = await this.apiClient.getUser();
|
|
1595
|
-
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
1596
|
-
return getClientKeyShareBackupInfo({
|
|
1597
|
-
walletProperties: wallet == null ? void 0 : wallet.walletProperties
|
|
1598
|
-
});
|
|
1599
2586
|
}
|
|
1600
|
-
async getWallet({ accountAddress, walletOperation = core.WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
shareCount
|
|
1606
|
-
});
|
|
1607
|
-
if (existingWalletCheck) {
|
|
1608
|
-
this.logger.debug(`Wallet ${accountAddress} already exists`);
|
|
1609
|
-
return this.walletMap[accountAddress];
|
|
1610
|
-
}
|
|
1611
|
-
// Fetch and restore wallet from server
|
|
1612
|
-
const user = await this.apiClient.getUser();
|
|
1613
|
-
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
1614
|
-
this.logger.debug('Restoring wallet', wallet);
|
|
1615
|
-
const walletProperties = wallet.walletProperties;
|
|
1616
|
-
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
1617
|
-
walletId: wallet.id,
|
|
1618
|
-
chainName: wallet.chainName,
|
|
1619
|
-
accountAddress,
|
|
1620
|
-
thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
|
|
1621
|
-
derivationPath: walletProperties.derivationPath,
|
|
1622
|
-
clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
|
|
1623
|
-
walletProperties
|
|
1624
|
-
})
|
|
1625
|
-
});
|
|
1626
|
-
if (walletOperation !== core.WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1627
|
-
accountAddress,
|
|
1628
|
-
walletOperation
|
|
1629
|
-
})) {
|
|
1630
|
-
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
2587
|
+
async getWallet({ accountAddress, walletOperation = core.WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined, signedSessionId }) {
|
|
2588
|
+
const dynamicRequestId = uuid.v4();
|
|
2589
|
+
try {
|
|
2590
|
+
var _user_verifiedCredentials;
|
|
2591
|
+
const existingWalletCheck = await this.checkWalletFields({
|
|
1631
2592
|
accountAddress,
|
|
1632
|
-
|
|
1633
|
-
walletOperation: walletOperation,
|
|
2593
|
+
walletOperation,
|
|
1634
2594
|
shareCount
|
|
1635
2595
|
});
|
|
1636
|
-
|
|
1637
|
-
accountAddress
|
|
2596
|
+
if (existingWalletCheck) {
|
|
2597
|
+
this.logger.debug(`[DynamicWaasWalletClient] Wallet ${accountAddress} already exists`);
|
|
2598
|
+
return this.walletMap[accountAddress];
|
|
2599
|
+
}
|
|
2600
|
+
// Fetch and restore wallet from server
|
|
2601
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2602
|
+
const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
|
|
2603
|
+
this.logger.debug('[DynamicWaasWalletClient] Restoring wallet', wallet);
|
|
2604
|
+
const walletProperties = wallet.walletProperties;
|
|
2605
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
2606
|
+
walletId: wallet.id,
|
|
2607
|
+
chainName: core.verifiedCredentialNameToChainEnum[wallet.chain],
|
|
2608
|
+
accountAddress,
|
|
2609
|
+
thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
|
|
2610
|
+
derivationPath: walletProperties.derivationPath,
|
|
2611
|
+
clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
|
|
2612
|
+
walletProperties
|
|
2613
|
+
})
|
|
1638
2614
|
});
|
|
1639
|
-
await this.
|
|
2615
|
+
if (walletOperation !== core.WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
|
|
1640
2616
|
accountAddress,
|
|
1641
|
-
|
|
2617
|
+
walletOperation
|
|
2618
|
+
})) {
|
|
2619
|
+
// TODO(zfaizal2): throw error if signedSessionId is not provided after service deploy
|
|
2620
|
+
const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
|
|
2621
|
+
accountAddress,
|
|
2622
|
+
password: password != null ? password : this.environmentId,
|
|
2623
|
+
walletOperation: walletOperation,
|
|
2624
|
+
signedSessionId,
|
|
2625
|
+
shareCount
|
|
2626
|
+
});
|
|
2627
|
+
const existingKeyShares = await this.getClientKeySharesFromLocalStorage({
|
|
2628
|
+
accountAddress
|
|
2629
|
+
});
|
|
2630
|
+
await this.setClientKeySharesToLocalStorage({
|
|
2631
|
+
accountAddress,
|
|
2632
|
+
clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
|
|
2633
|
+
});
|
|
2634
|
+
this.logger.debug('[DynamicWaasWalletClient] Recovered backup', decryptedKeyShares);
|
|
2635
|
+
}
|
|
2636
|
+
const walletCount = Object.keys(this.walletMap).length;
|
|
2637
|
+
if (walletCount === 0) {
|
|
2638
|
+
throw new Error('No wallets found');
|
|
2639
|
+
}
|
|
2640
|
+
// Return the only wallet if there's just one
|
|
2641
|
+
if (walletCount === 1) {
|
|
2642
|
+
return Object.values(this.walletMap)[0];
|
|
2643
|
+
}
|
|
2644
|
+
return this.walletMap[accountAddress];
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
logError({
|
|
2647
|
+
message: 'Error in getWallet',
|
|
2648
|
+
error: error,
|
|
2649
|
+
context: {
|
|
2650
|
+
accountAddress,
|
|
2651
|
+
walletOperation,
|
|
2652
|
+
shareCount,
|
|
2653
|
+
dynamicRequestId
|
|
2654
|
+
}
|
|
1642
2655
|
});
|
|
1643
|
-
|
|
1644
|
-
}
|
|
1645
|
-
const walletCount = Object.keys(this.walletMap).length;
|
|
1646
|
-
if (walletCount === 0) {
|
|
1647
|
-
throw new Error('No wallets found');
|
|
1648
|
-
}
|
|
1649
|
-
// Return the only wallet if there's just one
|
|
1650
|
-
if (walletCount === 1) {
|
|
1651
|
-
return Object.values(this.walletMap)[0];
|
|
2656
|
+
throw error;
|
|
1652
2657
|
}
|
|
1653
|
-
return this.walletMap[accountAddress];
|
|
1654
2658
|
}
|
|
1655
2659
|
async getWallets() {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
2660
|
+
const dynamicRequestId = uuid.v4();
|
|
2661
|
+
try {
|
|
2662
|
+
var _user_verifiedCredentials;
|
|
2663
|
+
const user = await this.apiClient.getUser(dynamicRequestId);
|
|
2664
|
+
this.userId = user.id;
|
|
2665
|
+
const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
|
|
2666
|
+
const wallets = waasWallets.map((vc)=>{
|
|
2667
|
+
var _this_walletMap_vc_address, _vc_walletProperties;
|
|
2668
|
+
var _this_walletMap_vc_address_derivationPath;
|
|
2669
|
+
return {
|
|
2670
|
+
walletId: vc.id,
|
|
2671
|
+
chainName: vc.chain,
|
|
2672
|
+
accountAddress: vc.address,
|
|
2673
|
+
clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
|
|
2674
|
+
walletProperties: vc.walletProperties || {}
|
|
2675
|
+
}),
|
|
2676
|
+
derivationPath: (_this_walletMap_vc_address_derivationPath = (_this_walletMap_vc_address = this.walletMap[vc.address]) == null ? void 0 : _this_walletMap_vc_address.derivationPath) != null ? _this_walletMap_vc_address_derivationPath : undefined,
|
|
2677
|
+
thresholdSignatureScheme: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.thresholdSignatureScheme
|
|
2678
|
+
};
|
|
2679
|
+
});
|
|
2680
|
+
this.walletMap = wallets.reduce((acc, wallet)=>{
|
|
2681
|
+
var _acc_accountAddress;
|
|
2682
|
+
const accountAddress = wallet.accountAddress;
|
|
2683
|
+
acc[wallet.accountAddress] = {
|
|
2684
|
+
walletId: wallet.walletId,
|
|
2685
|
+
chainName: wallet.chainName,
|
|
2686
|
+
accountAddress: wallet.accountAddress,
|
|
2687
|
+
clientKeySharesBackupInfo: wallet.clientKeySharesBackupInfo,
|
|
2688
|
+
derivationPath: ((_acc_accountAddress = acc[accountAddress]) == null ? void 0 : _acc_accountAddress.derivationPath) || undefined,
|
|
2689
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme
|
|
2690
|
+
};
|
|
2691
|
+
return acc;
|
|
2692
|
+
}, {});
|
|
2693
|
+
return wallets;
|
|
2694
|
+
} catch (error) {
|
|
2695
|
+
logError({
|
|
2696
|
+
message: 'Error in getWallets',
|
|
2697
|
+
error: error,
|
|
2698
|
+
context: {
|
|
2699
|
+
dynamicRequestId
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
throw error;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* sync auth token with api client
|
|
2707
|
+
* @param authToken - auth token to sync
|
|
2708
|
+
*/ syncAuthToken(authToken) {
|
|
2709
|
+
if (this.authMode === core.AuthMode.COOKIE) {
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
this.apiClient.syncAuthToken(authToken);
|
|
1687
2713
|
}
|
|
1688
|
-
|
|
2714
|
+
/**
|
|
2715
|
+
* Helper method to instrument with automatic properties inclusion
|
|
2716
|
+
*/ instrument(message, context) {
|
|
2717
|
+
const defaultContext = {
|
|
2718
|
+
environmentId: context.environmentId || this.environmentId
|
|
2719
|
+
};
|
|
2720
|
+
this.logger.debug(message, _extends({}, defaultContext, context));
|
|
2721
|
+
this.logger.instrument(message, _extends({}, defaultContext, context));
|
|
2722
|
+
}
|
|
2723
|
+
getTraceContext(traceContext) {
|
|
2724
|
+
const now = Date.now();
|
|
2725
|
+
return _extends({
|
|
2726
|
+
now,
|
|
2727
|
+
time: (traceContext == null ? void 0 : traceContext.startTime) ? now - traceContext.startTime : 0
|
|
2728
|
+
}, traceContext);
|
|
2729
|
+
}
|
|
2730
|
+
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, featureFlags, authMode = core.AuthMode.HEADER, authToken = undefined, // Represents the version of the client SDK used by developer
|
|
2731
|
+
sdkVersion, forwardMPCClient, baseClientKeysharesRelayApiUrl }){
|
|
2732
|
+
this.userId = undefined;
|
|
2733
|
+
this.sessionId = undefined;
|
|
1689
2734
|
this.initializePromise = null;
|
|
1690
2735
|
this.logger = logger;
|
|
1691
2736
|
this.walletMap = {} // todo: store in session storage
|
|
1692
2737
|
;
|
|
1693
|
-
this.iframeStorage = null;
|
|
1694
|
-
this.iframeDisplay = null;
|
|
1695
2738
|
this.memoryStorage = null;
|
|
1696
|
-
this.messageTransport = null;
|
|
1697
2739
|
this.iframe = null;
|
|
1698
|
-
this.
|
|
2740
|
+
this.forwardMPCEnabled = false;
|
|
2741
|
+
this.featureFlags = {};
|
|
1699
2742
|
this.environmentId = environmentId;
|
|
1700
2743
|
this.storageKey = `${STORAGE_KEY}-${storageKey != null ? storageKey : environmentId}`;
|
|
1701
2744
|
this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
|
|
2745
|
+
this.authMode = authMode;
|
|
2746
|
+
this.sdkVersion = sdkVersion;
|
|
2747
|
+
this.baseClientKeysharesRelayApiUrl = baseClientKeysharesRelayApiUrl;
|
|
1702
2748
|
this.apiClient = new core.DynamicApiClient({
|
|
1703
2749
|
environmentId,
|
|
1704
2750
|
authToken,
|
|
1705
|
-
baseApiUrl
|
|
2751
|
+
baseApiUrl,
|
|
2752
|
+
authMode,
|
|
2753
|
+
sdkVersion,
|
|
2754
|
+
forwardMPCClient,
|
|
2755
|
+
baseClientKeysharesRelayApiUrl
|
|
1706
2756
|
});
|
|
1707
2757
|
this.debug = Boolean(debug);
|
|
1708
|
-
this.logger.setLogLevel(this.debug ?
|
|
2758
|
+
this.logger.setLogLevel(this.debug ? logger$1.LogLevel.DEBUG : DEFAULT_LOG_LEVEL);
|
|
2759
|
+
this.featureFlags = featureFlags || {};
|
|
1709
2760
|
// setup storage
|
|
1710
2761
|
if (supportsLocalStorage()) {
|
|
1711
2762
|
this.storage = localStorageAdapter;
|
|
@@ -1713,15 +2764,32 @@ class DynamicWalletClient {
|
|
|
1713
2764
|
this.memoryStorage = {};
|
|
1714
2765
|
this.storage = memoryLocalStorageAdapter(this.memoryStorage);
|
|
1715
2766
|
}
|
|
1716
|
-
|
|
1717
|
-
this.iframeDomain = core.
|
|
2767
|
+
const environment = core.getEnvironmentFromUrl(baseApiUrl);
|
|
2768
|
+
this.iframeDomain = core.IFRAME_DOMAIN_MAP[environment];
|
|
1718
2769
|
// Generate unique instanceId when client is created
|
|
1719
|
-
this.instanceId =
|
|
1720
|
-
// initialize
|
|
1721
|
-
|
|
2770
|
+
this.instanceId = uuid.v4();
|
|
2771
|
+
// initialize logger context
|
|
2772
|
+
if (authMode === core.AuthMode.HEADER && authToken) {
|
|
2773
|
+
this.initLoggerContext(authToken);
|
|
2774
|
+
}
|
|
2775
|
+
this.forwardMPCEnabled = this.featureFlags && this.featureFlags[core.FEATURE_FLAGS.ENABLE_FORWARD_MPC_CLIENT_FLAG] === true;
|
|
2776
|
+
// if forwardMPCEnabled is true and forwardMPCClient is not provided, initialize the forwardMPCClient
|
|
2777
|
+
if (this.forwardMPCEnabled && !forwardMPCClient) {
|
|
2778
|
+
this.initializeForwardMPCClient();
|
|
2779
|
+
}
|
|
1722
2780
|
}
|
|
1723
2781
|
}
|
|
1724
2782
|
|
|
2783
|
+
const ERROR_KEYGEN_FAILED = '[DynamicWaasWalletClient]: Error with keygen';
|
|
2784
|
+
const ERROR_CREATE_WALLET_ACCOUNT = '[DynamicWaasWalletClient]: Error creating wallet account';
|
|
2785
|
+
const ERROR_SIGN_MESSAGE = '[DynamicWaasWalletClient]: Error signing message';
|
|
2786
|
+
const ERROR_SIGN_TYPED_DATA = '[DynamicWaasWalletClient]: Error signing typed data';
|
|
2787
|
+
const ERROR_ACCOUNT_ADDRESS_REQUIRED = '[DynamicWaasWalletClient]: Account address is required';
|
|
2788
|
+
const ERROR_VERIFY_MESSAGE_SIGNATURE = '[DynamicWaasWalletClient]: Error verifying message signature';
|
|
2789
|
+
const ERROR_VERIFY_TRANSACTION_SIGNATURE = '[DynamicWaasWalletClient]: Error verifying transaction signature';
|
|
2790
|
+
const ERROR_EXPORT_PRIVATE_KEY = '[DynamicWaasWalletClient]: Error exporting private key';
|
|
2791
|
+
const ERROR_IMPORT_PRIVATE_KEY = '[DynamicWaasWalletClient]: Error importing private key';
|
|
2792
|
+
|
|
1725
2793
|
Object.defineProperty(exports, "BIP340", {
|
|
1726
2794
|
enumerable: true,
|
|
1727
2795
|
get: function () { return web.BIP340; }
|
|
@@ -1770,19 +2838,40 @@ Object.defineProperty(exports, "MessageHash", {
|
|
|
1770
2838
|
enumerable: true,
|
|
1771
2839
|
get: function () { return web.MessageHash; }
|
|
1772
2840
|
});
|
|
2841
|
+
Object.defineProperty(exports, "Logger", {
|
|
2842
|
+
enumerable: true,
|
|
2843
|
+
get: function () { return logger$1.Logger; }
|
|
2844
|
+
});
|
|
1773
2845
|
exports.DynamicWalletClient = DynamicWalletClient;
|
|
1774
|
-
exports.
|
|
1775
|
-
exports.
|
|
1776
|
-
exports.
|
|
2846
|
+
exports.ERROR_ACCOUNT_ADDRESS_REQUIRED = ERROR_ACCOUNT_ADDRESS_REQUIRED;
|
|
2847
|
+
exports.ERROR_CREATE_WALLET_ACCOUNT = ERROR_CREATE_WALLET_ACCOUNT;
|
|
2848
|
+
exports.ERROR_EXPORT_PRIVATE_KEY = ERROR_EXPORT_PRIVATE_KEY;
|
|
2849
|
+
exports.ERROR_IMPORT_PRIVATE_KEY = ERROR_IMPORT_PRIVATE_KEY;
|
|
2850
|
+
exports.ERROR_KEYGEN_FAILED = ERROR_KEYGEN_FAILED;
|
|
2851
|
+
exports.ERROR_SIGN_MESSAGE = ERROR_SIGN_MESSAGE;
|
|
2852
|
+
exports.ERROR_SIGN_TYPED_DATA = ERROR_SIGN_TYPED_DATA;
|
|
2853
|
+
exports.ERROR_VERIFY_MESSAGE_SIGNATURE = ERROR_VERIFY_MESSAGE_SIGNATURE;
|
|
2854
|
+
exports.ERROR_VERIFY_TRANSACTION_SIGNATURE = ERROR_VERIFY_TRANSACTION_SIGNATURE;
|
|
2855
|
+
exports.createAddGoogleDriveToExistingDelegationDistribution = createAddGoogleDriveToExistingDelegationDistribution;
|
|
2856
|
+
exports.createBackupData = createBackupData;
|
|
2857
|
+
exports.createDelegationOnlyDistribution = createDelegationOnlyDistribution;
|
|
2858
|
+
exports.createDelegationWithGoogleDriveDistribution = createDelegationWithGoogleDriveDistribution;
|
|
2859
|
+
exports.createDynamicOnlyDistribution = createDynamicOnlyDistribution;
|
|
2860
|
+
exports.createGoogleDriveOnlyDistribution = createGoogleDriveOnlyDistribution;
|
|
2861
|
+
exports.downloadStringAsFile = downloadStringAsFile;
|
|
2862
|
+
exports.formatEvmMessage = formatEvmMessage;
|
|
2863
|
+
exports.formatMessage = formatMessage;
|
|
1777
2864
|
exports.getClientKeyShareBackupInfo = getClientKeyShareBackupInfo;
|
|
1778
2865
|
exports.getClientKeyShareExportFileName = getClientKeyShareExportFileName;
|
|
2866
|
+
exports.getGoogleOAuthAccountId = getGoogleOAuthAccountId;
|
|
1779
2867
|
exports.getMPCSignatureScheme = getMPCSignatureScheme;
|
|
1780
2868
|
exports.getMPCSigner = getMPCSigner;
|
|
2869
|
+
exports.hasDelegatedBackup = hasDelegatedBackup;
|
|
2870
|
+
exports.hasGoogleDriveBackup = hasGoogleDriveBackup;
|
|
1781
2871
|
exports.isBrowser = isBrowser;
|
|
1782
2872
|
exports.isHexString = isHexString;
|
|
1783
2873
|
exports.mergeUniqueKeyShares = mergeUniqueKeyShares;
|
|
1784
2874
|
exports.retryPromise = retryPromise;
|
|
1785
|
-
exports.stringToBytes = stringToBytes;
|
|
1786
2875
|
exports.timeoutPromise = timeoutPromise;
|
|
1787
2876
|
Object.keys(core).forEach(function (k) {
|
|
1788
2877
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|