export * from '#internal/core';
import { MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, isAxiosError, handleAxiosError, Logger, DynamicApiClient, getClientThreshold, MPC_CONFIG, classifyForwardMpcError, getTSSConfig, serializeMessageForForwardMPC, getEnvironmentFromUrl, MPC_RELAY_URL_MAP, WalletOperation, getServerWalletReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, ThresholdSignatureScheme, DYNAMIC_AUTH_PROD_BASE_API_URL, DYNAMIC_FORWARD_MPC_ENCLAVE_URL_MAP, DYNAMIC_FORWARD_MPC_ENCLAVE_ATTESTATION_CONFIG_MAP } from '@dynamic-labs-wallet/core';
export { SOLANA_RPC_URL, ThresholdSignatureScheme, WalletOperation, getMPCChainConfig } from '@dynamic-labs-wallet/core';
import { SigningAlgorithm } from '@dynamic-labs-wallet/primitives';
import { BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/node';
import { Logger as Logger$1 } from '@dynamic-labs/logger';
import { ForwardMPCClientV2 } from '@dynamic-labs-wallet/forward-mpc-client';
import { v4 } from 'uuid';
import crypto from 'crypto';
import crypto$1 from 'node:crypto';

// Removed duplicate exports - these are already exported from #internal/core
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
    switch(signingAlgorithm){
        case SigningAlgorithm.ECDSA:
            return new Ecdsa(baseRelayUrl);
        case SigningAlgorithm.ED25519:
            return new ExportableEd25519(baseRelayUrl);
        case SigningAlgorithm.BIP340:
            return new BIP340(baseRelayUrl);
        default:
            throw new Error(`Unsupported signing algorithm: ${signingAlgorithm}`);
    }
};
const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig })=>{
    const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
    const signatureScheme = getMPCSignatureScheme({
        signingAlgorithm: chainConfig.signingAlgorithm,
        baseRelayUrl
    });
    return signatureScheme;
};

function _extends() {
    _extends = Object.assign || function assign(target) {
        for(var i = 1; i < arguments.length; i++){
            var source = arguments[i];
            for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
        }
        return target;
    };
    return _extends.apply(this, arguments);
}

const createLogError = (clientTag)=>({ message, error, context })=>logError({
            message,
            error,
            context,
            clientTag
        });
const logError = ({ message, error, context, clientTag = 'node' })=>{
    if (isAxiosError(error)) {
        handleAxiosError(error, message, context);
        return;
    }
    Logger.error(`[DynamicWaasWalletClient] Error in ${clientTag} client`, {
        message,
        error: error instanceof Error ? error.message : String(error),
        context
    });
};
const bytesToBase64 = (arr)=>{
    return btoa(Array.from(arr, (b)=>String.fromCharCode(b)).join(''));
};
const stringToBytes = (str)=>{
    return new TextEncoder().encode(str);
};
const base64ToBytes = (base64)=>{
    return new Uint8Array(Buffer.from(base64, 'base64'));
};
// Helper function to ensure proper base64 padding
const ensureBase64Padding = (str)=>{
    return str.padEnd(Math.ceil(str.length / 4) * 4, '=');
};
const stripHexPrefix = (str)=>str.startsWith('0x') ? str.slice(2) : str;
const isHexString = (str)=>/^[0-9A-Fa-f]+$/.test(stripHexPrefix(str));
const getExternalServerKeyShareBackupInfo = (params)=>{
    var _params_walletProperties, _params_walletProperties_keyShares_;
    const backups = {
        [BackupLocation.DYNAMIC]: [],
        [BackupLocation.GOOGLE_DRIVE]: [],
        [BackupLocation.ICLOUD]: [],
        [BackupLocation.USER]: [],
        [BackupLocation.EXTERNAL]: [],
        [BackupLocation.DELEGATED]: []
    };
    if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
        return {
            backups,
            passwordEncrypted: false
        };
    }
    params.walletProperties.keyShares.forEach((keyShare)=>{
        if (backups[keyShare.backupLocation]) {
            backups[keyShare.backupLocation].push({
                location: keyShare.backupLocation,
                id: keyShare.id,
                keygenId: keyShare.keygenId,
                externalKeyShareId: keyShare.externalKeyShareId,
                passwordEncrypted: keyShare.passwordEncrypted
            });
        }
    });
    const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
    return {
        backups,
        passwordEncrypted
    };
};
/**
 * Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
 * @param existingKeyShares - Array of existing keyshares
 * @param newKeyShares - Array of new keyshares to merge
 * @returns Array of merged unique keyshares
 */ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
    const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
            if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
            return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
        }));
    return [
        ...existingKeyShares,
        ...uniqueKeyShares
    ];
};
/**
 * Generic helper function to retry a promise-based operations
 *
 * @param operation - The async operation to retry
 * @param config - Configuration options for retry behavior
 * @returns Promise with the operation result
 * @throws Last error encountered after all retries are exhausted
 */ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
    let attempts = 0;
    while(attempts < maxAttempts){
        try {
            return await operation();
        } catch (error) {
            attempts++;
            if (attempts === maxAttempts) {
                Logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
                    error
                }));
                throw error;
            }
            await new Promise((resolve)=>setTimeout(resolve, retryInterval));
        }
    }
    // TypeScript needs this even though it's unreachable
    throw new Error('Unreachable code');
}
const formatEvmMessage = (message)=>{
    if (typeof message === 'string' && message.startsWith('0x')) {
        const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
        return MessageHash.keccak256(serializedTxBytes);
    }
    return MessageHash.keccak256(message);
};
const formatSolanaMessage = (message)=>{
    if (typeof message === 'string') {
        if (!isHexString(message)) {
            return Buffer.from(message).toString('hex');
        } else {
            return new Uint8Array(Buffer.from(message, 'hex'));
        }
    } else {
        return message;
    }
};
const formatTonMessage = (message)=>{
    if (typeof message === 'string') {
        if (!isHexString(message)) {
            return Buffer.from(message).toString('hex');
        } else {
            return new Uint8Array(Buffer.from(message, 'hex'));
        }
    } else {
        return message;
    }
};
const formatMessage = (chainName, message)=>{
    switch(chainName){
        case 'EVM':
            return formatEvmMessage(message);
        case 'SVM':
            return formatSolanaMessage(message);
        case 'SUI':
            return message;
        case 'TON':
            return formatTonMessage(message);
        default:
            throw new Error('Unsupported chain name');
    }
};

const ENCRYPTION_VERSION_LEGACY = 'v1';
const ENCRYPTION_VERSION_CURRENT = 'v2';
const PBKDF2_ALGORITHM = 'PBKDF2';
const PBKDF2_HASH_ALGORITHM = 'SHA-256';
const AES_GCM_ALGORITHM = 'AES-GCM';
const AES_GCM_LENGTH = 256;
const ENCRYPTION_VERSIONS = {
    [ENCRYPTION_VERSION_LEGACY]: {
        version: ENCRYPTION_VERSION_LEGACY,
        algorithm: AES_GCM_ALGORITHM,
        keyDerivation: PBKDF2_ALGORITHM,
        iterations: 100000,
        hashAlgorithm: PBKDF2_HASH_ALGORITHM,
        algorithmLength: AES_GCM_LENGTH
    },
    [ENCRYPTION_VERSION_CURRENT]: {
        version: ENCRYPTION_VERSION_CURRENT,
        algorithm: AES_GCM_ALGORITHM,
        keyDerivation: PBKDF2_ALGORITHM,
        iterations: 1000000,
        hashAlgorithm: PBKDF2_HASH_ALGORITHM,
        algorithmLength: AES_GCM_LENGTH
    }
};
ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_CURRENT].iterations;
ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_LEGACY].iterations;
const getKey = async ({ password, salt, encryptionConfig })=>{
    const passwordBytes = stringToBytes(password);
    const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
        name: 'PBKDF2'
    }, false, [
        'deriveKey'
    ]);
    return crypto.subtle.deriveKey({
        name: encryptionConfig.keyDerivation,
        salt: salt,
        iterations: encryptionConfig.iterations,
        hash: encryptionConfig.hashAlgorithm
    }, initialKey, {
        name: encryptionConfig.algorithm,
        length: encryptionConfig.algorithmLength
    }, false, [
        'encrypt',
        'decrypt'
    ]);
};
/**
 * Encrypts data using the specified encryption version.
 * Always uses the latest encryption configuration for new encryptions by default.
 */ const encryptData = async ({ data, password, version = ENCRYPTION_VERSION_CURRENT })=>{
    const encryptionConfig = ENCRYPTION_VERSIONS[version];
    if (!encryptionConfig) {
        throw new Error(`Unsupported encryption version: ${version}`);
    }
    try {
        // Generate a random salt and IV
        const salt = crypto.getRandomValues(new Uint8Array(16));
        const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM requires 12 bytes
        const key = await getKey({
            password,
            salt,
            encryptionConfig
        });
        // Convert the input string to bytes
        const dataBytes = new TextEncoder().encode(data);
        // Encrypt the data
        const encryptedData = await crypto.subtle.encrypt({
            name: AES_GCM_ALGORITHM,
            iv
        }, key, dataBytes);
        // Convert to base64 strings, ensure proper padding
        return {
            salt: bytesToBase64(salt),
            iv: bytesToBase64(iv),
            cipher: bytesToBase64(new Uint8Array(encryptedData)),
            version
        };
    } catch (e) {
        throw new Error('Error encrypting data');
    }
};
/**
 * Decrypts data with version-based configuration.
 * Uses the version field from the data to determine encryption parameters.
 * Falls back to legacy version for backward compatibility if no version is specified.
 */ const decryptData = async ({ data, password })=>{
    const { salt, iv, cipher, version } = data;
    // Ensure proper base64 padding for all values
    const paddedSalt = ensureBase64Padding(salt);
    const paddedIv = ensureBase64Padding(iv);
    const paddedCipher = ensureBase64Padding(cipher);
    const saltBytes = base64ToBytes(paddedSalt);
    const ivBytes = base64ToBytes(paddedIv);
    const cipherBytes = base64ToBytes(paddedCipher);
    let encryptionConfig;
    // Use version-based configuration if available, otherwise fallback to legacy
    if (version && ENCRYPTION_VERSIONS[version]) {
        encryptionConfig = ENCRYPTION_VERSIONS[version];
    } else {
        // Fallback to legacy version for backward compatibility
        encryptionConfig = ENCRYPTION_VERSIONS[ENCRYPTION_VERSION_LEGACY];
    }
    try {
        const key = await getKey({
            password,
            salt: saltBytes,
            encryptionConfig
        });
        const decryptedData = await crypto.subtle.decrypt({
            name: AES_GCM_ALGORITHM,
            iv: ivBytes
        }, key, cipherBytes);
        return new TextDecoder().decode(decryptedData);
    } catch (error) {
        throw new Error('Decryption failed: ' + error);
    }
};

