@dynamic-labs-wallet/browser 1.1.34 → 1.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +707 -86
- package/index.esm.js +708 -88
- package/package.json +3 -3
- package/src/client.d.ts +179 -4
- package/src/client.d.ts.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/queue.d.ts +8 -1
- package/src/queue.d.ts.map +1 -1
- package/src/services/forwardMpcEnclaveState.d.ts +22 -0
- package/src/services/forwardMpcEnclaveState.d.ts.map +1 -0
- package/src/services/logger.d.ts +11 -0
- package/src/services/logger.d.ts.map +1 -1
package/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, DEFAULT_ED25519_VARIANT, getMPCChainConfig, parseNamespacedVersion, WalletApiError, NETWORK_ERROR_STATUS, formatRawMessageForSigning, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, createRejectablePromise, handleAxiosError, WalletOperation, ThresholdSignatureScheme, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, getClientThreshold, MPC_CONFIG, normalizeAndValidatePrivateKey, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, isBusinessAccountActionRequired, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
|
|
1
|
+
import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, DEFAULT_ED25519_VARIANT, getMPCChainConfig, parseNamespacedVersion, WalletApiError, NETWORK_ERROR_STATUS, formatRawMessageForSigning, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, createRejectablePromise, handleAxiosError, extractServerErrorCode, WalletOperation, ThresholdSignatureScheme, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, getClientThreshold, MPC_CONFIG, normalizeAndValidatePrivateKey, getTSSConfig, toJsonSafeContext, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, withTraceId, attachRequestIds, getReshareConfig, isBusinessAccountActionRequired, createExportIntent, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
|
|
2
2
|
export * from '@dynamic-labs-wallet/core';
|
|
3
3
|
export { Logger } from '@dynamic-labs-wallet/core';
|
|
4
4
|
import { EdBls12377, BIP340, Ed25519, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, Ed25519KeygenResult, BIP340KeygenResult } from '#internal/web';
|
|
@@ -2601,6 +2601,45 @@ const classifyOne = (error)=>{
|
|
|
2601
2601
|
}
|
|
2602
2602
|
return fallback;
|
|
2603
2603
|
};
|
|
2604
|
+
/** The HTTP status and backend code of one link in a `cause` chain, or `undefined` when it is not a rejected request. */ const httpFieldsOf = (error)=>{
|
|
2605
|
+
if (error instanceof AxiosError && error.response) {
|
|
2606
|
+
const errorCode = extractServerErrorCode(error);
|
|
2607
|
+
return _extends({
|
|
2608
|
+
httpStatus: error.response.status
|
|
2609
|
+
}, errorCode ? {
|
|
2610
|
+
errorCode
|
|
2611
|
+
} : {});
|
|
2612
|
+
}
|
|
2613
|
+
const status = getWalletApiErrorStatus(error);
|
|
2614
|
+
if (status === undefined) return undefined;
|
|
2615
|
+
const { code } = error;
|
|
2616
|
+
return _extends({
|
|
2617
|
+
httpStatus: status
|
|
2618
|
+
}, typeof code === 'string' ? {
|
|
2619
|
+
errorCode: code
|
|
2620
|
+
} : {});
|
|
2621
|
+
};
|
|
2622
|
+
/**
|
|
2623
|
+
* The fields a failure is filtered by: its class, the HTTP status and the
|
|
2624
|
+
* backend's own code when a request was rejected. Walks the `cause` chain so a
|
|
2625
|
+
* chain client's wrapper does not hide the API error beneath it. Scalars only,
|
|
2626
|
+
* so nothing from a response body can carry key material into a log.
|
|
2627
|
+
*/ const describeErrorForLog = (error)=>{
|
|
2628
|
+
const errorName = error instanceof Error ? error.name : typeof error;
|
|
2629
|
+
let current = error;
|
|
2630
|
+
for(let depth = 0; current !== undefined && current !== null && depth < MAX_CAUSE_DEPTH; depth += 1){
|
|
2631
|
+
const httpFields = httpFieldsOf(current);
|
|
2632
|
+
if (httpFields) return _extends({
|
|
2633
|
+
errorName
|
|
2634
|
+
}, httpFields);
|
|
2635
|
+
const next = current instanceof Error ? current.cause : undefined;
|
|
2636
|
+
if (next === current) break;
|
|
2637
|
+
current = next;
|
|
2638
|
+
}
|
|
2639
|
+
return {
|
|
2640
|
+
errorName
|
|
2641
|
+
};
|
|
2642
|
+
};
|
|
2604
2643
|
/**
|
|
2605
2644
|
* Decides how an operation failure reaches the Datadog logger, whose signature
|
|
2606
2645
|
* is `(message, context, error)`.
|
|
@@ -3036,9 +3075,10 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3036
3075
|
* @param accountAddress - The wallet address
|
|
3037
3076
|
* @param operation - The operation type (must be a valid SignQueueOperation)
|
|
3038
3077
|
* @param callback - The sign operation to execute
|
|
3078
|
+
* @param options - Optional diagnostics attached to timeout logs
|
|
3039
3079
|
* @throws Error if operation is not a valid sign queue operation
|
|
3040
3080
|
* @returns Promise resolving to the sign result
|
|
3041
|
-
*/ static async queueSignOperation(accountAddress, operation, callback) {
|
|
3081
|
+
*/ static async queueSignOperation(accountAddress, operation, callback, options) {
|
|
3042
3082
|
accountAddress = normalizeAddress(accountAddress);
|
|
3043
3083
|
// Runtime validation to catch any type assertion bypasses
|
|
3044
3084
|
if (!isSignQueueOperation(operation)) {
|
|
@@ -3061,6 +3101,13 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3061
3101
|
let currentPhase = 'queued';
|
|
3062
3102
|
let phaseStartedAt = Date.now();
|
|
3063
3103
|
const signDetails = {};
|
|
3104
|
+
const enclaveContext = ()=>{
|
|
3105
|
+
var _options_getEnclaveSnapshot;
|
|
3106
|
+
const forwardMpcEnclave = options == null ? void 0 : (_options_getEnclaveSnapshot = options.getEnclaveSnapshot) == null ? void 0 : _options_getEnclaveSnapshot.call(options);
|
|
3107
|
+
return forwardMpcEnclave ? {
|
|
3108
|
+
forwardMpcEnclave
|
|
3109
|
+
} : {};
|
|
3110
|
+
};
|
|
3064
3111
|
// The task keeps running after the queue timeout rejects `add()`, so keep
|
|
3065
3112
|
// a reference to observe how it eventually settles.
|
|
3066
3113
|
let taskPromise;
|
|
@@ -3116,7 +3163,7 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3116
3163
|
signQueuePending: signQueue.pending,
|
|
3117
3164
|
signQueueSize: signQueue.size,
|
|
3118
3165
|
recoveryInProgress: this.isRecoveryInProgress(accountAddress)
|
|
3119
|
-
}, signDetails),
|
|
3166
|
+
}, enclaveContext(), signDetails),
|
|
3120
3167
|
level: 'warn'
|
|
3121
3168
|
});
|
|
3122
3169
|
// The caller already saw an error; record how the orphaned ceremony
|
|
@@ -3134,7 +3181,7 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3134
3181
|
operation,
|
|
3135
3182
|
outcome: 'resolved',
|
|
3136
3183
|
settledMs: Date.now() - timedOutAt
|
|
3137
|
-
}, signDetails),
|
|
3184
|
+
}, enclaveContext(), signDetails),
|
|
3138
3185
|
level: 'warn'
|
|
3139
3186
|
});
|
|
3140
3187
|
}, (lateError)=>{
|
|
@@ -3147,7 +3194,7 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3147
3194
|
outcome: 'rejected',
|
|
3148
3195
|
settledMs: Date.now() - timedOutAt,
|
|
3149
3196
|
causes: describeCauseChain(lateError)
|
|
3150
|
-
}, signDetails),
|
|
3197
|
+
}, enclaveContext(), signDetails),
|
|
3151
3198
|
level: 'warn'
|
|
3152
3199
|
});
|
|
3153
3200
|
});
|
|
@@ -3239,6 +3286,46 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3239
3286
|
/** Track which wallets are currently executing inside a sign operation.
|
|
3240
3287
|
* Used to prevent deadlocks when recovery is called from within a sign op. */ WalletQueueManager.walletsWithActiveSignOp = new Map();
|
|
3241
3288
|
|
|
3289
|
+
const FLAP_WINDOW_MS = 60000;
|
|
3290
|
+
// Bounds memory on a socket that flaps indefinitely; the count saturates here.
|
|
3291
|
+
const MAX_TRACKED_DISCONNECTS = 50;
|
|
3292
|
+
// The iframe shares one socket across every chain client; one history per socket.
|
|
3293
|
+
const readers = new WeakMap();
|
|
3294
|
+
/**
|
|
3295
|
+
* Starts recording enclave disconnects and returns a reader for the current
|
|
3296
|
+
* state. Idempotent per client: call it right after creating the socket (before
|
|
3297
|
+
* `connect()`) so startup disconnects are kept, and later calls reuse the same
|
|
3298
|
+
* history instead of stacking listeners.
|
|
3299
|
+
*/ const trackForwardMpcEnclave = (client)=>{
|
|
3300
|
+
const existing = readers.get(client);
|
|
3301
|
+
if (existing) {
|
|
3302
|
+
return existing;
|
|
3303
|
+
}
|
|
3304
|
+
const disconnects = [];
|
|
3305
|
+
client.on('disconnected', (closeCode)=>{
|
|
3306
|
+
disconnects.push({
|
|
3307
|
+
at: Date.now(),
|
|
3308
|
+
closeCode
|
|
3309
|
+
});
|
|
3310
|
+
if (disconnects.length > MAX_TRACKED_DISCONNECTS) {
|
|
3311
|
+
disconnects.shift();
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
const reader = ()=>{
|
|
3315
|
+
const now = Date.now();
|
|
3316
|
+
const last = disconnects.at(-1);
|
|
3317
|
+
return _extends({
|
|
3318
|
+
connected: client.connected,
|
|
3319
|
+
disconnectsLastMinute: disconnects.filter(({ at })=>now - at <= FLAP_WINDOW_MS).length
|
|
3320
|
+
}, last && {
|
|
3321
|
+
lastCloseCode: last.closeCode,
|
|
3322
|
+
msSinceLastDisconnect: now - last.at
|
|
3323
|
+
});
|
|
3324
|
+
};
|
|
3325
|
+
readers.set(client, reader);
|
|
3326
|
+
return reader;
|
|
3327
|
+
};
|
|
3328
|
+
|
|
3242
3329
|
// A DYNAMIC backup can persist multiple shares (e.g. 2-of-3); the singular
|
|
3243
3330
|
// externalKeyShareId is a fallback for older single-id shapes.
|
|
3244
3331
|
const collectDynamicKeyShareIds = (locations)=>{
|
|
@@ -3767,6 +3854,11 @@ class DynamicWalletClient {
|
|
|
3767
3854
|
});
|
|
3768
3855
|
});
|
|
3769
3856
|
}
|
|
3857
|
+
queueSign(accountAddress, task) {
|
|
3858
|
+
return WalletQueueManager.queueSignOperation(accountAddress, WalletOperation.SIGN_MESSAGE, task, {
|
|
3859
|
+
getEnclaveSnapshot: this.enclaveSnapshot
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3770
3862
|
connectForwardMPC() {
|
|
3771
3863
|
const forwardMPCConnectStartedAt = Date.now();
|
|
3772
3864
|
this.apiClient.forwardMPCClient.connect().then(()=>{
|
|
@@ -4668,7 +4760,7 @@ class DynamicWalletClient {
|
|
|
4668
4760
|
}
|
|
4669
4761
|
// Note: the server signing party (wallet-service) selects the ed25519
|
|
4670
4762
|
// protocol variant from its own persisted EAC, so it is NOT threaded here.
|
|
4671
|
-
const serializedContext = context ?
|
|
4763
|
+
const serializedContext = context ? toJsonSafeContext(context) : undefined;
|
|
4672
4764
|
const useHttpTransport = this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT_SIGNMESSAGE] === true;
|
|
4673
4765
|
const params = {
|
|
4674
4766
|
walletId,
|
|
@@ -4691,6 +4783,45 @@ class DynamicWalletClient {
|
|
|
4691
4783
|
})) : await this.apiClient.signMessage(params);
|
|
4692
4784
|
return data;
|
|
4693
4785
|
}
|
|
4786
|
+
/**
|
|
4787
|
+
* The server half of `signAndBroadcast`: asks Dynamic to open the signing rooms and returns the transaction record.
|
|
4788
|
+
*
|
|
4789
|
+
* @param params.walletId - The wallet that signs.
|
|
4790
|
+
* @param params.shareSetId - Which of the wallet's key sets to use. Defaults to its active one.
|
|
4791
|
+
* @param params.messages - The transaction once per fee attempt, in fee order, as the bytes to sign (RLP hex on EVM, message bytes on Solana). Not hashed.
|
|
4792
|
+
* @param params.contexts - What each message does in readable form (chain, recipient, amount), one per message in the same order. Dynamic checks each against the wallet and its policies.
|
|
4793
|
+
* @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
|
|
4794
|
+
* @param params.roomIds - Signing rooms you already opened, one per message. Send all or none; Dynamic opens them when omitted.
|
|
4795
|
+
* @param params.requestedCertainty - How sure to be before calling it confirmed: `fast`, `safe` or `final`, or `chain:` plus a chain-specific value. Defaults to `safe`.
|
|
4796
|
+
* @param params.externalReference - Your own label, returned on every event. Not unique.
|
|
4797
|
+
* @param params.idempotencyKey - Makes a retry safe: the same key replays the stored answer instead of sending twice.
|
|
4798
|
+
* @param params.mfaToken - Proof of the user's multi-factor check, when the wallet needs one.
|
|
4799
|
+
* @param params.elevatedAccessToken - Proof of a recent re-login, when the wallet needs one.
|
|
4800
|
+
* @param params.approvalQuorum - Approvers' consent for a governed wallet, sent on the retry after `intent_required`.
|
|
4801
|
+
* @param params.onError - Called with the failure's message, never the raw error.
|
|
4802
|
+
* @param params.dynamicRequestId - Id of this one request, to match client and server logs.
|
|
4803
|
+
* @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
|
|
4804
|
+
*/ async serverSignAndBroadcast({ walletId, shareSetId, contexts, broadcastOptions, messages, roomIds, requestedCertainty, externalReference, idempotencyKey, mfaToken, elevatedAccessToken, approvalQuorum, onError, dynamicRequestId, traceContext }) {
|
|
4805
|
+
// Opens one signing room per message, unless `roomIds` names them, and returns the transaction record.
|
|
4806
|
+
return await this.apiClient.signAndBroadcast({
|
|
4807
|
+
walletId,
|
|
4808
|
+
shareSetId,
|
|
4809
|
+
contexts: contexts.map((context)=>toJsonSafeContext(context)),
|
|
4810
|
+
broadcastOptions,
|
|
4811
|
+
messages,
|
|
4812
|
+
roomIds,
|
|
4813
|
+
isFormatted: false,
|
|
4814
|
+
requestedCertainty,
|
|
4815
|
+
externalReference,
|
|
4816
|
+
idempotencyKey,
|
|
4817
|
+
mfaToken,
|
|
4818
|
+
elevatedAccessToken,
|
|
4819
|
+
approvalQuorum,
|
|
4820
|
+
onError,
|
|
4821
|
+
dynamicRequestId,
|
|
4822
|
+
traceContext
|
|
4823
|
+
});
|
|
4824
|
+
}
|
|
4694
4825
|
prepareMessageForForwardMPC({ chainName, message, formattedMessage, signingAlgo, isFormatted }) {
|
|
4695
4826
|
// BIP340 hash unwrap MUST come first. When `isFormatted=true`, callers wrap
|
|
4696
4827
|
// the payload in a `MessageHash`; the direct-MPC path then reads
|
|
@@ -4779,7 +4910,7 @@ class DynamicWalletClient {
|
|
|
4779
4910
|
throw error;
|
|
4780
4911
|
}
|
|
4781
4912
|
}
|
|
4782
|
-
async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, variant, refreshRoom, onForwardMpcFailure }) {
|
|
4913
|
+
async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, variant, refreshRoom, skipForwardMPC, onForwardMpcFailure }) {
|
|
4783
4914
|
// Reassigned when forward MPC fails and we mint a fresh room for the
|
|
4784
4915
|
// relay-based fallback below.
|
|
4785
4916
|
let roomId = initialRoomId;
|
|
@@ -4807,7 +4938,7 @@ class DynamicWalletClient {
|
|
|
4807
4938
|
isFormatted
|
|
4808
4939
|
}, this.getTraceContext(traceContext)));
|
|
4809
4940
|
// Try forward MPC first if enabled, with fallback to relay-based signing on attestation failure
|
|
4810
|
-
if (this.forwardMPCEnabled && !this.shouldSkipForwardMPC(chainName)) {
|
|
4941
|
+
if (this.forwardMPCEnabled && !skipForwardMPC && !this.shouldSkipForwardMPC(chainName)) {
|
|
4811
4942
|
attemptedForwardMPCSign = true;
|
|
4812
4943
|
const forwardMPCStartedAt = Date.now();
|
|
4813
4944
|
try {
|
|
@@ -5116,7 +5247,7 @@ class DynamicWalletClient {
|
|
|
5116
5247
|
}
|
|
5117
5248
|
//todo: need to modify with imported flag
|
|
5118
5249
|
async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, approvalQuorum, onError, traceContext, bitcoinConfig }) {
|
|
5119
|
-
return
|
|
5250
|
+
return this.queueSign(accountAddress, ({ reportPhase, reportDetails })=>this.internalSign({
|
|
5120
5251
|
accountAddress,
|
|
5121
5252
|
message,
|
|
5122
5253
|
chainName,
|
|
@@ -5135,6 +5266,278 @@ class DynamicWalletClient {
|
|
|
5135
5266
|
}));
|
|
5136
5267
|
}
|
|
5137
5268
|
/**
|
|
5269
|
+
* Signs a transaction for Dynamic to broadcast, one signing ceremony per message. The signature stays on the server.
|
|
5270
|
+
*
|
|
5271
|
+
* @param params.accountAddress - The wallet's address.
|
|
5272
|
+
* @param params.chainName - The wallet's chain, e.g. `EVM`.
|
|
5273
|
+
* @param params.messages - The transaction once per fee attempt, in fee order, as the bytes to sign (RLP hex on EVM, message bytes on Solana). Not hashed.
|
|
5274
|
+
* @param params.contexts - What each message does in readable form (chain, recipient, amount), one per message in the same order. Dynamic checks each against the wallet and its policies.
|
|
5275
|
+
* @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
|
|
5276
|
+
* @param params.password - The wallet's backup password, if it has one.
|
|
5277
|
+
* @param params.signedSessionId - The signed-in session, used to unlock the wallet's key share.
|
|
5278
|
+
* @param params.mfaToken - Proof of the user's multi-factor check, when the wallet needs one.
|
|
5279
|
+
* @param params.elevatedAccessToken - Proof of a recent re-login, when the wallet needs one.
|
|
5280
|
+
* @param params.approvalQuorum - Approvers' consent for a governed wallet, sent on the retry after `intent_required`.
|
|
5281
|
+
* @param params.requestedCertainty - How sure to be before calling it confirmed: `fast`, `safe` or `final`, or `chain:` plus a chain-specific value. Defaults to `safe`.
|
|
5282
|
+
* @param params.externalReference - Your own label, returned on every event. Not unique.
|
|
5283
|
+
* @param params.idempotencyKey - Makes a retry safe: the same key replays the stored answer instead of sending twice. A new one is made when omitted.
|
|
5284
|
+
* @param params.onError - Called with the failure's message, never the raw error.
|
|
5285
|
+
* @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
|
|
5286
|
+
* @returns The transaction record. A finished one comes back without signing again.
|
|
5287
|
+
* @throws Error when `contexts` and `messages` differ in length.
|
|
5288
|
+
*/ async signAndBroadcast({ accountAddress, chainName, contexts, broadcastOptions, messages, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, approvalQuorum, requestedCertainty, externalReference, idempotencyKey = v4(), onError, traceContext }) {
|
|
5289
|
+
// Each fee attempt is one message and one context; a mismatch is the caller's, so it is named here, before any ceremony.
|
|
5290
|
+
if (messages.length === 0 || contexts.length !== messages.length) {
|
|
5291
|
+
throw new Error(`Sign and broadcast needs one signing context per message, got ${contexts.length} contexts for ${messages.length} messages`);
|
|
5292
|
+
}
|
|
5293
|
+
return this.queueSign(accountAddress, ({ reportPhase })=>this.internalSignAndBroadcast({
|
|
5294
|
+
accountAddress,
|
|
5295
|
+
chainName,
|
|
5296
|
+
contexts,
|
|
5297
|
+
broadcastOptions,
|
|
5298
|
+
messages,
|
|
5299
|
+
password,
|
|
5300
|
+
signedSessionId,
|
|
5301
|
+
mfaToken,
|
|
5302
|
+
elevatedAccessToken,
|
|
5303
|
+
approvalQuorum,
|
|
5304
|
+
requestedCertainty,
|
|
5305
|
+
externalReference,
|
|
5306
|
+
idempotencyKey,
|
|
5307
|
+
onError,
|
|
5308
|
+
traceContext,
|
|
5309
|
+
onSignPhase: reportPhase
|
|
5310
|
+
}));
|
|
5311
|
+
}
|
|
5312
|
+
/**
|
|
5313
|
+
* Does the work of `signAndBroadcast` once it has its turn in the wallet queue: loads the wallet, asks Dynamic to open the rooms, then signs in each.
|
|
5314
|
+
*
|
|
5315
|
+
* Takes the same fields as `signAndBroadcast`.
|
|
5316
|
+
*
|
|
5317
|
+
* @param params.onSignPhase - Reports which step it is on, so the queue can tell which step used up the time.
|
|
5318
|
+
*/ async internalSignAndBroadcast({ accountAddress, chainName, contexts, broadcastOptions, messages, password, signedSessionId, mfaToken, elevatedAccessToken, approvalQuorum, requestedCertainty, externalReference, idempotencyKey, onError, traceContext, onSignPhase, staleShareRetry = false, roomIdRetry = false }) {
|
|
5319
|
+
const dynamicRequestId = v4();
|
|
5320
|
+
traceContext = withTraceId(traceContext);
|
|
5321
|
+
let stage = 'prepareWallet';
|
|
5322
|
+
let roomIdSource;
|
|
5323
|
+
this.logger.info('[DynamicWaasWalletClient] Sign and broadcast started', _extends({
|
|
5324
|
+
key: 'signAndBroadcast',
|
|
5325
|
+
operation: 'start',
|
|
5326
|
+
accountAddress,
|
|
5327
|
+
chainName,
|
|
5328
|
+
dynamicRequestId,
|
|
5329
|
+
attemptCount: messages.length,
|
|
5330
|
+
staleShareRetry,
|
|
5331
|
+
roomIdRetry
|
|
5332
|
+
}, this.getTraceContext(traceContext)));
|
|
5333
|
+
try {
|
|
5334
|
+
const { wallet, derivationPath, clientKeyShares, timings: { startedAt, passwordVerifiedAt, walletLoadedAt, sharesReadyAt } } = await this.prepareWalletForSign({
|
|
5335
|
+
accountAddress,
|
|
5336
|
+
password,
|
|
5337
|
+
signedSessionId,
|
|
5338
|
+
onSignPhase
|
|
5339
|
+
});
|
|
5340
|
+
stage = 'resolveCachedRoom';
|
|
5341
|
+
onSignPhase == null ? void 0 : onSignPhase('resolveCachedRoom');
|
|
5342
|
+
const roomIds = roomIdRetry ? undefined : await this.getCachedRoomsForSignAndBroadcast(wallet, accountAddress, chainName, messages.length, dynamicRequestId, traceContext);
|
|
5343
|
+
roomIdSource = roomIds ? 'cache' : 'server';
|
|
5344
|
+
const roomsResolvedAt = Date.now();
|
|
5345
|
+
this.logSlowSignPreflight({
|
|
5346
|
+
accountAddress,
|
|
5347
|
+
chainName,
|
|
5348
|
+
dynamicRequestId,
|
|
5349
|
+
traceContext,
|
|
5350
|
+
verifyPasswordMs: passwordVerifiedAt - startedAt,
|
|
5351
|
+
getWalletMs: walletLoadedAt - passwordVerifiedAt,
|
|
5352
|
+
ensureClientShareMs: sharesReadyAt - walletLoadedAt,
|
|
5353
|
+
resolveCachedRoomMs: roomsResolvedAt - sharesReadyAt,
|
|
5354
|
+
totalMs: roomsResolvedAt - startedAt
|
|
5355
|
+
});
|
|
5356
|
+
stage = 'serverSign';
|
|
5357
|
+
onSignPhase == null ? void 0 : onSignPhase('serverSign');
|
|
5358
|
+
const record = await this.serverSignAndBroadcast({
|
|
5359
|
+
walletId: wallet.walletId,
|
|
5360
|
+
shareSetId: wallet.shareSetId,
|
|
5361
|
+
contexts,
|
|
5362
|
+
broadcastOptions,
|
|
5363
|
+
messages,
|
|
5364
|
+
roomIds,
|
|
5365
|
+
requestedCertainty,
|
|
5366
|
+
externalReference,
|
|
5367
|
+
idempotencyKey,
|
|
5368
|
+
mfaToken,
|
|
5369
|
+
elevatedAccessToken,
|
|
5370
|
+
approvalQuorum,
|
|
5371
|
+
onError,
|
|
5372
|
+
dynamicRequestId,
|
|
5373
|
+
traceContext
|
|
5374
|
+
});
|
|
5375
|
+
var _record_rooms;
|
|
5376
|
+
const rooms = (_record_rooms = record.rooms) != null ? _record_rooms : [];
|
|
5377
|
+
var _roomIds_length;
|
|
5378
|
+
this.logger.info('[DynamicWaasWalletClient] Sign and broadcast accepted', _extends({
|
|
5379
|
+
key: 'signAndBroadcast',
|
|
5380
|
+
operation: 'accepted',
|
|
5381
|
+
accountAddress,
|
|
5382
|
+
chainName,
|
|
5383
|
+
dynamicRequestId,
|
|
5384
|
+
transactionId: record.transactionId,
|
|
5385
|
+
transactionStatus: record.status,
|
|
5386
|
+
attemptCount: messages.length,
|
|
5387
|
+
roomIdSource,
|
|
5388
|
+
roomsSent: (_roomIds_length = roomIds == null ? void 0 : roomIds.length) != null ? _roomIds_length : 0,
|
|
5389
|
+
roomsReturned: rooms.length,
|
|
5390
|
+
// A retry of an existing row names that row's rooms, not the ones just sent.
|
|
5391
|
+
returnedRoomsMatchSent: roomIds ? roomIds.every((roomId, index)=>{
|
|
5392
|
+
var _rooms_index;
|
|
5393
|
+
return ((_rooms_index = rooms[index]) == null ? void 0 : _rooms_index.roomId) === roomId;
|
|
5394
|
+
}) : undefined
|
|
5395
|
+
}, this.getTraceContext(traceContext)));
|
|
5396
|
+
// A retry of a finished request returns the record without rooms.
|
|
5397
|
+
if (record.status !== 'pending_signature') {
|
|
5398
|
+
return record;
|
|
5399
|
+
}
|
|
5400
|
+
if (rooms.length !== messages.length) {
|
|
5401
|
+
throw new Error(`Expected ${messages.length} signing rooms, got ${rooms.length} for transaction ${record.transactionId}`);
|
|
5402
|
+
}
|
|
5403
|
+
stage = 'ceremony';
|
|
5404
|
+
onSignPhase == null ? void 0 : onSignPhase('ceremony');
|
|
5405
|
+
const ceremonies = Promise.all(rooms.map((room, index)=>this.clientSign({
|
|
5406
|
+
chainName,
|
|
5407
|
+
message: messages[index],
|
|
5408
|
+
roomId: room.roomId,
|
|
5409
|
+
keyShare: clientKeyShares[0],
|
|
5410
|
+
derivationPath,
|
|
5411
|
+
isFormatted: false,
|
|
5412
|
+
dynamicRequestId,
|
|
5413
|
+
traceContext,
|
|
5414
|
+
variant: wallet.variant,
|
|
5415
|
+
// Nothing mints a replacement room for a broadcast attempt.
|
|
5416
|
+
skipForwardMPC: true
|
|
5417
|
+
})));
|
|
5418
|
+
const getClientSignMismatch = this.trackClientSignMismatch(ceremonies, {
|
|
5419
|
+
accountAddress,
|
|
5420
|
+
chainName,
|
|
5421
|
+
password,
|
|
5422
|
+
signedSessionId,
|
|
5423
|
+
mfaToken,
|
|
5424
|
+
dynamicRequestId,
|
|
5425
|
+
traceContext
|
|
5426
|
+
});
|
|
5427
|
+
try {
|
|
5428
|
+
await ceremonies;
|
|
5429
|
+
} catch (signError) {
|
|
5430
|
+
stage = 'healAfterFailure';
|
|
5431
|
+
await this.healAfterSignFailure({
|
|
5432
|
+
signError,
|
|
5433
|
+
clientSignMismatch: getClientSignMismatch(),
|
|
5434
|
+
accountAddress,
|
|
5435
|
+
chainName,
|
|
5436
|
+
password,
|
|
5437
|
+
signedSessionId,
|
|
5438
|
+
mfaToken,
|
|
5439
|
+
dynamicRequestId,
|
|
5440
|
+
traceContext
|
|
5441
|
+
});
|
|
5442
|
+
stage = 'ceremony';
|
|
5443
|
+
throw signError;
|
|
5444
|
+
}
|
|
5445
|
+
this.logger.info('[DynamicWaasWalletClient] Client sign completed', _extends({
|
|
5446
|
+
accountAddress,
|
|
5447
|
+
chainName,
|
|
5448
|
+
roomIds: rooms.map((room)=>room.roomId),
|
|
5449
|
+
dynamicRequestId,
|
|
5450
|
+
key: 'signAndBroadcast',
|
|
5451
|
+
operation: 'clientSign'
|
|
5452
|
+
}, this.getTraceContext(traceContext)));
|
|
5453
|
+
return record;
|
|
5454
|
+
} catch (error) {
|
|
5455
|
+
const retryParams = {
|
|
5456
|
+
accountAddress,
|
|
5457
|
+
chainName,
|
|
5458
|
+
contexts,
|
|
5459
|
+
broadcastOptions,
|
|
5460
|
+
messages,
|
|
5461
|
+
password,
|
|
5462
|
+
signedSessionId,
|
|
5463
|
+
mfaToken,
|
|
5464
|
+
elevatedAccessToken,
|
|
5465
|
+
approvalQuorum,
|
|
5466
|
+
requestedCertainty,
|
|
5467
|
+
externalReference,
|
|
5468
|
+
idempotencyKey,
|
|
5469
|
+
onError,
|
|
5470
|
+
traceContext,
|
|
5471
|
+
onSignPhase
|
|
5472
|
+
};
|
|
5473
|
+
// The server rejected the local shares as stale before opening any room. Heal them
|
|
5474
|
+
// from backup and retry once with the same idempotency key, as plain signing does.
|
|
5475
|
+
if (!staleShareRetry && isStaleClientSharesError(error)) {
|
|
5476
|
+
await this.healStaleShares({
|
|
5477
|
+
accountAddress,
|
|
5478
|
+
chainName,
|
|
5479
|
+
password,
|
|
5480
|
+
signedSessionId,
|
|
5481
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
5482
|
+
mfaToken,
|
|
5483
|
+
dynamicRequestId,
|
|
5484
|
+
traceContext,
|
|
5485
|
+
trigger: 'server 409 stale-shares preflight (sign and broadcast)',
|
|
5486
|
+
confirmedMismatch: true
|
|
5487
|
+
});
|
|
5488
|
+
this.logger.info('[StaleShareCheck] retrying sign and broadcast after 409 stale-shares preflight', _extends({
|
|
5489
|
+
key: 'signAndBroadcast',
|
|
5490
|
+
operation: 'staleShareRetry',
|
|
5491
|
+
accountAddress,
|
|
5492
|
+
chainName,
|
|
5493
|
+
dynamicRequestId,
|
|
5494
|
+
stage
|
|
5495
|
+
}, this.getTraceContext(traceContext)));
|
|
5496
|
+
return await this.internalSignAndBroadcast(_extends({}, retryParams, {
|
|
5497
|
+
staleShareRetry: true,
|
|
5498
|
+
roomIdRetry
|
|
5499
|
+
}));
|
|
5500
|
+
}
|
|
5501
|
+
// A cached room was already claimed by an earlier ceremony. Retry once with the same
|
|
5502
|
+
// idempotency key and no cached rooms, so the server opens fresh ones.
|
|
5503
|
+
if (!roomIdRetry && isRoomIdAlreadyUsedError(error)) {
|
|
5504
|
+
this.logger.info('[RoomCache] retrying sign and broadcast without cached rooms after 409 ROOM_ID_ALREADY_USED', _extends({
|
|
5505
|
+
key: 'signAndBroadcast',
|
|
5506
|
+
operation: 'roomIdRetry',
|
|
5507
|
+
accountAddress,
|
|
5508
|
+
chainName,
|
|
5509
|
+
dynamicRequestId,
|
|
5510
|
+
stage
|
|
5511
|
+
}, this.getTraceContext(traceContext)));
|
|
5512
|
+
return await this.internalSignAndBroadcast(_extends({}, retryParams, {
|
|
5513
|
+
staleShareRetry,
|
|
5514
|
+
roomIdRetry: true
|
|
5515
|
+
}));
|
|
5516
|
+
}
|
|
5517
|
+
this.reportSignError({
|
|
5518
|
+
error,
|
|
5519
|
+
accountAddress,
|
|
5520
|
+
chainName,
|
|
5521
|
+
isFormatted: false,
|
|
5522
|
+
dynamicRequestId,
|
|
5523
|
+
traceContext,
|
|
5524
|
+
operation: 'signAndBroadcast',
|
|
5525
|
+
details: {
|
|
5526
|
+
stage,
|
|
5527
|
+
attemptCount: messages.length,
|
|
5528
|
+
roomIdSource,
|
|
5529
|
+
staleShareRetry,
|
|
5530
|
+
roomIdRetry
|
|
5531
|
+
}
|
|
5532
|
+
});
|
|
5533
|
+
attachRequestIds(error, {
|
|
5534
|
+
traceId: traceContext.traceId,
|
|
5535
|
+
requestId: dynamicRequestId
|
|
5536
|
+
});
|
|
5537
|
+
throw error;
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5540
|
+
/**
|
|
5138
5541
|
* Reports a sign that spent a long time before reaching the network. Emitted
|
|
5139
5542
|
* once per slow sign with the phase breakdown, so a stall can be attributed
|
|
5140
5543
|
* without guessing: a shared dependency shows up as many signs reporting a
|
|
@@ -5158,11 +5561,83 @@ class DynamicWalletClient {
|
|
|
5158
5561
|
dynamicRequestId
|
|
5159
5562
|
}, this.getTraceContext(traceContext)));
|
|
5160
5563
|
}
|
|
5161
|
-
|
|
5564
|
+
/**
|
|
5565
|
+
* Takes one already-opened room per message from the local pool, or none: a partial set saves nothing, so the server opens them all instead.
|
|
5566
|
+
*
|
|
5567
|
+
* @param wallet - The wallet signing; its key scheme decides which rooms fit.
|
|
5568
|
+
* @param accountAddress - The wallet's address, for logs.
|
|
5569
|
+
* @param chainName - The wallet's chain, for logs.
|
|
5570
|
+
* @param count - How many rooms are needed: one per message.
|
|
5571
|
+
* @param dynamicRequestId - Id of this request, so the pool's logs join the sign's.
|
|
5572
|
+
* @param traceContext - Trace ids for the same reason.
|
|
5573
|
+
* @returns The room ids, or `undefined` when the pool cannot cover every message.
|
|
5574
|
+
*/ async getCachedRoomsForSignAndBroadcast(wallet, accountAddress, chainName, count, dynamicRequestId, traceContext) {
|
|
5575
|
+
const logContext = _extends({
|
|
5576
|
+
dynamicRequestId
|
|
5577
|
+
}, this.getTraceContext(traceContext));
|
|
5162
5578
|
if (this.featureFlags[FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] !== true) {
|
|
5579
|
+
this.logCachedRoomsUnavailable({
|
|
5580
|
+
reason: 'cache_disabled',
|
|
5581
|
+
accountAddress,
|
|
5582
|
+
chainName,
|
|
5583
|
+
count,
|
|
5584
|
+
logContext
|
|
5585
|
+
});
|
|
5163
5586
|
return undefined;
|
|
5164
5587
|
}
|
|
5165
|
-
const
|
|
5588
|
+
const rooms = await this.takeRooms({
|
|
5589
|
+
roomType: RoomTypeEnum.Threshold,
|
|
5590
|
+
thresholdSignatureScheme: wallet.thresholdSignatureScheme,
|
|
5591
|
+
count,
|
|
5592
|
+
logContext
|
|
5593
|
+
});
|
|
5594
|
+
if (rooms.length === 0) {
|
|
5595
|
+
this.logCachedRoomsUnavailable({
|
|
5596
|
+
reason: 'pool_too_small',
|
|
5597
|
+
accountAddress,
|
|
5598
|
+
chainName,
|
|
5599
|
+
count,
|
|
5600
|
+
logContext
|
|
5601
|
+
});
|
|
5602
|
+
return undefined;
|
|
5603
|
+
}
|
|
5604
|
+
const roomIds = rooms.map((room)=>room.roomId);
|
|
5605
|
+
this.logger.info('[DynamicWaasWalletClient] Using cached rooms', _extends({
|
|
5606
|
+
key: 'signAndBroadcast',
|
|
5607
|
+
operation: 'cachedRoomsFound',
|
|
5608
|
+
accountAddress,
|
|
5609
|
+
chainName,
|
|
5610
|
+
walletId: wallet.walletId,
|
|
5611
|
+
roomIds
|
|
5612
|
+
}, logContext));
|
|
5613
|
+
return roomIds;
|
|
5614
|
+
}
|
|
5615
|
+
/**
|
|
5616
|
+
* Logs why no cached rooms were sent, so how often the cache covers a request can be measured.
|
|
5617
|
+
*
|
|
5618
|
+
* @param params.reason - `cache_disabled` or `pool_too_small`.
|
|
5619
|
+
* @param params.accountAddress - The wallet's address.
|
|
5620
|
+
* @param params.chainName - The wallet's chain.
|
|
5621
|
+
* @param params.count - How many rooms were needed.
|
|
5622
|
+
* @param params.logContext - Request and trace ids to attach.
|
|
5623
|
+
*/ logCachedRoomsUnavailable({ reason, accountAddress, chainName, count, logContext }) {
|
|
5624
|
+
this.logger.info('[DynamicWaasWalletClient] Cached rooms unavailable; the server opens them', _extends({
|
|
5625
|
+
key: 'signAndBroadcast',
|
|
5626
|
+
operation: 'cachedRoomsUnavailable',
|
|
5627
|
+
reason,
|
|
5628
|
+
accountAddress,
|
|
5629
|
+
chainName,
|
|
5630
|
+
requestedCount: count
|
|
5631
|
+
}, logContext));
|
|
5632
|
+
}
|
|
5633
|
+
async getCachedRoomForSign(wallet, accountAddress, chainName, dynamicRequestId, traceContext) {
|
|
5634
|
+
if (this.featureFlags[FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] !== true) {
|
|
5635
|
+
return undefined;
|
|
5636
|
+
}
|
|
5637
|
+
const logContext = _extends({
|
|
5638
|
+
dynamicRequestId
|
|
5639
|
+
}, this.getTraceContext(traceContext));
|
|
5640
|
+
const room = await this.getRoom(RoomTypeEnum.Threshold, wallet.thresholdSignatureScheme, logContext);
|
|
5166
5641
|
const roomId = room == null ? void 0 : room.roomId;
|
|
5167
5642
|
if (roomId) {
|
|
5168
5643
|
this.logger.info('[DynamicWaasWalletClient] Using cached room', _extends({
|
|
@@ -5172,7 +5647,7 @@ class DynamicWalletClient {
|
|
|
5172
5647
|
chainName,
|
|
5173
5648
|
walletId: wallet.walletId,
|
|
5174
5649
|
roomId
|
|
5175
|
-
},
|
|
5650
|
+
}, logContext));
|
|
5176
5651
|
}
|
|
5177
5652
|
return roomId;
|
|
5178
5653
|
}
|
|
@@ -5239,38 +5714,130 @@ class DynamicWalletClient {
|
|
|
5239
5714
|
clearTimeout(warningTimer);
|
|
5240
5715
|
}
|
|
5241
5716
|
}
|
|
5717
|
+
/** Verifies the password, loads the wallet and makes sure its client share exists: the start of every sign. */ async prepareWalletForSign({ accountAddress, password, signedSessionId, onSignPhase }) {
|
|
5718
|
+
const startedAt = Date.now();
|
|
5719
|
+
onSignPhase == null ? void 0 : onSignPhase('verifyPassword');
|
|
5720
|
+
await this.verifyPassword({
|
|
5721
|
+
accountAddress,
|
|
5722
|
+
password,
|
|
5723
|
+
signedSessionId
|
|
5724
|
+
});
|
|
5725
|
+
const passwordVerifiedAt = Date.now();
|
|
5726
|
+
onSignPhase == null ? void 0 : onSignPhase('getWallet');
|
|
5727
|
+
const wallet = await this.getWallet({
|
|
5728
|
+
accountAddress,
|
|
5729
|
+
password,
|
|
5730
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
5731
|
+
signedSessionId
|
|
5732
|
+
});
|
|
5733
|
+
const walletLoadedAt = Date.now();
|
|
5734
|
+
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
5735
|
+
onSignPhase == null ? void 0 : onSignPhase('ensureClientShare');
|
|
5736
|
+
const clientKeyShares = await this.ensureClientShare(accountAddress);
|
|
5737
|
+
const sharesReadyAt = Date.now();
|
|
5738
|
+
return {
|
|
5739
|
+
wallet,
|
|
5740
|
+
derivationPath,
|
|
5741
|
+
clientKeyShares,
|
|
5742
|
+
timings: {
|
|
5743
|
+
startedAt,
|
|
5744
|
+
passwordVerifiedAt,
|
|
5745
|
+
walletLoadedAt,
|
|
5746
|
+
sharesReadyAt
|
|
5747
|
+
}
|
|
5748
|
+
};
|
|
5749
|
+
}
|
|
5750
|
+
/**
|
|
5751
|
+
* Watches a ceremony for a client-side ERROR_PUBLIC_KEY_MISMATCH, the only place a stale
|
|
5752
|
+
* local share surfaces when a generic server error wins the race and masks it. Schedules a
|
|
5753
|
+
* heal as soon as it settles and returns a reader for the mismatch.
|
|
5754
|
+
*/ trackClientSignMismatch(clientSignPromise, healContext) {
|
|
5755
|
+
let mismatch;
|
|
5756
|
+
clientSignPromise.catch((error)=>{
|
|
5757
|
+
if (isPublicKeyMismatchError(error)) {
|
|
5758
|
+
mismatch = error;
|
|
5759
|
+
this.logger.warn('[StaleShareCheck] clientSign rejected with public-key mismatch — scheduling share recovery', _extends({
|
|
5760
|
+
accountAddress: healContext.accountAddress,
|
|
5761
|
+
chainName: healContext.chainName,
|
|
5762
|
+
dynamicRequestId: healContext.dynamicRequestId
|
|
5763
|
+
}, this.getTraceContext(healContext.traceContext)));
|
|
5764
|
+
void this.healStaleShares(_extends({}, healContext, {
|
|
5765
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
5766
|
+
trigger: 'clientSign rejection',
|
|
5767
|
+
confirmedMismatch: true
|
|
5768
|
+
}));
|
|
5769
|
+
}
|
|
5770
|
+
});
|
|
5771
|
+
return ()=>mismatch;
|
|
5772
|
+
}
|
|
5773
|
+
/**
|
|
5774
|
+
* Heals stale shares after a failed ceremony so the caller's retry succeeds. The ground-truth
|
|
5775
|
+
* check runs whatever the raced error was, because a generic server error often wins the race
|
|
5776
|
+
* while the client-side mismatch settles later. It no-ops when shares are fresh.
|
|
5777
|
+
*/ async healAfterSignFailure(_param) {
|
|
5778
|
+
var { signError, clientSignMismatch } = _param, healContext = _object_without_properties_loose(_param, [
|
|
5779
|
+
"signError",
|
|
5780
|
+
"clientSignMismatch"
|
|
5781
|
+
]);
|
|
5782
|
+
await this.healStaleShares(_extends({}, healContext, {
|
|
5783
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
5784
|
+
trigger: 'sign failure',
|
|
5785
|
+
confirmedMismatch: isPublicKeyMismatchError(signError) || clientSignMismatch !== undefined
|
|
5786
|
+
}));
|
|
5787
|
+
}
|
|
5788
|
+
/**
|
|
5789
|
+
* Logs a failed sign or sign-and-broadcast with the same fields on both, so failures can be filtered by operation, HTTP status and backend code.
|
|
5790
|
+
*
|
|
5791
|
+
* @param params.error - What failed.
|
|
5792
|
+
* @param params.accountAddress - The wallet's address.
|
|
5793
|
+
* @param params.chainName - The wallet's chain.
|
|
5794
|
+
* @param params.isFormatted - Whether the message was a hash.
|
|
5795
|
+
* @param params.dynamicRequestId - Id of the failed attempt.
|
|
5796
|
+
* @param params.traceContext - Ids shared by every attempt of this operation.
|
|
5797
|
+
* @param params.operation - Which call failed. Defaults to `sign`.
|
|
5798
|
+
* @param params.details - Where and how it failed, as short values (never request or response bodies).
|
|
5799
|
+
*/ reportSignError({ error, accountAddress, chainName, isFormatted, dynamicRequestId, traceContext, operation = 'sign', details }) {
|
|
5800
|
+
logError({
|
|
5801
|
+
message: `Error in ${operation}`,
|
|
5802
|
+
error: error,
|
|
5803
|
+
context: _extends({
|
|
5804
|
+
accountAddress,
|
|
5805
|
+
chainName,
|
|
5806
|
+
isFormatted: isFormatted ? 'true' : 'false',
|
|
5807
|
+
dynamicRequestId,
|
|
5808
|
+
operation
|
|
5809
|
+
}, describeErrorForLog(error), details, this.getTraceContext(traceContext))
|
|
5810
|
+
});
|
|
5811
|
+
}
|
|
5242
5812
|
async internalSign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, approvalQuorum, onError, traceContext, bitcoinConfig, onSignPhase, onSignDetails, staleShareRetry = false, roomIdRetry = false }) {
|
|
5243
5813
|
const dynamicRequestId = v4();
|
|
5244
5814
|
onSignDetails == null ? void 0 : onSignDetails({
|
|
5245
5815
|
dynamicRequestId
|
|
5246
5816
|
});
|
|
5817
|
+
// Retries below reuse this, so every attempt of one sign shares a trace id.
|
|
5818
|
+
traceContext = withTraceId(traceContext);
|
|
5819
|
+
this.logger.info('[DynamicWaasWalletClient] Sign started', _extends({
|
|
5820
|
+
key: 'sign',
|
|
5821
|
+
operation: 'start',
|
|
5822
|
+
accountAddress,
|
|
5823
|
+
chainName,
|
|
5824
|
+
isFormatted,
|
|
5825
|
+
dynamicRequestId,
|
|
5826
|
+
staleShareRetry,
|
|
5827
|
+
roomIdRetry
|
|
5828
|
+
}, this.getTraceContext(traceContext)));
|
|
5247
5829
|
try {
|
|
5248
5830
|
// Pre-flight phase boundaries. Nothing reaches the network until
|
|
5249
5831
|
// serverSign below, so a stall here is invisible in server logs — these
|
|
5250
5832
|
// timestamps are what attribute it to a phase.
|
|
5251
|
-
const preflightStartedAt =
|
|
5252
|
-
onSignPhase == null ? void 0 : onSignPhase('verifyPassword');
|
|
5253
|
-
await this.verifyPassword({
|
|
5254
|
-
accountAddress,
|
|
5255
|
-
password,
|
|
5256
|
-
signedSessionId
|
|
5257
|
-
});
|
|
5258
|
-
const passwordVerifiedAt = Date.now();
|
|
5259
|
-
onSignPhase == null ? void 0 : onSignPhase('getWallet');
|
|
5260
|
-
const wallet = await this.getWallet({
|
|
5833
|
+
const { wallet, derivationPath, clientKeyShares, timings: { startedAt: preflightStartedAt, passwordVerifiedAt, walletLoadedAt, sharesReadyAt } } = await this.prepareWalletForSign({
|
|
5261
5834
|
accountAddress,
|
|
5262
5835
|
password,
|
|
5263
|
-
|
|
5264
|
-
|
|
5836
|
+
signedSessionId,
|
|
5837
|
+
onSignPhase
|
|
5265
5838
|
});
|
|
5266
|
-
const walletLoadedAt = Date.now();
|
|
5267
|
-
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
5268
|
-
// Ensure client key shares exist before hitting the API
|
|
5269
|
-
onSignPhase == null ? void 0 : onSignPhase('ensureClientShare');
|
|
5270
|
-
const clientKeyShares = await this.ensureClientShare(accountAddress);
|
|
5271
|
-
const sharesReadyAt = Date.now();
|
|
5272
5839
|
onSignPhase == null ? void 0 : onSignPhase('resolveCachedRoom');
|
|
5273
|
-
const cachedRoomId = roomIdRetry ? undefined : await this.getCachedRoomForSign(wallet, accountAddress, chainName, traceContext);
|
|
5840
|
+
const cachedRoomId = roomIdRetry ? undefined : await this.getCachedRoomForSign(wallet, accountAddress, chainName, dynamicRequestId, traceContext);
|
|
5274
5841
|
const roomResolvedAt = Date.now();
|
|
5275
5842
|
this.logSlowSignPreflight({
|
|
5276
5843
|
accountAddress,
|
|
@@ -5356,9 +5923,7 @@ class DynamicWalletClient {
|
|
|
5356
5923
|
operation: 'serverSign'
|
|
5357
5924
|
}, this.getTraceContext(traceContext)));
|
|
5358
5925
|
let signature;
|
|
5359
|
-
|
|
5360
|
-
// detect a client-side mismatch masked by a generic SSE race winner.
|
|
5361
|
-
let clientSignMismatchError;
|
|
5926
|
+
let getClientSignMismatch = ()=>undefined;
|
|
5362
5927
|
try {
|
|
5363
5928
|
// Race clientSign against SSE errors so that server-side failures
|
|
5364
5929
|
// (e.g. policy violations) immediately reject instead of hanging
|
|
@@ -5421,28 +5986,15 @@ class DynamicWalletClient {
|
|
|
5421
5986
|
// a stale-local-share failure surfaces, since the server's generic SSE
|
|
5422
5987
|
// error wins the race and masks it. Schedule recovery regardless of
|
|
5423
5988
|
// which promise wins or when this rejection settles.
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
accountAddress,
|
|
5434
|
-
chainName,
|
|
5435
|
-
password,
|
|
5436
|
-
signedSessionId,
|
|
5437
|
-
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
5438
|
-
mfaToken,
|
|
5439
|
-
dynamicRequestId,
|
|
5440
|
-
traceContext,
|
|
5441
|
-
bitcoinConfig,
|
|
5442
|
-
trigger: 'clientSign rejection',
|
|
5443
|
-
confirmedMismatch: true
|
|
5444
|
-
});
|
|
5445
|
-
}
|
|
5989
|
+
getClientSignMismatch = this.trackClientSignMismatch(clientSignPromise, {
|
|
5990
|
+
accountAddress,
|
|
5991
|
+
chainName,
|
|
5992
|
+
password,
|
|
5993
|
+
signedSessionId,
|
|
5994
|
+
mfaToken,
|
|
5995
|
+
dynamicRequestId,
|
|
5996
|
+
traceContext,
|
|
5997
|
+
bitcoinConfig
|
|
5446
5998
|
});
|
|
5447
5999
|
signature = await Promise.race([
|
|
5448
6000
|
clientSignPromise,
|
|
@@ -5456,23 +6008,17 @@ class DynamicWalletClient {
|
|
|
5456
6008
|
if (isStaleClientSharesError(signError) || isRoomIdAlreadyUsedError(signError)) {
|
|
5457
6009
|
throw signError;
|
|
5458
6010
|
}
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
// settles later, so error-string inspection alone is timing-dependent.
|
|
5463
|
-
// The check no-ops when shares are fresh.
|
|
5464
|
-
await this.healStaleShares({
|
|
6011
|
+
await this.healAfterSignFailure({
|
|
6012
|
+
signError,
|
|
6013
|
+
clientSignMismatch: getClientSignMismatch(),
|
|
5465
6014
|
accountAddress,
|
|
5466
6015
|
chainName,
|
|
5467
6016
|
password,
|
|
5468
6017
|
signedSessionId,
|
|
5469
|
-
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
5470
6018
|
mfaToken,
|
|
5471
6019
|
dynamicRequestId,
|
|
5472
6020
|
traceContext,
|
|
5473
|
-
bitcoinConfig
|
|
5474
|
-
trigger: 'sign failure',
|
|
5475
|
-
confirmedMismatch: isPublicKeyMismatchError(signError) || clientSignMismatchError !== undefined
|
|
6021
|
+
bitcoinConfig
|
|
5476
6022
|
});
|
|
5477
6023
|
throw signError;
|
|
5478
6024
|
}
|
|
@@ -5483,6 +6029,7 @@ class DynamicWalletClient {
|
|
|
5483
6029
|
roomId,
|
|
5484
6030
|
derivationPath,
|
|
5485
6031
|
isFormatted,
|
|
6032
|
+
dynamicRequestId,
|
|
5486
6033
|
key: 'sign',
|
|
5487
6034
|
operation: 'clientSign'
|
|
5488
6035
|
}, this.getTraceContext(traceContext)));
|
|
@@ -5566,15 +6113,21 @@ class DynamicWalletClient {
|
|
|
5566
6113
|
roomIdRetry: true
|
|
5567
6114
|
});
|
|
5568
6115
|
}
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
6116
|
+
this.reportSignError({
|
|
6117
|
+
error,
|
|
6118
|
+
accountAddress,
|
|
6119
|
+
chainName,
|
|
6120
|
+
isFormatted,
|
|
6121
|
+
dynamicRequestId,
|
|
6122
|
+
traceContext,
|
|
6123
|
+
details: {
|
|
6124
|
+
staleShareRetry,
|
|
6125
|
+
roomIdRetry
|
|
6126
|
+
}
|
|
6127
|
+
});
|
|
6128
|
+
attachRequestIds(error, {
|
|
6129
|
+
traceId: traceContext == null ? void 0 : traceContext.traceId,
|
|
6130
|
+
requestId: dynamicRequestId
|
|
5578
6131
|
});
|
|
5579
6132
|
throw error;
|
|
5580
6133
|
}
|
|
@@ -7139,7 +7692,42 @@ class DynamicWalletClient {
|
|
|
7139
7692
|
}
|
|
7140
7693
|
return new BIP340KeygenResult(extractedPubkey, secretShare);
|
|
7141
7694
|
}
|
|
7142
|
-
|
|
7695
|
+
/**
|
|
7696
|
+
* Builds the export Intent for a business-account wallet, to be signed with
|
|
7697
|
+
* the session key and sent as `approvalQuorum` on the export call.
|
|
7698
|
+
*
|
|
7699
|
+
* The `exportId` is the key share's keygen id, so it comes out the same here
|
|
7700
|
+
* and on the export call; the Intent built here binds it. Makes no API call
|
|
7701
|
+
* and starts no ceremony.
|
|
7702
|
+
*/ async prepareExportIntent({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, businessAccountId, initiatorUserId, completeBy }) {
|
|
7703
|
+
const wallet = await this.getWallet({
|
|
7704
|
+
accountAddress,
|
|
7705
|
+
password,
|
|
7706
|
+
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY,
|
|
7707
|
+
signedSessionId
|
|
7708
|
+
});
|
|
7709
|
+
const mpcSigner = getMPCSigner({
|
|
7710
|
+
chainName,
|
|
7711
|
+
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
7712
|
+
bitcoinConfig,
|
|
7713
|
+
variant: wallet.variant
|
|
7714
|
+
});
|
|
7715
|
+
const reconstructedKeyShare = await this.getReconstructedKeyShare(accountAddress, mpcSigner);
|
|
7716
|
+
const exportId = await this.getExportId({
|
|
7717
|
+
chainName,
|
|
7718
|
+
clientKeyShare: reconstructedKeyShare,
|
|
7719
|
+
bitcoinConfig,
|
|
7720
|
+
accountAddress: wallet.accountAddress
|
|
7721
|
+
});
|
|
7722
|
+
return createExportIntent({
|
|
7723
|
+
accountAddress,
|
|
7724
|
+
businessAccountId,
|
|
7725
|
+
completeBy,
|
|
7726
|
+
exportId,
|
|
7727
|
+
initiatorUserId
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
async exportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, approvalQuorum, proposalId, traceContext }) {
|
|
7143
7731
|
// Public entry point delegates to the private implementation. Keeping the
|
|
7144
7732
|
// `staleShareRetry` flag off the public signature mirrors sign/refresh/
|
|
7145
7733
|
// reshare and ensures the transparent 409 retry can't be suppressed by
|
|
@@ -7152,10 +7740,12 @@ class DynamicWalletClient {
|
|
|
7152
7740
|
signedSessionId,
|
|
7153
7741
|
mfaToken,
|
|
7154
7742
|
elevatedAccessToken,
|
|
7743
|
+
approvalQuorum,
|
|
7744
|
+
proposalId,
|
|
7155
7745
|
traceContext
|
|
7156
7746
|
});
|
|
7157
7747
|
}
|
|
7158
|
-
async internalExportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext, staleShareRetry = false }) {
|
|
7748
|
+
async internalExportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, approvalQuorum, proposalId, traceContext, staleShareRetry = false }) {
|
|
7159
7749
|
const dynamicRequestId = v4();
|
|
7160
7750
|
try {
|
|
7161
7751
|
const wallet = await this.getWallet({
|
|
@@ -7182,7 +7772,10 @@ class DynamicWalletClient {
|
|
|
7182
7772
|
// transport has no such stream, so a post-init denial surfaces as an
|
|
7183
7773
|
// MPC relay timeout there.
|
|
7184
7774
|
const sseErrorGate = createRejectablePromise();
|
|
7185
|
-
|
|
7775
|
+
// An export that carries approvals stays on SSE: the server answers the
|
|
7776
|
+
// approval round (`consent_required`) on that stream, and the HTTP route
|
|
7777
|
+
// refuses `approvalQuorum` / `proposalId`.
|
|
7778
|
+
const useHttpTransport = this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT_EXPORT] === true && !approvalQuorum && !proposalId;
|
|
7186
7779
|
// Fresh key per invocation: the transparent stale-share retry below is a
|
|
7187
7780
|
// NEW ceremony (new shareSetId), so it must not reuse the failed
|
|
7188
7781
|
// attempt's key — the dedup would replay that row's terminal error.
|
|
@@ -7200,6 +7793,8 @@ class DynamicWalletClient {
|
|
|
7200
7793
|
const data = useHttpTransport ? await this.apiClient.requestExportKeyJson(_extends({}, exportParams, {
|
|
7201
7794
|
idempotencyKey: idempotencyKey
|
|
7202
7795
|
})) : await this.apiClient.exportKey(_extends({}, exportParams, {
|
|
7796
|
+
approvalQuorum,
|
|
7797
|
+
proposalId,
|
|
7203
7798
|
onError: (error)=>sseErrorGate.reject(error)
|
|
7204
7799
|
}));
|
|
7205
7800
|
this.logger.debug('[DynamicWaasWalletClient] Starting export of private key', _extends({
|
|
@@ -7268,6 +7863,8 @@ class DynamicWalletClient {
|
|
|
7268
7863
|
signedSessionId,
|
|
7269
7864
|
mfaToken,
|
|
7270
7865
|
elevatedAccessToken,
|
|
7866
|
+
approvalQuorum,
|
|
7867
|
+
proposalId,
|
|
7271
7868
|
traceContext,
|
|
7272
7869
|
staleShareRetry: true
|
|
7273
7870
|
});
|
|
@@ -10678,8 +11275,25 @@ class DynamicWalletClient {
|
|
|
10678
11275
|
const MPC_SCHEME_CONFIG = MPC_CONFIG[thresholdSignatureScheme];
|
|
10679
11276
|
return roomType === RoomTypeEnum.Threshold ? MPC_SCHEME_CONFIG.threshold : MPC_SCHEME_CONFIG.numberOfParties;
|
|
10680
11277
|
}
|
|
10681
|
-
async getRoom(roomType, thresholdSignatureScheme) {
|
|
10682
|
-
|
|
11278
|
+
async getRoom(roomType, thresholdSignatureScheme, logContext) {
|
|
11279
|
+
const [room] = await this.takeRooms({
|
|
11280
|
+
roomType,
|
|
11281
|
+
thresholdSignatureScheme,
|
|
11282
|
+
count: 1,
|
|
11283
|
+
logContext
|
|
11284
|
+
});
|
|
11285
|
+
return room;
|
|
11286
|
+
}
|
|
11287
|
+
/**
|
|
11288
|
+
* Takes `count` cached rooms, or none when the pool holds fewer: a caller that needs every room gains nothing from some of them, and would spend them.
|
|
11289
|
+
*
|
|
11290
|
+
* @param params.roomType - Which kind of room.
|
|
11291
|
+
* @param params.thresholdSignatureScheme - The key scheme; it decides how many parties the room needs.
|
|
11292
|
+
* @param params.count - How many rooms to take.
|
|
11293
|
+
* @param params.logContext - Request and trace ids, so the pool's log joins the operation's.
|
|
11294
|
+
* @returns The rooms taken, or an empty list.
|
|
11295
|
+
*/ async takeRooms({ roomType, thresholdSignatureScheme, count, logContext }) {
|
|
11296
|
+
var _rooms_, _DynamicWalletClient_rooms_numberOfParties;
|
|
10683
11297
|
const numberOfParties = this.getNumberOfParties(roomType, thresholdSignatureScheme);
|
|
10684
11298
|
const consume = async ()=>{
|
|
10685
11299
|
// Re-read from localStorage rather than the in-memory static so we see rooms
|
|
@@ -10692,29 +11306,34 @@ class DynamicWalletClient {
|
|
|
10692
11306
|
const schemeRooms = storedRooms[numberOfParties] || [];
|
|
10693
11307
|
// Filter out expired rooms and remove the next available room.
|
|
10694
11308
|
const filteredRooms = schemeRooms.filter((room)=>room.expiresAt > now);
|
|
10695
|
-
const
|
|
11309
|
+
const taken = filteredRooms.length >= count ? filteredRooms.splice(0, count) : [];
|
|
10696
11310
|
// Use setRooms() rather than replacing DynamicWalletClient.rooms wholesale.
|
|
10697
11311
|
// setRooms() only mutates the specific numberOfParties key, so rooms for other
|
|
10698
11312
|
// party counts and any rooms written in-memory by a concurrent createRooms()
|
|
10699
11313
|
// call that hasn't flushed to storage yet are preserved. Persistence is also
|
|
10700
11314
|
// routed through the existing persistRooms() chain to keep writes serialized.
|
|
10701
11315
|
await this.setRooms(numberOfParties, filteredRooms);
|
|
10702
|
-
return
|
|
11316
|
+
return taken;
|
|
10703
11317
|
};
|
|
10704
11318
|
// Use the Web Locks API for cross-tab mutual exclusion so that two tabs
|
|
10705
11319
|
// cannot both consume the same cached room. Falls back to a direct call in
|
|
10706
11320
|
// environments where navigator.locks is unavailable (e.g. Node.js tests).
|
|
10707
11321
|
const webLocksAvailable = typeof navigator !== 'undefined' && !!navigator.locks;
|
|
10708
|
-
const
|
|
11322
|
+
const rooms = webLocksAvailable ? await navigator.locks.request(`${this.storageKey}-room-cache`, consume) : await consume();
|
|
10709
11323
|
var _DynamicWalletClient_rooms_numberOfParties_length;
|
|
10710
|
-
this.logger.info('[DynamicWaasWalletClient] getRoom result', {
|
|
11324
|
+
this.logger.info('[DynamicWaasWalletClient] getRoom result', _extends({
|
|
10711
11325
|
key: 'roomCache',
|
|
10712
11326
|
operation: 'getRoom',
|
|
10713
11327
|
numberOfParties,
|
|
10714
|
-
roomId:
|
|
11328
|
+
roomId: (_rooms_ = rooms[0]) == null ? void 0 : _rooms_.roomId,
|
|
11329
|
+
requestedCount: count,
|
|
11330
|
+
takenCount: rooms.length
|
|
11331
|
+
}, count > 1 ? {
|
|
11332
|
+
roomIds: rooms.map((room)=>room.roomId)
|
|
11333
|
+
} : {}, logContext, {
|
|
10715
11334
|
remainingInMemory: (_DynamicWalletClient_rooms_numberOfParties_length = (_DynamicWalletClient_rooms_numberOfParties = DynamicWalletClient.rooms[numberOfParties]) == null ? void 0 : _DynamicWalletClient_rooms_numberOfParties.length) != null ? _DynamicWalletClient_rooms_numberOfParties_length : 0,
|
|
10716
11335
|
webLocksAvailable
|
|
10717
|
-
});
|
|
11336
|
+
}));
|
|
10718
11337
|
// If less than or only 1 room left after removing one, trigger async creation for more rooms.
|
|
10719
11338
|
// The catch prevents an unhandled promise rejection when the API
|
|
10720
11339
|
// rate-limits this best-effort background call — createRooms already
|
|
@@ -10727,7 +11346,7 @@ class DynamicWalletClient {
|
|
|
10727
11346
|
roomCount: ROOM_CACHE_COUNT
|
|
10728
11347
|
}).catch(()=>{});
|
|
10729
11348
|
}
|
|
10730
|
-
return
|
|
11349
|
+
return rooms;
|
|
10731
11350
|
}
|
|
10732
11351
|
getTraceContext(traceContext) {
|
|
10733
11352
|
const now = Date.now();
|
|
@@ -10832,6 +11451,7 @@ class DynamicWalletClient {
|
|
|
10832
11451
|
this.startLoggerContext(authToken);
|
|
10833
11452
|
}
|
|
10834
11453
|
this.forwardMPCEnabled = this.featureFlags && this.featureFlags[FEATURE_FLAGS.ENABLE_FORWARD_MPC_CLIENT_FLAG] === true;
|
|
11454
|
+
this.enclaveSnapshot = this.forwardMPCEnabled ? trackForwardMpcEnclave(this.apiClient.forwardMPCClient) : undefined;
|
|
10835
11455
|
if (this.forwardMPCEnabled) {
|
|
10836
11456
|
this.connectForwardMPC();
|
|
10837
11457
|
}
|
|
@@ -10849,4 +11469,4 @@ DynamicWalletClient.roomsPersistDirty = false;
|
|
|
10849
11469
|
// rooms back into the new user's storage (TOCTOU on the create→merge gap).
|
|
10850
11470
|
DynamicWalletClient.roomsGeneration = 0;
|
|
10851
11471
|
|
|
10852
|
-
export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_TARGET_SIGNER_PASSWORD_REQUIRED, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, EncryptedBackupUnavailableError, ExpiredGoogleDriveTokenError, GoogleDriveBackupBlockedError, HEAVY_QUEUE_OPERATIONS, OfflineRecoveryCloudBackupConflictError, OperationSource, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SERVER_ROOM_ID_WAIT_WARNING_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, buildErrorContext, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, createOfflineRecoveryDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getEd25519PublicKeyFromRawScalar, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, getOfflineRecoveryShareSet, getShareSet, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isMultipleWalletsPerChainRejection, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, normalizeRawEd25519ScalarHex, readEnvironmentSettings, recordOfflineRecoveryBackupLocations, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };
|
|
11472
|
+
export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_TARGET_SIGNER_PASSWORD_REQUIRED, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, EncryptedBackupUnavailableError, ExpiredGoogleDriveTokenError, GoogleDriveBackupBlockedError, HEAVY_QUEUE_OPERATIONS, OfflineRecoveryCloudBackupConflictError, OperationSource, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SERVER_ROOM_ID_WAIT_WARNING_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, buildErrorContext, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, createOfflineRecoveryDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getEd25519PublicKeyFromRawScalar, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, getOfflineRecoveryShareSet, getShareSet, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isMultipleWalletsPerChainRejection, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, normalizeRawEd25519ScalarHex, readEnvironmentSettings, recordOfflineRecoveryBackupLocations, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise, trackForwardMpcEnclave };
|