// Cap on the post-MPC wait for ceremony_complete before falling through.
// Legacy redcoast never emits the event; the operation still succeeds via
// walletId resolution server-side.
const CEREMONY_COMPLETE_WAIT_MS = 5000;

class DynamicWalletClient {
    ensureApiClientAuthenticated() {
        if (!this.isApiClientAuthenticated) {
            throw new Error('Client must be authenticated before making API calls. Call authenticateApiToken first.');
        }
    }
    /**
   * Guards against silent operate-on-different-wallet bugs when callers pass
   * both `accountAddress` and `walletMetadata`. Throws when either is missing
   * or when they disagree.
   *
   * Comparison is case-insensitive only for EVM (where checksum casing is
   * decoration over an underlying hex address). SVM (base58), BTC base58,
   * and TON (base64url) are case-sensitive — two addresses that differ only
   * in case are different addresses.
   */ assertAddressMatchesMetadata(accountAddress, walletMetadata) {
        var _walletMetadata_chainName;
        if (!accountAddress) {
            throw new Error('accountAddress is required and must not be empty.');
        }
        if (!walletMetadata.accountAddress) {
            throw new Error('walletMetadata.accountAddress is required and must not be empty.');
        }
        // chainName comparison is case-insensitive — chain clients set the
        // canonical uppercase form, but server-fetched walletMetadata could
        // theoretically arrive with a different case.
        const isEvm = ((_walletMetadata_chainName = walletMetadata.chainName) == null ? void 0 : _walletMetadata_chainName.toUpperCase()) === 'EVM';
        const left = isEvm ? accountAddress.toLowerCase() : accountAddress;
        const right = isEvm ? walletMetadata.accountAddress.toLowerCase() : walletMetadata.accountAddress;
        if (left !== right) {
            throw new Error('accountAddress mismatch: parameter does not match walletMetadata.accountAddress. ' + 'Pass them consistently — preferably read from walletMetadata.accountAddress.');
        }
    }
    async authenticateApiToken(authToken) {
        const tmpClient = new DynamicApiClient({
            environmentId: this.environmentId,
            authToken,
            baseApiUrl: this.baseApiUrl,
            logger: this.logger
        });
        const response = await tmpClient.authenticateApiToken({
            environmentId: this.environmentId
        });
        const jwtTokenAuth = response.data.encodedJwts.minifiedJwt;
        this.baseJWTAuthToken = jwtTokenAuth;
        this.apiClient = new DynamicApiClient({
            environmentId: this.environmentId,
            authToken: jwtTokenAuth,
            baseApiUrl: this.baseApiUrl,
            forwardMPCClient: this.resolvedForwardMPCClient,
            logger: this.logger
        });
        this.isApiClientAuthenticated = true;
    }
    /**
   * Fetches non-sensitive wallet identity (walletId, accountAddress, chainName,
   * derivationPath, thresholdSignatureScheme) from the Dynamic API by
   * `accountAddress`. SDK-scoped — works with the customer auth token from
   * `authenticateApiToken`.
   *
   * **What this returns and what it does NOT return:**
   * - Returns: wallet identity fields above.
   * - Does NOT return: `externalServerKeySharesBackupInfo` (the per-share
   *   pointer metadata that drives recovery), `addressType`, or any
   *   encrypted material. The byAddress endpoint is intentionally a slim
   *   identity lookup; pointer metadata and addressType only come from the
   *   `createWalletAccount` / `importPrivateKey` return values.
   *
   * **Customer responsibility:**
   * Persist the FULL `walletMetadata` returned by `createWalletAccount` /
   * `importPrivateKey` (which includes `externalServerKeySharesBackupInfo` and
   * `addressType`). `fetchWalletMetadata` recovers identity only — it cannot
   * substitute for a properly-cached `walletMetadata` after a Redis wipe if
   * the operation needs `backupInfo` (sign with auto-recovery, password
   * checks, refresh, etc.).
   *
   * Does not cache, does not mutate SDK state.
   */ async fetchWalletMetadata(accountAddress) {
        this.ensureApiClientAuthenticated();
        const { wallet } = await this.apiClient.getWaasWalletByAddress({
            walletAddress: accountAddress
        });
        return {
            walletId: wallet.walletId,
            accountAddress: wallet.accountAddress,
            chainName: wallet.chainName,
            thresholdSignatureScheme: wallet.thresholdSignatureScheme,
            derivationPath: wallet.derivationPath,
            // shareSetId/Type/otherShareSets are returned by redcoast post-deploy of
            // its byWalletAddress extension. Pass undefined on older deploys; the
            // SDK's lifecycle ops fall back to walletId-based resolution.
            shareSetId: wallet.shareSetId,
            shareSetType: wallet.shareSetType,
            otherShareSets: wallet.otherShareSets
        };
    }
    /**
   * Returns the cached `externalServerKeySharesBackupInfo` from
   * `walletMetadata`. The Dynamic API does not currently expose a SDK-scoped
   * endpoint that returns the keyShare pointer metadata by address (only the
   * unbounded `getUser` does, which we avoid for server wallets). So the
   * customer must persist the full `walletMetadata` (with backupInfo)
   * returned by `createWalletAccount` / `importPrivateKey` and pass it back
   * in on subsequent calls. Throws loudly when missing — never silently
   * synthesizes empty state.
   */ async resolveBackupInfo(walletMetadata) {
        if (!walletMetadata.externalServerKeySharesBackupInfo) {
            throw new Error('walletMetadata.externalServerKeySharesBackupInfo is required. ' + 'Persist the full walletMetadata returned by createWalletAccount/importPrivateKey ' + '(including externalServerKeySharesBackupInfo) and pass it back in on subsequent calls.');
        }
        return walletMetadata.externalServerKeySharesBackupInfo;
    }
    async dynamicServerInitializeKeyGen({ chainName, externalServerKeygenIds, thresholdSignatureScheme, dynamicRequestId, skipLock, bitcoinConfig, onError, onCeremonyComplete }) {
        this.ensureApiClientAuthenticated();
        try {
            const data = await this.apiClient.createWalletAccount({
                chainName,
                clientKeygenIds: externalServerKeygenIds,
                thresholdSignatureScheme,
                dynamicRequestId,
                skipLock,
                bitcoinConfig,
                onError,
                onCeremonyComplete
            });
            return data;
        } catch (error) {
            this.logger.error('Error in dynamicServerInitializeKeyGen', error);
            throw new Error('Error creating wallet account in dynamicServerInitializeKeyGen');
        }
    }
    async externalServerInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig }) {
        try {
            // Get the mpc signer
            const mpcSigner = getMPCSigner({
                chainName,
                baseRelayUrl: this.baseMPCRelayApiUrl,
                bitcoinConfig
            });
            const clientThreshold = getClientThreshold(thresholdSignatureScheme);
            const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
            return keygenInitResults;
        } catch (error) {
            this.logger.error('Error in externalServerInitializeKeyGen', error);
            throw new Error('Error initializing keygen in externalServerInitializeKeyGen');
        }
    }
    async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig }) {
        try {
            const mpcSigner = getMPCSigner({
                chainName,
                baseRelayUrl: this.baseMPCRelayApiUrl,
                bitcoinConfig
            });
            let publicKey;
            if (mpcSigner instanceof Ecdsa) {
                publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
            } else if (mpcSigner instanceof ExportableEd25519) {
                publicKey = await mpcSigner.getPubkey(keyShare);
            } else if (mpcSigner instanceof BIP340) {
                publicKey = await mpcSigner.deriveTweakPubkey(keyShare, derivationPath);
            }
            return publicKey;
        } catch (error) {
            this.logger.error('Error in derivePublicKey', error);
            throw new Error('Error deriving public key in derivePublicKey');
        }
    }
    async forwardMPCExternalServerKeyGen({ chainName, roomId, dynamicServerKeygenIds, externalServerInitKeygenResults, thresholdSignatureScheme, bitcoinConfig }) {
        try {
            const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
            const signingAlgo = chainConfig.signingAlgorithm;
            const mpcConfig = MPC_CONFIG[thresholdSignatureScheme];
            const isEd25519 = typeof signingAlgo === 'string' && signingAlgo.toLowerCase().includes('ed25519');
            const serverKeygenResults = await Promise.all(externalServerInitKeygenResults.map(async (currentInit)=>{
                const otherExternalServerKeygenIds = externalServerInitKeygenResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
                const allOtherKeygenIds = [
                    ...dynamicServerKeygenIds,
                    ...otherExternalServerKeygenIds
                ];
                const keygenMethod = isEd25519 ? this.apiClient.forwardMPCClient.receiveKey.bind(this.apiClient.forwardMPCClient) : this.apiClient.forwardMPCClient.keygen.bind(this.apiClient.forwardMPCClient);
                return await keygenMethod({
                    roomUuid: roomId,
                    signingAlgo,
                    numParties: mpcConfig.numberOfParties,
                    threshold: mpcConfig.threshold,
                    keygenInit: {
                        keygenId: currentInit.keygenId,
                        keygenSecret: currentInit.keygenSecret
                    },
                    keygenIds: allOtherKeygenIds,
                    relayDomain: this.baseMPCRelayApiUrl || '',
                    environmentId: this.environmentId
                });
            }));
            const [serverKeygenResult] = serverKeygenResults;
            const chainConfigForDeriv = getMPCChainConfig(chainName, bitcoinConfig);
            const derivationPath = new Uint32Array(chainConfigForDeriv.derivationPath);
            const rawPublicKey = await this.derivePublicKey({
                chainName,
                keyShare: serverKeygenResult,
                derivationPath,
                bitcoinConfig
            });
            return {
                rawPublicKey,
                externalServerKeyGenResults: serverKeygenResults
            };
        } catch (error) {
            this.logger.error('Error during forward MPC external server keygen', {
                error,
                chainName,
                environmentId: this.environmentId
            });
            throw error;
        } finally{
            this.apiClient.forwardMPCClient.disconnect();
        }
    }
    async externalServerKeyGen({ chainName, roomId, dynamicServerKeygenIds, externalServerInitKeygenResults, thresholdSignatureScheme, bitcoinConfig }) {
        // Try forward MPC first if enabled
        if (this.forwardMPCEnabled) {
            try {
                return await this.forwardMPCExternalServerKeyGen({
                    chainName,
                    roomId,
                    dynamicServerKeygenIds,
                    externalServerInitKeygenResults,
                    thresholdSignatureScheme,
                    bitcoinConfig
                });
            } catch (error) {
                const errorInfo = classifyForwardMpcError(error);
                this.logger.warn('Forward MPC keygen failed', _extends({}, errorInfo, {
                    chainName,
                    environmentId: this.environmentId
                }));
                if (!errorInfo.shouldFallback) {
                    throw error;
                }
            // Fall through to relay-based keygen below
            }
        }
        try {
            // Get the chain config and the mpc signer
            const mpcSigner = getMPCSigner({
                chainName,
                baseRelayUrl: this.baseMPCRelayApiUrl,
                bitcoinConfig
            });
            // Get the MPC config for the threshold signature scheme
            const mpcConfig = MPC_CONFIG[thresholdSignatureScheme];
            // For each client keygen init result, create an array of other parties' keygenIds
            const serverKeygenResults = await Promise.all(externalServerInitKeygenResults.map((currentInit)=>{
                // Get all other client keygenIds (excluding current one)
                const otherExternalServerKeygenIds = externalServerInitKeygenResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
                // Combine server keygenIds with other client keygenIds
                const allOtherKeygenIds = [
                    ...dynamicServerKeygenIds,
                    ...otherExternalServerKeygenIds
                ];
                if (!(mpcSigner instanceof ExportableEd25519)) {
                    return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
                } else {
                    // One party joins the keygen room using acting as the sampler: (wallet-service)
                    // The remaining parties join the key sampling ceremony using: (browser)
                    return mpcSigner.receiveKey(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
                }
            }));
            // only need one client keygen result to derive the public key
            const [serverKeygenResult] = serverKeygenResults;
            const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
            const derivationPath = new Uint32Array(chainConfig.derivationPath);
            const rawPublicKey = await this.derivePublicKey({
                chainName,
                keyShare: serverKeygenResult,
                derivationPath,
                bitcoinConfig
            });
            return {
                rawPublicKey,
                externalServerKeyGenResults: serverKeygenResults
            };
        } catch (error) {
            this.logger.error('Error in externalServerKeyGen', error);
            throw new Error('Error deriving public key in externalServerKeyGen');
        }
    }
    validatePasswordForBackup({ password, backUpToDynamic }) {
        if (backUpToDynamic && (!password || password === this.environmentId)) {
            throw new Error('A password is required when backing up to Dynamic. ' + 'You can use the same password for all wallets or a unique password per wallet.');
        }
    }
    async keyGen({ chainName, thresholdSignatureScheme, skipLock, bitcoinConfig, password, backUpToDynamic, onError, onCeremonyComplete }) {
        this.validatePasswordForBackup({
            password,
            backUpToDynamic
        });
        const dynamicRequestId = v4();
        try {
            const externalServerInitKeygenResults = await this.externalServerInitializeKeyGen({
                chainName,
                thresholdSignatureScheme,
                bitcoinConfig
            });
            const externalServerKeygenIds = externalServerInitKeygenResults.map((result)=>result.keygenId);
            const { roomId, serverKeygenIds: dynamicServerKeygenIds } = await this.dynamicServerInitializeKeyGen({
                chainName,
                externalServerKeygenIds,
                dynamicRequestId,
                thresholdSignatureScheme,
                skipLock,
                bitcoinConfig,
                onCeremonyComplete
            });
            const { rawPublicKey, externalServerKeyGenResults } = await this.externalServerKeyGen({
                chainName,
                roomId,
                dynamicServerKeygenIds,
                externalServerInitKeygenResults,
                thresholdSignatureScheme,
                bitcoinConfig
            });
            return {
                rawPublicKey,
                externalServerKeyShares: externalServerKeyGenResults
            };
        } catch (error) {
            this.logger.error('Error in keyGen', error);
            throw new Error('Error creating wallet account in keyGen');
        }
    }
    async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, password, backUpToDynamic, onError, onCeremonyComplete }) {
        this.validatePasswordForBackup({
            password,
            backUpToDynamic
        });
        this.ensureApiClientAuthenticated();
        const dynamicRequestId = v4();
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: this.baseMPCRelayApiUrl,
            bitcoinConfig
        });
        const externalServerKeygenInitResults = await this.externalServerInitializeKeyGen({
            chainName,
            thresholdSignatureScheme,
            bitcoinConfig
        });
        const externalServerKeygenIds = externalServerKeygenInitResults.map((result)=>result.keygenId);
        const { roomId, serverKeygenIds: dynamicServerKeygenIds } = await this.apiClient.importPrivateKey({
            chainName,
            clientKeygenIds: externalServerKeygenIds,
            thresholdSignatureScheme,
            bitcoinConfig,
            dynamicRequestId,
            onError,
            onCeremonyComplete
        });
        const { threshold } = getTSSConfig(thresholdSignatureScheme);
        const externalServerKeygenResults = await Promise.all(externalServerKeygenInitResults.map(async (currentInit, index)=>{
            const otherExternalServerKeygenIds = externalServerKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
            if (index === 0) {
                const otherKeyGenIds = [
                    ...dynamicServerKeygenIds,
                    ...otherExternalServerKeygenIds
                ];
                const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
                return importerKeygenResult;
            } else {
                const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
                    ...dynamicServerKeygenIds,
                    ...otherExternalServerKeygenIds
                ]);
                return recipientKeygenResult;
            }
        }));
        const [externalServerKeygenResult] = externalServerKeygenResults;
        const rawPublicKey = await this.derivePublicKey({
            chainName,
            keyShare: externalServerKeygenResult,
            derivationPath: undefined,
            bitcoinConfig
        });
        return {
            rawPublicKey,
            externalServerKeyShares: externalServerKeygenResults
        };
    }
    async dynamicServerSign({ walletId, message, isFormatted, context, bitcoinConfig, onError, dynamicRequestId: providedRequestId }) {
        const dynamicRequestId = providedRequestId || v4();
        this.ensureApiClientAuthenticated();
        // Create the room and sign the message
        if (typeof message !== 'string') {
            message = '0x' + Buffer.from(message).toString('hex');
        }
        const data = await this.apiClient.signMessage({
            walletId,
            message,
            isFormatted,
            dynamicRequestId,
            context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined,
            onError,
            forwardMPCClientEnabled: this.forwardMPCEnabled,
            bitcoinConfig
        });
        return _extends({}, data, {
            dynamicRequestId
        });
    }
    async externalServerSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, bitcoinConfig }) {
        try {
            const mpcSigner = getMPCSigner({
                chainName,
                baseRelayUrl: this.baseMPCRelayApiUrl,
                bitcoinConfig
            });
            const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
            const tweak = bitcoinConfig == null ? void 0 : bitcoinConfig.tweak;
            const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath, tweak);
            return signature;
        } catch (error) {
            this.logger.error('Error in externalServerSign', error);
            throw error;
        }
    }
    async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, walletId }) {
        try {
            const messageForForwardMPC = serializeMessageForForwardMPC({
                message,
                isFormatted,
                chainName
            });
            this.logger.debug('Signing message with MPC Socket Client');
            const environment = getEnvironmentFromUrl(this.baseApiUrl);
            const defaultRelayUrl = MPC_RELAY_URL_MAP[environment];
            // Ed25519 chains (SVM, TON) must send the formatted (hex) message — the
            // forward-MPC server decodes it as hex before signing. Raw plaintext for
            // odd-length messages produces "hex string expected, got unpadded hex of length N".
            const isEd25519Chain = chainName === 'SVM' || chainName === 'TON';
            const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage({
                keyshare: keyShare,
                message: isEd25519Chain ? formattedMessage : messageForForwardMPC,
                relayDomain: this.baseMPCRelayApiUrl || defaultRelayUrl,
                signingAlgo: chainName === 'EVM' ? 'ECDSA' : 'ED25519',
                hashAlgo: chainName === 'EVM' && !isFormatted ? 'keccak256' : undefined,
                derivationPath: derivationPath,
                roomUuid: roomId,
                walletId,
                environmentId: this.environmentId,
                traceContext: {
                    traceId: dynamicRequestId
                }
            });
            if (!(signatureBytes instanceof Uint8Array)) {
                throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
            }
            // Convert to EcdsaSignature
            if (chainName === 'EVM') {
                const ecdsaSignature = EcdsaSignature.fromBuffer(signatureBytes);
                return ecdsaSignature;
            } else {
                return signatureBytes;
            }
        } catch (error) {
            this.logger.error('Error signing message with forward MPC client', {
                error,
                walletId,
                environmentId: this.environmentId,
                dynamicRequestId
            });
            throw error;
        } finally{
            this.apiClient.forwardMPCClient.disconnect();
        }
    }
    /**
   * Attempts forward MPC signing when enabled. Returns the signature on success,
   * or `undefined` when forward MPC is disabled or failed with a recoverable
   * (fallback-eligible) error. Non-fallback-eligible errors are rethrown.
   */ async trySignViaForwardMpc({ chainName, message, roomId, keyShare, derivationPath, isFormatted, walletId, dynamicRequestId }) {
        if (!this.forwardMPCEnabled) {
            return undefined;
        }
        try {
            const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
            return await this.forwardMPCClientSign({
                chainName,
                message,
                roomId,
                keyShare,
                derivationPath,
                formattedMessage,
                dynamicRequestId,
                isFormatted,
                walletId
            });
        } catch (error) {
            const errorInfo = classifyForwardMpcError(error);
            this.logger.warn('Forward MPC signing failed', _extends({}, errorInfo, {
                chainName,
                environmentId: this.environmentId,
                dynamicRequestId
            }));
            if (!errorInfo.shouldFallback) {
                throw error;
            }
            return undefined;
        }
    }
    async sign({ accountAddress, externalServerKeyShares, message, chainName, password = undefined, isFormatted = false, context, onError, bitcoinConfig, walletMetadata, walletOperation = WalletOperation.SIGN_MESSAGE, passwordPreVerified = false }) {
        try {
            this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
            const backupInfo = walletMetadata.externalServerKeySharesBackupInfo;
            // Recover internally if no shares supplied — decryption with `password`
            // implicitly validates it, so we won't re-run verifyPassword below.
            const resolvedShares = externalServerKeyShares && externalServerKeyShares.length > 0 ? externalServerKeyShares : await this.resolveKeyShares({
                accountAddress,
                password,
                walletOperation,
                externalServerKeyShares,
                errorMessage: 'External server key shares are required to sign a message',
                walletMetadata,
                backupInfo
            });
            const sharesProvided = resolvedShares === externalServerKeyShares;
            // verifyPassword only when shares came from the caller (no implicit
            // validation). BTC sets `passwordPreVerified` when it pre-resolved.
            // Refuse to silently sign without verifying — missing backupInfo on
            // this branch (e.g. caller built walletMetadata via fetchWalletMetadata
            // identity-only path) would otherwise let any password through.
            if (sharesProvided && !passwordPreVerified) {
                if (!backupInfo) {
                    throw new Error('walletMetadata.externalServerKeySharesBackupInfo is required to verify the password ' + 'when signing with caller-supplied externalServerKeyShares. Persist the full walletMetadata ' + 'returned by createWalletAccount/importPrivateKey and pass it back in. ' + 'fetchWalletMetadata returns identity-only metadata and cannot be used on this path.');
                }
                await this.verifyPassword({
                    accountAddress,
                    password,
                    walletMetadata,
                    backupInfo
                });
            }
            const walletId = walletMetadata.walletId;
            const derivationPathStr = walletMetadata.derivationPath;
            // Perform the dynamic server sign
            const data = await this.dynamicServerSign({
                walletId,
                message,
                isFormatted,
                context,
                bitcoinConfig,
                onError
            });
            const derivationPath = derivationPathStr && derivationPathStr != '' ? new Uint32Array(Object.values(JSON.parse(derivationPathStr))) : undefined;
            // Try forward MPC first if enabled, with fallback to relay-based signing on attestation failure
            const forwardMpcSignature = await this.trySignViaForwardMpc({
                chainName,
                message,
                roomId: data.roomId,
                keyShare: resolvedShares[0],
                derivationPath,
                isFormatted,
                walletId,
                dynamicRequestId: data.dynamicRequestId
            });
            if (forwardMpcSignature !== undefined) {
                return forwardMpcSignature;
            }
            // Relay-based signing (fallback or when forward MPC is disabled)
            return await this.externalServerSign({
                chainName,
                message,
                roomId: data.roomId,
                keyShare: resolvedShares[0],
                derivationPath,
                isFormatted,
                bitcoinConfig
            });
        } catch (error) {
            if (!(error instanceof Error && error.message === 'External server key shares are required to sign a message')) {
                this.logger.error('Error in sign', error);
            }
            throw error;
        }
    }
    async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, externalServerKeyShares, backUpToDynamic = false, walletMetadata }) {
        this.validatePasswordForBackup({
            password,
            backUpToDynamic
        });
        this.ensureApiClientAuthenticated();
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const backupInfo = await this.resolveBackupInfo(walletMetadata);
        await this.verifyPassword({
            accountAddress,
            password,
            walletMetadata,
            backupInfo
        });
        const bitcoinConfig = chainName === 'BTC' && walletMetadata.addressType ? {
            addressType: walletMetadata.addressType
        } : undefined;
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: this.baseMPCRelayApiUrl,
            bitcoinConfig
        });
        // Capture the new shareSetId from the ceremony so backup/locations routes
        // the new server share to the post-ceremony pending share set, not the
        // stale active one. Bounded await covers legacy redcoast that doesn't emit
        // the event — redcoast's server-route fallback handles that case.
        let resolveCeremonyComplete;
        const ceremonyComplete = new Promise((resolve)=>{
            resolveCeremonyComplete = resolve;
        });
        let capturedShareSetId;
        let capturedShareSetType;
        // Tracks whether the SSE ceremony_complete event fired. Logged at
        // completion so we can correlate "callback didn't fire" with downstream
        // sign failures.
        let ceremonyCallbackFired = false;
        this.logger.info('[WaasRefresh] calling refreshWalletAccountShares', {
            shareSetId: walletMetadata.shareSetId,
            walletId: walletMetadata.walletId
        });
        const data = await this.apiClient.refreshWalletAccountShares({
            walletId: walletMetadata.walletId,
            shareSetId: walletMetadata.shareSetId,
            onCeremonyComplete: (_accountAddress, _walletId, shareSetId, shareSetType)=>{
                ceremonyCallbackFired = true;
                capturedShareSetId = shareSetId;
                capturedShareSetType = shareSetType;
                this.logger.info('[WaasRefresh] ceremony_complete received', {
                    newShareSetId: shareSetId,
                    newShareSetType: shareSetType,
                    walletId: walletMetadata.walletId
                });
                resolveCeremonyComplete();
            }
        });
        const roomId = data.roomId;
        this.logger.info('[WaasRefresh] room_created received, starting local MPC', {
            roomId,
            walletId: walletMetadata.walletId
        });
        if (!externalServerKeyShares) {
            throw new Error('External server key shares are required to refresh');
        }
        const refreshResults = await Promise.all(externalServerKeyShares.map((serverKeyShare)=>mpcSigner.refresh(roomId, serverKeyShare)));
        this.logger.info('[WaasRefresh] local MPC done, awaiting ceremony_complete before backup', {
            ceremonyCallbackFired,
            walletId: walletMetadata.walletId
        });
        // Wait for ceremony_complete so capturedShareSetId is populated before
        // backup reads it. Bounded by CEREMONY_COMPLETE_WAIT_MS so a missing/late
        // event never hangs the SDK — if it fires we proceed with the rotated id;
        // if it doesn't we fall through with capturedShareSetId still undefined
        // and rely on redcoast's walletId-based fallback (legacy behavior).
        let ceremonyCompleteTimedOut = false;
        let refreshTimeoutId;
        await Promise.race([
            ceremonyComplete,
            new Promise((resolve)=>{
                refreshTimeoutId = setTimeout(()=>{
                    ceremonyCompleteTimedOut = true;
                    resolve();
                }, CEREMONY_COMPLETE_WAIT_MS);
            })
        ]);
        // Cancel the pending timer when ceremony_complete wins the race so it
        // doesn't fire later and flip ceremonyCompleteTimedOut after the fact.
        clearTimeout(refreshTimeoutId);
        if (ceremonyCompleteTimedOut) {
            this.logger.warn(`[WaasRefresh] ceremony_complete didn't arrive within ${CEREMONY_COMPLETE_WAIT_MS}ms — proceeding without captured shareSetId`, {
                walletId: walletMetadata.walletId
            });
        }
        const { backupInfo: updatedBackupInfo } = await this.storeEncryptedBackupByWallet({
            accountAddress,
            password: password != null ? password : this.environmentId,
            backUpToDynamic,
            externalServerKeyShares: refreshResults,
            walletMetadata,
            shareSetId: capturedShareSetId
        });
        // Return updated walletMetadata so callers can re-cache without merging
        // fields manually. shareSetId/Type fall back to the input if the server
        // didn't emit ceremony_complete (legacy redcoast).
        const updatedWalletMetadata = _extends({}, walletMetadata, {
            externalServerKeySharesBackupInfo: updatedBackupInfo,
            shareSetId: capturedShareSetId != null ? capturedShareSetId : walletMetadata.shareSetId,
            shareSetType: capturedShareSetType != null ? capturedShareSetType : walletMetadata.shareSetType
        });
        this.logger.info('[WaasRefresh] completed', {
            ceremonyCallbackFired,
            ceremonyCompleteTimedOut,
            newShareSetId: updatedWalletMetadata.shareSetId,
            newShareSetType: updatedWalletMetadata.shareSetType,
            oldShareSetId: walletMetadata.shareSetId,
            walletId: walletMetadata.walletId
        });
        return {
            externalServerKeyShares: refreshResults,
            backupInfo: updatedBackupInfo,
            walletMetadata: updatedWalletMetadata
        };
    }
    async getExportId({ chainName, serverKeyShare, bitcoinConfig }) {
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: this.baseMPCRelayApiUrl,
            bitcoinConfig
        });
        const exportId = await mpcSigner.exportID(serverKeyShare);
        return exportId;
    }
    /**
   * Helper function to create client shares required to complete a reshare ceremony.
   * @param {string} chainName - The chain to create shares for
   * @param {WalletProperties} wallet - The wallet to reshare
   * @param {ThresholdSignatureScheme} oldThresholdSignatureScheme - The current threshold signature scheme
   * @param {ThresholdSignatureScheme} newThresholdSignatureScheme - The target threshold signature scheme
   * @returns {Promise<{
   *   newClientInitKeygenResults: ClientInitKeygenResult[],
   *   newClientKeygenIds: string[],
   *   existingClientKeygenIds: string[],
   *   existingClientKeyShares: ClientKeyShare[]
   * }>} Object containing new and existing client keygen results, IDs and shares
   * @todo Support higher to lower reshare strategies
   */ async reshareStrategy({ chainName, wallet, oldThresholdSignatureScheme, newThresholdSignatureScheme }) {
        // Derive bitcoinConfig for BTC wallets
        const bitcoinConfig = chainName === 'BTC' && wallet.addressType ? {
            addressType: wallet.addressType
        } : undefined;
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: this.baseMPCRelayApiUrl,
            bitcoinConfig
        });
        // Determine share counts based on threshold signature schemes
        const { newExternalServerShareCount, existingExternalServerShareCount } = getServerWalletReshareConfig({
            oldThresholdSignatureScheme,
            newThresholdSignatureScheme
        });
        // Create new client shares
        const newExternalServerInitKeygenResults = await Promise.all(Array.from({
            length: newExternalServerShareCount
        }, ()=>mpcSigner.initKeygen()));
        const newExternalServerKeygenIds = newExternalServerInitKeygenResults.map((result)=>result.keygenId);
        // Get existing client shares
        const existingExternalServerKeyShares = wallet.externalServerKeyShares.slice(0, existingExternalServerShareCount);
        const existingExternalServerKeygenIds = await Promise.all(existingExternalServerKeyShares.map(async (keyShare)=>await this.getExportId({
                chainName,
                serverKeyShare: keyShare,
                bitcoinConfig
            })));
        return {
            newExternalServerInitKeygenResults,
            newExternalServerKeygenIds,
            existingExternalServerKeygenIds,
            existingExternalServerKeyShares
        };
    }
    async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, externalServerKeyShares, backUpToDynamic = false, walletMetadata }) {
        this.validatePasswordForBackup({
            password,
            backUpToDynamic
        });
        this.ensureApiClientAuthenticated();
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const backupInfo = await this.resolveBackupInfo(walletMetadata);
        await this.verifyPassword({
            accountAddress,
            password,
            walletMetadata,
            backupInfo
        });
        if (!externalServerKeyShares) {
            throw new Error('External server key shares are required to reshare');
        }
        const { newExternalServerInitKeygenResults, newExternalServerKeygenIds, existingExternalServerKeygenIds, existingExternalServerKeyShares } = await this.reshareStrategy({
            chainName,
            wallet: {
                addressType: walletMetadata.addressType,
                externalServerKeyShares
            },
            oldThresholdSignatureScheme,
            newThresholdSignatureScheme
        });
        const externalServerKeygenIds = [
            ...newExternalServerKeygenIds,
            ...existingExternalServerKeygenIds
        ];
        // Capture the new shareSetId from the ceremony so backup/locations routes
        // the new server share to the post-ceremony pending share set, not the
        // stale active one. Bounded await covers legacy redcoast that doesn't emit
        // the event — redcoast's server-route fallback handles that case.
        let resolveCeremonyComplete;
        const ceremonyComplete = new Promise((resolve)=>{
            resolveCeremonyComplete = resolve;
        });
        let capturedShareSetId;
        let capturedShareSetType;
        let ceremonyCallbackFired = false;
        this.logger.info('[WaasReshare] calling reshare', {
            newThresholdSignatureScheme,
            oldThresholdSignatureScheme,
            shareSetId: walletMetadata.shareSetId,
            walletId: walletMetadata.walletId
        });
        const data = await this.apiClient.reshare({
            walletId: walletMetadata.walletId,
            shareSetId: walletMetadata.shareSetId,
            clientKeygenIds: externalServerKeygenIds,
            oldThresholdSignatureScheme,
            newThresholdSignatureScheme,
            onCeremonyComplete: (_accountAddress, _walletId, shareSetId, shareSetType)=>{
                ceremonyCallbackFired = true;
                capturedShareSetId = shareSetId;
                capturedShareSetType = shareSetType;
                this.logger.info('[WaasReshare] ceremony_complete received', {
                    newShareSetId: shareSetId,
                    newShareSetType: shareSetType,
                    walletId: walletMetadata.walletId
                });
                resolveCeremonyComplete();
            }
        });
        const { roomId, serverKeygenIds: dynamicServerKeygenIds, newServerKeygenIds: newDynamicServerKeygenIds = [] } = data;
        this.logger.info('[WaasReshare] room_created received, starting local MPC', {
            roomId,
            walletId: walletMetadata.walletId
        });
        const oldMpcConfig = MPC_CONFIG[oldThresholdSignatureScheme];
        const newMpcConfig = MPC_CONFIG[newThresholdSignatureScheme];
        const allPartyKeygenIds = [
            ...externalServerKeygenIds,
            ...dynamicServerKeygenIds,
            ...newDynamicServerKeygenIds
        ];
        const bitcoinConfig = chainName === 'BTC' && walletMetadata.addressType ? {
            addressType: walletMetadata.addressType
        } : undefined;
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: this.baseMPCRelayApiUrl,
            bitcoinConfig
        });
        const reshareResults = await Promise.all([
            ...newExternalServerInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
            ...existingExternalServerKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
        ]);
        this.logger.info('[WaasReshare] local MPC done, awaiting ceremony_complete before backup', {
            ceremonyCallbackFired,
            walletId: walletMetadata.walletId
        });
        let ceremonyCompleteTimedOut = false;
        let reshareTimeoutId;
        await Promise.race([
            ceremonyComplete,
            new Promise((resolve)=>{
                reshareTimeoutId = setTimeout(()=>{
                    ceremonyCompleteTimedOut = true;
                    resolve();
                }, CEREMONY_COMPLETE_WAIT_MS);
            })
        ]);
        clearTimeout(reshareTimeoutId);
        if (ceremonyCompleteTimedOut) {
            this.logger.warn(`[WaasReshare] ceremony_complete didn't arrive within ${CEREMONY_COMPLETE_WAIT_MS}ms — proceeding without captured shareSetId`, {
                walletId: walletMetadata.walletId
            });
        }
        const { backupInfo: updatedBackupInfo } = await this.storeEncryptedBackupByWallet({
            accountAddress,
            password,
            backUpToDynamic,
            externalServerKeyShares: reshareResults,
            walletMetadata,
            shareSetId: capturedShareSetId
        });
        // Return updated walletMetadata so callers can re-cache without merging
        // fields manually. thresholdSignatureScheme also changes on reshare.
        const updatedWalletMetadata = _extends({}, walletMetadata, {
            thresholdSignatureScheme: newThresholdSignatureScheme,
            externalServerKeySharesBackupInfo: updatedBackupInfo,
            shareSetId: capturedShareSetId != null ? capturedShareSetId : walletMetadata.shareSetId,
            shareSetType: capturedShareSetType != null ? capturedShareSetType : walletMetadata.shareSetType
        });
        this.logger.info('[WaasReshare] completed', {
            ceremonyCallbackFired,
            ceremonyCompleteTimedOut,
            newShareSetId: updatedWalletMetadata.shareSetId,
            newShareSetType: updatedWalletMetadata.shareSetType,
            newThresholdSignatureScheme,
            oldShareSetId: walletMetadata.shareSetId,
            walletId: walletMetadata.walletId
        });
        return {
            externalServerKeyShares: reshareResults,
            backupInfo: updatedBackupInfo,
            walletMetadata: updatedWalletMetadata
        };
    }
    async exportKey({ accountAddress, chainName, password = undefined, externalServerKeyShares, bitcoinConfig, elevatedAccessToken, walletMetadata }) {
        this.ensureApiClientAuthenticated();
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const backupInfo = walletMetadata.externalServerKeySharesBackupInfo;
        const resolvedShares = externalServerKeyShares && externalServerKeyShares.length > 0 ? externalServerKeyShares : await this.resolveKeyShares({
            accountAddress,
            password,
            walletOperation: WalletOperation.EXPORT_PRIVATE_KEY,
            externalServerKeyShares,
            errorMessage: 'External server key shares are required to export a private key',
            walletMetadata,
            backupInfo
        });
        const sharesProvided = resolvedShares === externalServerKeyShares;
        // Refuse to export without verifying — exportKey returns the raw private
        // key, so a missing backupInfo on the caller-supplied branch would let
        // any password through. See sign() for the analogous guard.
        if (sharesProvided) {
            if (!backupInfo) {
                throw new Error('walletMetadata.externalServerKeySharesBackupInfo is required to verify the password ' + 'when exporting with caller-supplied externalServerKeyShares. Persist the full walletMetadata ' + 'returned by createWalletAccount/importPrivateKey and pass it back in. ' + 'fetchWalletMetadata returns identity-only metadata and cannot be used on this path.');
            }
            await this.verifyPassword({
                accountAddress,
                password,
                walletMetadata,
                backupInfo
            });
        }
        const walletId = walletMetadata.walletId;
        const derivationPathStr = walletMetadata.derivationPath;
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: this.baseMPCRelayApiUrl,
            bitcoinConfig
        });
        const exportId = await this.getExportId({
            chainName,
            serverKeyShare: resolvedShares[0],
            bitcoinConfig
        });
        const data = await this.apiClient.exportKey({
            walletId,
            exportId,
            bitcoinConfig,
            elevatedAccessToken
        });
        const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, resolvedShares[0], exportId);
        if (!keyExportRaw) {
            throw new Error('Error exporting private key');
        }
        const derivationPath = derivationPathStr && derivationPathStr != '' ? new Uint32Array(Object.values(JSON.parse(derivationPathStr))) : undefined;
        let derivedPrivateKey;
        if (mpcSigner instanceof Ecdsa) {
            derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
        } else if (mpcSigner instanceof ExportableEd25519) {
            derivedPrivateKey = keyExportRaw;
        } else if (mpcSigner instanceof BIP340) {
            derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
        }
        return {
            derivedPrivateKey
        };
    }
    async offlineExportKey({ chainName, keyShares, derivationPath }) {
        try {
            if (!keyShares || keyShares.length < 2) {
                throw new Error(`Must provide at least min threshold of key shares`);
            }
            const mpcSigner = getMPCSigner({
                chainName,
                baseRelayUrl: this.baseMPCRelayApiUrl
            });
            const walletKeyShares = keyShares.map((keyShare)=>{
                return mpcSigner instanceof Ecdsa ? new EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof ExportableEd25519 ? new ExportableEd25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
            });
            const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
            if (!keyExportRaw) {
                throw new Error('Error exporting private key: Export returned null');
            }
            const chainConfig = getMPCChainConfig(chainName);
            const walletDerivationPath = !derivationPath ? undefined : new Uint32Array(chainConfig.derivationPath);
            let derivedPrivateKey;
            if (mpcSigner instanceof Ecdsa) {
                derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
            } else if (mpcSigner instanceof ExportableEd25519) {
                derivedPrivateKey = keyExportRaw;
            } else if (mpcSigner instanceof BIP340) {
                derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
            }
            return {
                derivedPrivateKey
            };
        } catch (error) {
            this.logger.error('Error in offlineExportKey:', error);
            throw error;
        }
    }
    async encryptKeyShare({ keyShare, password }) {
        const serializedKeyShare = JSON.stringify(keyShare);
        const encryptedKeyShare = await encryptData({
            data: serializedKeyShare,
            password: password != null ? password : this.environmentId
        });
        // stringify the encrypted key share, convert to base64, and store it
        const serializedEncryptedKeyShare = Buffer.from(JSON.stringify(encryptedKeyShare)).toString('base64');
        return serializedEncryptedKeyShare;
    }
    async storeEncryptedBackupByWallet({ accountAddress, externalServerKeyShares = undefined, password = undefined, backUpToDynamic, walletMetadata, shareSetId }) {
        this.ensureApiClientAuthenticated();
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const resolvedShareSetId = shareSetId != null ? shareSetId : walletMetadata.shareSetId;
        const dynamicRequestId = v4();
        try {
            const keySharesToBackup = externalServerKeyShares != null ? externalServerKeyShares : await this.getExternalServerKeyShares({
                accountAddress,
                password,
                walletMetadata
            });
            if (!keySharesToBackup || keySharesToBackup.length === 0) {
                throw new Error('Key shares not found for the requested wallet.');
            }
            const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
                    keyShare,
                    password
                })));
            const { walletId, chainName, addressType } = walletMetadata;
            const passwordEncryptedFlag = Boolean(password) && password !== this.environmentId;
            const bitcoinConfig = chainName === 'BTC' && addressType ? {
                addressType: addressType
            } : undefined;
            // Build locations + result for all shares
            const locations = [];
            const result = [];
            // Upload first share to Dynamic if requested
            if (backUpToDynamic) {
                const keyShareToBackupToDynamic = keySharesToBackup[0];
                const data = await this.apiClient.storeEncryptedBackupByWallet({
                    walletId,
                    shareSetId: resolvedShareSetId,
                    encryptedKeyShares: [
                        encryptedKeyShares[0]
                    ],
                    passwordEncrypted: passwordEncryptedFlag,
                    encryptionVersion: ENCRYPTION_VERSION_CURRENT,
                    requiresSignedSessionId: false,
                    dynamicRequestId
                });
                const keygenId = await this.getExportId({
                    chainName,
                    serverKeyShare: keyShareToBackupToDynamic,
                    bitcoinConfig
                });
                locations.push({
                    location: BackupLocation.DYNAMIC,
                    // `id` and `externalKeyShareId` are the same server-assigned identifier
                    // for dynamic backups; populating both keeps the synthesized backupInfo
                    // shape identical to what `getExternalServerKeyShareBackupInfo` produces
                    // from the API response (used by fetchWalletMetadata / getWalletByAddress).
                    id: data.keyShareIds[0],
                    externalKeyShareId: data.keyShareIds[0],
                    passwordEncrypted: passwordEncryptedFlag,
                    keygenId
                });
                result.push({
                    share: keyShareToBackupToDynamic,
                    backedUpToClientKeyShareService: true
                });
            }
            const keySharesNotToBackupToDynamic = backUpToDynamic ? keySharesToBackup.slice(1) : keySharesToBackup;
            for (const keyShare of keySharesNotToBackupToDynamic){
                const keygenId = await this.getExportId({
                    chainName,
                    serverKeyShare: keyShare,
                    bitcoinConfig
                });
                locations.push({
                    location: BackupLocation.EXTERNAL,
                    passwordEncrypted: passwordEncryptedFlag,
                    keygenId
                });
                result.push({
                    share: keyShare,
                    backedUpToClientKeyShareService: false
                });
            }
            const markResponse = await this.apiClient.markKeySharesAsBackedUp({
                walletId,
                shareSetId: resolvedShareSetId,
                locations,
                dynamicRequestId
            });
            // Build backupInfo from the server response (`locationsWithKeyShares`) so
            // the cached backupInfo always agrees with what the server actually
            // stored — even if the server normalizes ids/keygenIds. The server
            // response does not include `passwordEncrypted` per location, so merge
            // the local `passwordEncryptedFlag` (a single boolean for this batch).
            // The fallback to local `locations` is defensive; warn loudly when it
            // fires so a server contract regression doesn't silently degrade.
            if (!(markResponse == null ? void 0 : markResponse.locationsWithKeyShares)) {
                this.logger.warn('markKeySharesAsBackedUp response missing locationsWithKeyShares; falling back to local synthesis. ' + 'This may indicate a server contract regression.');
            }
            var _markResponse_locationsWithKeyShares;
            const serverLocations = (_markResponse_locationsWithKeyShares = markResponse == null ? void 0 : markResponse.locationsWithKeyShares) != null ? _markResponse_locationsWithKeyShares : locations;
            const backupInfo = {
                backups: serverLocations.reduce((acc, loc)=>{
                    // Defensive: if the server returns an unrecognized location enum
                    // (e.g. a future BackupLocation we don't know yet), initialize
                    // the bucket rather than throwing on `acc[unknown].push(...)`.
                    if (!acc[loc.location]) {
                        acc[loc.location] = [];
                    }
                    acc[loc.location].push({
                        location: loc.location,
                        id: loc.id,
                        keygenId: loc.keygenId,
                        externalKeyShareId: loc.externalKeyShareId,
                        passwordEncrypted: passwordEncryptedFlag
                    });
                    return acc;
                }, {
                    [BackupLocation.DYNAMIC]: [],
                    [BackupLocation.GOOGLE_DRIVE]: [],
                    [BackupLocation.ICLOUD]: [],
                    [BackupLocation.USER]: [],
                    [BackupLocation.EXTERNAL]: [],
                    [BackupLocation.DELEGATED]: []
                }),
                passwordEncrypted: passwordEncryptedFlag
            };
            return {
                keySharesWithBackupStatus: result,
                backupInfo
            };
        } catch (error) {
            this.logger.error('Error in storeEncryptedBackupByWallet:', {
                accountAddress,
                error,
                dynamicRequestId
            });
            throw error;
        }
    }
    async storeEncryptedBackupByWalletWithRetry({ accountAddress, externalServerKeyShares, password, backUpToDynamic, walletMetadata }) {
        return retryPromise(()=>this.storeEncryptedBackupByWallet({
                accountAddress,
                externalServerKeyShares,
                password,
                backUpToDynamic,
                walletMetadata
            }), {
            operationName: 'store encrypted backup',
            logContext: {
                walletAddress: accountAddress
            }
        });
    }
    async getExternalServerKeyShares({ accountAddress, password, walletMetadata, backupInfo }) {
        var _resolvedBackupInfo_backups;
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const resolvedBackupInfo = backupInfo != null ? backupInfo : await this.resolveBackupInfo(walletMetadata);
        // Check if the wallet has a dynamic backup and derive password encryption status from the first dynamic share
        const dynamicBackups = (resolvedBackupInfo == null ? void 0 : (_resolvedBackupInfo_backups = resolvedBackupInfo.backups) == null ? void 0 : _resolvedBackupInfo_backups.dynamic) || [];
        const passwordEncrypted = dynamicBackups.length > 0 ? Boolean(dynamicBackups[0].passwordEncrypted) : false;
        if (passwordEncrypted && !password) {
            throw new Error('Password is required for decryption but not provided. This backup was encrypted with a password.');
        }
        // recover the shares
        const recoveredShares = await this.recoverEncryptedBackupByWallet({
            accountAddress,
            password,
            walletOperation: WalletOperation.REACH_THRESHOLD,
            walletMetadata,
            backupInfo: resolvedBackupInfo
        });
        return recoveredShares;
    }
    async updatePassword({ accountAddress, existingPassword, newPassword, backUpToDynamic, externalServerKeyShares, walletMetadata }) {
        this.validatePasswordForBackup({
            password: newPassword,
            backUpToDynamic
        });
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const sharesToBackup = await this.resolveKeyShares({
            accountAddress,
            password: existingPassword,
            walletOperation: WalletOperation.REACH_ALL_PARTIES,
            externalServerKeyShares,
            errorMessage: 'External server key shares are required to update password',
            walletMetadata
        });
        const { backupInfo } = await this.storeEncryptedBackupByWallet({
            accountAddress,
            password: newPassword,
            backUpToDynamic,
            externalServerKeyShares: sharesToBackup,
            walletMetadata
        });
        return {
            backupInfo
        };
    }
    async decryptKeyShare({ keyShare, password }) {
        const decodedKeyShare = JSON.parse(Buffer.from(keyShare, 'base64').toString());
        const decryptedKeyShare = await decryptData({
            data: decodedKeyShare,
            password: password != null ? password : this.environmentId
        });
        const deserializedKeyShare = JSON.parse(decryptedKeyShare);
        return deserializedKeyShare;
    }
    /**
   * Helper function to determine keyshare recovery strategy for dynamic shares.
   * For REFRESH operations, retrieves enough shares to meet the client threshold.
   * For all other operations, retrieves just 1 share.
   *
   * @param clientKeyShareBackupInfo - Information about backed up key shares
   * @param thresholdSignatureScheme - The signature scheme being used (2-of-2, 2-of-3, etc)
   * @param walletOperation - The operation being performed (REFRESH, SIGN_MESSAGE, etc)
   * @param shareCount - The number of shares to recover if specified for reshare operations
   * @returns @shares: Object mapping backup locations to arrays of share IDs to recover
   * @returns @requiredShareCount: The number of shares required to recover
   */ recoverStrategy({ externalServerKeySharesBackupInfo, thresholdSignatureScheme, walletOperation, shareCount = undefined }) {
        const { backups } = externalServerKeySharesBackupInfo;
        const { clientThreshold } = MPC_CONFIG[thresholdSignatureScheme];
        let requiredShareCount = walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.RESHARE ? clientThreshold : 1;
        // Override requiredShareCount if shareCount is provided
        if (shareCount !== undefined) {
            requiredShareCount = shareCount;
        }
        const dynamicShares = (backups[BackupLocation.DYNAMIC] || []).slice(0, requiredShareCount);
        const externalShares = (backups[BackupLocation.EXTERNAL] || []).slice(0, requiredShareCount);
        // If we have DYNAMIC shares, use those; otherwise use EXTERNAL shares
        const sharesToUse = dynamicShares.length > 0 ? dynamicShares : externalShares;
        const backupLocation = dynamicShares.length > 0 ? BackupLocation.DYNAMIC : BackupLocation.EXTERNAL;
        return {
            shares: {
                // EXTERNAL shares don't have externalKeyShareId (they're not uploaded to the API),
                // so we use their id for counting purposes instead of calling getRequiredExternalKeyShareId.
                [backupLocation]: backupLocation === BackupLocation.DYNAMIC ? sharesToUse.map(getRequiredExternalKeyShareId) : sharesToUse.map((ks)=>ks.id || '')
            },
            requiredShareCount
        };
    }
    /**
   * Resolves and returns key shares for wallet operations.
   * Checks in order: provided shares, then attempts recovery from backup.
   * @param accountAddress - The account address to get shares for
   * @param password - The password to decrypt the shares
   * @param walletOperation - The wallet operation being performed
   * @param externalServerKeyShares - The provided key shares (if any)
   * @param errorMessage - The error message to throw if no shares are found
   * @param walletMetadata - Required wallet metadata identifying the target wallet
   * @returns The resolved key shares
   * @throws Error if no shares are found and recovery fails
   */ async resolveKeyShares({ accountAddress, password, walletOperation, externalServerKeyShares, errorMessage, walletMetadata, backupInfo }) {
        if (externalServerKeyShares == null ? void 0 : externalServerKeyShares.length) {
            return externalServerKeyShares;
        }
        const recoveredShares = await this.recoverEncryptedBackupByWallet({
            accountAddress,
            password,
            walletOperation,
            walletMetadata,
            backupInfo
        });
        if (!recoveredShares || recoveredShares.length === 0) {
            throw new Error(errorMessage);
        }
        return recoveredShares;
    }
    async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, walletMetadata, backupInfo }) {
        this.ensureApiClientAuthenticated();
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const walletId = walletMetadata.walletId;
        const thresholdSignatureScheme = walletMetadata.thresholdSignatureScheme;
        const externalServerKeySharesBackupInfo = backupInfo != null ? backupInfo : await this.resolveBackupInfo(walletMetadata);
        // PII: intentionally omitting full wallet object (contains key share info)
        this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, {
            walletId
        });
        const { shares } = this.recoverStrategy({
            externalServerKeySharesBackupInfo,
            thresholdSignatureScheme,
            walletOperation,
            shareCount
        });
        const dynamicKeyShareIds = shares[BackupLocation.DYNAMIC] || [];
        if (dynamicKeyShareIds.length === 0) {
            this.logger.debug('No DYNAMIC shares available for recovery');
            return [];
        }
        const data = await this.apiClient.recoverEncryptedBackupByWallet({
            walletId,
            shareSetId: walletMetadata.shareSetId,
            externalKeyShareIds: dynamicKeyShareIds,
            requiresSignedSessionId: false
        });
        const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === BackupLocation.DYNAMIC);
        var _externalServerKeySharesBackupInfo_passwordEncrypted;
        const isPasswordEncrypted = (_externalServerKeySharesBackupInfo_passwordEncrypted = externalServerKeySharesBackupInfo == null ? void 0 : externalServerKeySharesBackupInfo.passwordEncrypted) != null ? _externalServerKeySharesBackupInfo_passwordEncrypted : false;
        if (isPasswordEncrypted && !password) {
            throw new Error('Password is required for decryption but not provided. This backup was encrypted with a password.');
        }
        const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
                keyShare: keyShare.encryptedAccountCredential,
                password: password != null ? password : this.environmentId
            })));
        return decryptedKeyShares;
    }
    async exportExternalServerKeyShares({ accountAddress, password, walletMetadata }) {
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const backupInfo = await this.resolveBackupInfo(walletMetadata);
        await this.verifyPassword({
            accountAddress,
            password,
            walletMetadata,
            backupInfo
        });
        const externalServerKeyShares = await this.getExternalServerKeyShares({
            accountAddress,
            password,
            walletMetadata,
            backupInfo
        });
        return externalServerKeyShares;
    }
    /**
   * verifyPassword attempts to recover and decrypt a single client key share using the provided password.
   * If successful, the key share is encrypted with the new password. This method solely performs the recovery
   * and decryption without storing the restored key shares. If unsuccessful, it throws an error.
   */ async verifyPassword({ accountAddress, password = undefined, walletMetadata, backupInfo }) {
        this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
        const resolvedBackupInfo = backupInfo != null ? backupInfo : await this.resolveBackupInfo(walletMetadata);
        if (await this.requiresPasswordForOperation({
            accountAddress,
            walletOperation: WalletOperation.NO_OPERATION,
            walletMetadata,
            backupInfo: resolvedBackupInfo
        }) && !password) {
            throw new Error('Password is required for operation but not provided');
        }
        // silent return if no password is provided and operation does not require a password
        if (!password) {
            return;
        }
        const { backups } = resolvedBackupInfo;
        const { dynamic: dynamicKeyShareIds = [] } = backups;
        const { external: externalKeyShareIds = [] } = backups;
        // Check if we have any shares available (DYNAMIC or EXTERNAL)
        if (dynamicKeyShareIds.length === 0 && externalKeyShareIds.length === 0) {
            throw new Error('No key shares found');
        }
        try {
            await this.recoverEncryptedBackupByWallet({
                accountAddress,
                password,
                walletOperation: WalletOperation.NO_OPERATION,
                walletMetadata,
                backupInfo: resolvedBackupInfo
            });
        } catch (error) {
            this.logger.error('Error in verifying password', error);
            // We can't distinguish wrong-password from stale-metadata without a
            // server-side backupInfo lookup (fetchWalletMetadata returns identity
            // only). Surface both possibilities so customers know where to look.
            throw new Error('Failed to verify password. Either the password is incorrect, or the cached `walletMetadata.externalServerKeySharesBackupInfo` is stale ' + '(merge the `backupInfo` returned from updatePassword/refreshWalletAccountShares/reshare into your cached walletMetadata).');
        }
    }
    async isPasswordEncrypted({ accountAddress, walletMetadata, backupInfo }) {
        const externalServerKeySharesBackupInfo = backupInfo != null ? backupInfo : await this.getWalletExternalServerKeyShareBackupInfo({
            walletMetadata
        });
        return externalServerKeySharesBackupInfo == null ? void 0 : externalServerKeySharesBackupInfo.passwordEncrypted;
    }
    /**
   * check if the operation requires a password
   */ async requiresPasswordForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, walletMetadata, backupInfo }) {
        const isEncrypted = await this.isPasswordEncrypted({
            accountAddress,
            walletMetadata,
            backupInfo
        });
        if (!isEncrypted) {
            return false;
        }
        return this.requiresRestoreBackupSharesForOperation({
            accountAddress,
            walletOperation,
            walletMetadata,
            backupInfo
        });
    }
    /**
   * check if the operation requires restoring backup shares
   */ async requiresRestoreBackupSharesForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, walletMetadata, backupInfo }) {
        const externalServerKeySharesBackupInfo = backupInfo != null ? backupInfo : await this.getWalletExternalServerKeyShareBackupInfo({
            walletMetadata
        });
        const thresholdSignatureScheme = walletMetadata.thresholdSignatureScheme;
        if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
            return true;
        }
        const { shares, requiredShareCount } = this.recoverStrategy({
            externalServerKeySharesBackupInfo,
            thresholdSignatureScheme,
            walletOperation
        });
        const dynamicKeyShareIds = shares[BackupLocation.DYNAMIC] || [];
        const externalKeyShareIds = shares[BackupLocation.EXTERNAL] || [];
        const totalAvailableShares = dynamicKeyShareIds.length + externalKeyShareIds.length;
        if (totalAvailableShares >= requiredShareCount) {
            return false;
        }
        return true;
    }
    async getWalletExternalServerKeyShareBackupInfo({ walletMetadata }) {
        this.ensureApiClientAuthenticated();
        return this.resolveBackupInfo(walletMetadata);
    }
    /**
   * Get a single wallet by address.
   * First tries the efficient `getWaasWalletByAddress` endpoint, falls back to
   * `getUser()` if not available.
   *
   * Note: the `getWaasWalletByAddress` endpoint returns wallet identity only
   * — it does not include keyShare pointer metadata, so the byAddress path
   * leaves `externalServerKeySharesBackupInfo` `undefined`. Operations that
   * need it (sign with auto-recovery, password verification, refresh,
   * reshare, updatePassword) will throw on a result obtained via this path.
   * The `getUser()` fallback populates `externalServerKeySharesBackupInfo`
   * from `walletProperties.keyShares`.
   *
   * Prefer `fetchWalletMetadata(accountAddress)` for identity-only lookups.
   */ async getWalletByAddress(accountAddress) {
        this.ensureApiClientAuthenticated();
        try {
            const walletResponse = await this.apiClient.getWaasWalletByAddress({
                walletAddress: accountAddress
            });
            return {
                walletId: walletResponse.wallet.walletId,
                chainName: walletResponse.wallet.chainName,
                accountAddress: walletResponse.wallet.accountAddress,
                externalServerKeyShares: [],
                derivationPath: walletResponse.wallet.derivationPath,
                thresholdSignatureScheme: walletResponse.wallet.thresholdSignatureScheme
            };
        } catch (error) {
            var _error_response;
            if ((error == null ? void 0 : (_error_response = error.response) == null ? void 0 : _error_response.status) === 404 || (error == null ? void 0 : error.code) === 'ERR_BAD_REQUEST') {
                var _user_verifiedCredentials, _wallet_walletProperties, _wallet_walletProperties1;
                this.logger.debug('getWaasWalletByAddress endpoint not available, falling back to getUser()');
                const user = await this.apiClient.getUser(v4());
                const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>{
                    var _vc_address;
                    return vc.walletName === 'dynamicwaas' && ((_vc_address = vc.address) == null ? void 0 : _vc_address.toLowerCase()) === accountAddress.toLowerCase();
                });
                if (!wallet) {
                    return null;
                }
                var _wallet_walletProperties_derivationPath;
                return {
                    walletId: wallet.id,
                    chainName: verifiedCredentialNameToChainEnum[wallet.chain],
                    accountAddress: wallet.address,
                    externalServerKeyShares: [],
                    derivationPath: (_wallet_walletProperties_derivationPath = (_wallet_walletProperties = wallet.walletProperties) == null ? void 0 : _wallet_walletProperties.derivationPath) != null ? _wallet_walletProperties_derivationPath : undefined,
                    thresholdSignatureScheme: (_wallet_walletProperties1 = wallet.walletProperties) == null ? void 0 : _wallet_walletProperties1.thresholdSignatureScheme,
                    externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
                        walletProperties: wallet.walletProperties || {}
                    })
                };
            }
            throw error;
        }
    }
    /**
   * Get all wallets (kept for backward compatibility but now uses lazy loading)
   * @deprecated Consider using getWalletByAddress for better performance with large wallet counts
   */ async getWallets() {
        this.logger.warn('getWallets() is deprecated. Use getWalletByAddress() for better performance with large wallet counts.');
        this.ensureApiClientAuthenticated();
        const user = await this.apiClient.getUser(v4());
        var _user_verifiedCredentials;
        const verifiedCredentials = (_user_verifiedCredentials = user.verifiedCredentials) != null ? _user_verifiedCredentials : [];
        const waasWallets = verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas' && !!vc.address && !!vc.chain);
        return waasWallets.map((vc)=>{
            var _vc_walletProperties, _vc_walletProperties1;
            var _vc_walletProperties_thresholdSignatureScheme;
            return {
                walletId: vc.id,
                chainName: verifiedCredentialNameToChainEnum[vc.chain],
                accountAddress: vc.address,
                externalServerKeySharesBackupInfo: getExternalServerKeyShareBackupInfo({
                    walletProperties: vc.walletProperties
                }),
                externalServerKeyShares: [],
                derivationPath: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.derivationPath,
                thresholdSignatureScheme: (_vc_walletProperties_thresholdSignatureScheme = (_vc_walletProperties1 = vc.walletProperties) == null ? void 0 : _vc_walletProperties1.thresholdSignatureScheme) != null ? _vc_walletProperties_thresholdSignatureScheme : ThresholdSignatureScheme.TWO_OF_TWO
            };
        });
    }
    constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug, forwardMPCClient, enableMPCAccelerator = true, logger }){
        this.isApiClientAuthenticated = false;
        this.forwardMPCEnabled = true;
        if (logger) {
            this.logger = logger;
        } else {
            const defaultLogger = new Logger$1('DynamicWaasWalletClient');
            defaultLogger.setLogLevel(debug ? 'DEBUG' : 'INFO');
            this.logger = defaultLogger;
        }
        Logger.configure(this.logger);
        this.environmentId = environmentId;
        this.baseApiUrl = baseApiUrl != null ? baseApiUrl : DYNAMIC_AUTH_PROD_BASE_API_URL;
        const environment = getEnvironmentFromUrl(baseApiUrl);
        this.baseMPCRelayApiUrl = baseMPCRelayApiUrl != null ? baseMPCRelayApiUrl : MPC_RELAY_URL_MAP[environment];
        this.debug = Boolean(debug);
        this.forwardMPCEnabled = enableMPCAccelerator || Boolean(forwardMPCClient);
        this.resolvedForwardMPCClient = forwardMPCClient != null ? forwardMPCClient : (()=>{
            if (!this.forwardMPCEnabled) return undefined;
            const environment = getEnvironmentFromUrl(baseApiUrl);
            const enclaveUrl = DYNAMIC_FORWARD_MPC_ENCLAVE_URL_MAP[environment];
            if (!enclaveUrl) throw new Error(`No ForwardMPC enclave URL configured for environment: ${environment}`);
            return new ForwardMPCClientV2(enclaveUrl, {
                reconnectInterval: 5000,
                reconnectAttempts: 5,
                connectionTimeout: 10000,
                attestationConfig: DYNAMIC_FORWARD_MPC_ENCLAVE_ATTESTATION_CONFIG_MAP[environment],
                logger: this.logger
            });
        })();
        // Initialize API client with forwardMPCClient
        this.apiClient = new DynamicApiClient({
            environmentId,
            baseApiUrl,
            forwardMPCClient: this.resolvedForwardMPCClient,
            logger: this.logger
        });
    }
}

const createCore = ({ environmentId, baseApiUrl, baseMPCRelayApiUrl, debug = false, logger })=>{
    let coreLogger;
    if (logger) {
        coreLogger = logger;
    } else {
        const defaultLogger = new Logger$1('DynamicWaasWalletClient');
        defaultLogger.setLogLevel(debug ? 'DEBUG' : 'INFO');
        coreLogger = defaultLogger;
    }
    Logger.configure(coreLogger);
    const createApiClient = (options = {})=>{
        return new DynamicApiClient(_extends({
            environmentId,
            baseApiUrl
        }, options, {
            logger: coreLogger
        }));
    };
    return {
        environmentId,
        createApiClient,
        logger: coreLogger,
        baseMPCRelayApiUrl,
        baseApiUrl,
        debug
    };
};

// Fresh apiClient per call — `walletApiKey` is set on this instance's
// headers, not on a shared bag, so concurrent callers can't race on it.
const createDelegatedApiClientWithWalletKey = (client, walletApiKey)=>{
    const apiClient = client.createApiClient();
    apiClient.apiClient.defaults.headers['x-dyn-wallet-api-key'] = walletApiKey;
    return apiClient;
};
const createDelegatedWalletClient = ({ environmentId, baseApiUrl, baseMPCRelayApiUrl, apiKey, debug = false })=>{
    const core = createCore({
        environmentId,
        baseApiUrl,
        baseMPCRelayApiUrl,
        debug
    });
    core.apiKey = apiKey;
    const client = {
        get environmentId () {
            return core.environmentId;
        },
        get debug () {
            return core.debug;
        },
        get apiUrl () {
            var _core_baseApiUrl;
            return (_core_baseApiUrl = core.baseApiUrl) != null ? _core_baseApiUrl : 'https://app.dynamicauth.com';
        },
        createApiClient: (options = {})=>{
            return core.createApiClient(_extends({
                authToken: apiKey
            }, options));
        },
        logger: core.logger,
        apiKey,
        baseMPCRelayApiUrl: core.baseMPCRelayApiUrl
    };
    return client;
};

const dynamicDelegatedSign = async (client, { walletId, shareSetId, message, isFormatted, walletApiKey, onError, context })=>{
    const dynamicRequestId = v4();
    // Convert message to hex if it's a Uint8Array
    if (typeof message !== 'string') {
        message = '0x' + Buffer.from(message).toString('hex');
    }
    const apiClient = createDelegatedApiClientWithWalletKey(client, walletApiKey);
    const data = await apiClient.delegatedSignMessage({
        walletId,
        shareSetId,
        message,
        isFormatted,
        dynamicRequestId,
        onError,
        context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined
    });
    return data;
};
const delegatedSign = async (client, { chainName, message, roomId, keyShare, derivationPath, isFormatted })=>{
    try {
        const mpcSigner = getMPCSigner({
            chainName,
            baseRelayUrl: client.baseMPCRelayApiUrl
        });
        const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
        const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
        return signature;
    } catch (error) {
        client.logger.error('Error in delegatedSign', error);
        throw error;
    }
};
const delegatedSignMessage = async (client, { walletId, shareSetId, walletApiKey, keyShare, message, chainName, derivationPath, isFormatted = false, onError, context })=>{
    if (!walletId) {
        throw new Error('walletId is required');
    }
    if (!chainName) {
        throw new Error('chainName is required');
    }
    if (!keyShare) {
        throw new Error('Delegated key share is required to sign a message');
    }
    if (!walletApiKey) {
        throw new Error('walletApiKey is required');
    }
    const data = await dynamicDelegatedSign(client, {
        walletId,
        shareSetId,
        walletApiKey,
        message,
        isFormatted,
        onError,
        context
    });
    // The wallet's pubkey was derived along the chain default at keygen,
    // so signing with anything else produces a non-verifying signature.
    const resolvedDerivationPath = derivationPath != null ? derivationPath : new Uint32Array(getMPCChainConfig(chainName).derivationPath);
    const signature = await delegatedSign(client, {
        chainName,
        message,
        roomId: data.roomId,
        keyShare,
        derivationPath: resolvedDerivationPath,
        isFormatted
    });
    return signature;
};

// TODO: wire to apiClient.revokeDelegation once the server method ships.
const revokeDelegation = async (client, { walletId })=>{
    if (!walletId) {
        throw new Error('walletId is required');
    }
    try {
        client.logger.info(`Delegation revoked for wallet: ${walletId}`);
    } catch (error) {
        client.logger.error('Error revoking delegation', error);
        throw new Error('Failed to revoke delegation');
    }
};

function decryptAesGcm(key, ivB64, ctB64, tagB64) {
    const iv = Buffer.from(ivB64, 'base64url');
    const ciphertext = Buffer.from(ctB64, 'base64url');
    const tag = Buffer.from(tagB64, 'base64url');
    const decipher = crypto$1.createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(tag);
    const updateResult = decipher.update(ciphertext);
    const finalResult = decipher.final();
    const plaintext = Buffer.concat([
        updateResult,
        finalResult
    ]);
    return plaintext;
}
function rsaOaepDecryptEk(privateKeyPem, ekB64) {
    return crypto$1.privateDecrypt({
        key: privateKeyPem,
        oaepHash: 'sha256',
        padding: crypto$1.constants.RSA_PKCS1_OAEP_PADDING
    }, Buffer.from(ekB64, 'base64url'));
}
function decryptDelegatedWebhookData({ privateKeyPem, encryptedDelegatedKeyShare, encryptedWalletApiKey }) {
    try {
        const shareKey = rsaOaepDecryptEk(privateKeyPem, encryptedDelegatedKeyShare.ek);
        const walletApiKeyKey = rsaOaepDecryptEk(privateKeyPem, encryptedWalletApiKey.ek);
        const decryptedDelegatedShare = decryptAesGcm(shareKey, encryptedDelegatedKeyShare.iv, encryptedDelegatedKeyShare.ct, encryptedDelegatedKeyShare.tag);
        const decryptedWalletApiKey = decryptAesGcm(walletApiKeyKey, encryptedWalletApiKey.iv, encryptedWalletApiKey.ct, encryptedWalletApiKey.tag);
        const decryptedWalletApiKeyString = decryptedWalletApiKey.toString('utf8');
        const decryptedDelegatedShareString = decryptedDelegatedShare.toString('utf8');
        const decryptedDelegatedShareObject = JSON.parse(decryptedDelegatedShareString);
        if (!decryptedDelegatedShareObject) {
            throw new Error('Failed to decrypt delegated share');
        }
        if (!decryptedWalletApiKeyString) {
            throw new Error('Failed to decrypt wallet API key');
        }
        return {
            decryptedDelegatedShare: decryptedDelegatedShareObject,
            decryptedWalletApiKey: decryptedWalletApiKeyString
        };
    } catch (error) {
        throw new Error('Failed to decrypt delegated webhook data: ' + error);
    }
}

export { DynamicWalletClient, base64ToBytes, bytesToBase64, createDelegatedWalletClient, createLogError, decryptDelegatedWebhookData, delegatedSignMessage, ensureBase64Padding, formatEvmMessage, formatMessage, getExternalServerKeyShareBackupInfo, getMPCSignatureScheme, getMPCSigner, isHexString, logError, mergeUniqueKeyShares, retryPromise, revokeDelegation, stringToBytes, stripHexPrefix };
