@dynamic-labs-wallet/browser 1.0.92 → 1.0.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
- import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, NETWORK_ERROR_STATUS, handleAxiosError, WalletOperation, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
1
+ import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, DEFAULT_ED25519_VARIANT, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, NETWORK_ERROR_STATUS, handleAxiosError, WalletOperation, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
2
2
  export * from '@dynamic-labs-wallet/core';
3
3
  export { Logger } from '@dynamic-labs-wallet/core';
4
- import { EdBls12377, BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/web';
4
+ import { EdBls12377, BIP340, Ed25519, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, Ed25519KeygenResult, BIP340KeygenResult } from '#internal/web';
5
5
  export { BIP340, BIP340InitKeygenResult, BIP340KeygenResult, Ecdsa, EcdsaInitKeygenResult, EcdsaKeygenResult, EcdsaPublicKey, EcdsaSignature, Ed25519, Ed25519InitKeygenResult, Ed25519KeygenResult, EdBls12377, EdBls12377KeygenResult, MessageHash } from '#internal/web';
6
6
  import { v4, validate } from 'uuid';
7
7
  import { SigningAlgorithm } from '@dynamic-labs-wallet/primitives';
@@ -10,6 +10,8 @@ import loadArgon2idWasm from 'argon2id';
10
10
  import { AxiosError } from 'axios';
11
11
  import gte from 'semver/functions/gte';
12
12
  import PQueue from 'p-queue';
13
+ import { ed25519 } from '@noble/curves/ed25519';
14
+ import { hexToBytes, bytesToNumberLE } from '@noble/curves/abstract/utils';
13
15
 
14
16
  function _extends() {
15
17
  _extends = Object.assign || function assign(target) {
@@ -79,12 +81,17 @@ function _extends() {
79
81
  return undefined;
80
82
  }
81
83
  };
82
- const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
84
+ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL, variant = DEFAULT_ED25519_VARIANT })=>{
83
85
  switch(signingAlgorithm){
84
86
  case SigningAlgorithm.ECDSA:
85
87
  return new Ecdsa(baseRelayUrl);
86
88
  case SigningAlgorithm.ED25519:
87
- return new ExportableEd25519(baseRelayUrl);
89
+ // `Ed25519` (non-exportable) and `ExportableEd25519` are distinct protocol
90
+ // classes with incompatible keyshare formats. Downstream `instanceof`
91
+ // dispatch (createWalletAccount / importPrivateKey / derive*) always tests
92
+ // `ExportableEd25519` before `Ed25519` — most-specific-first — so it stays
93
+ // correct even if `ExportableEd25519` were a subclass of `Ed25519`.
94
+ return variant === 'ed25519Standard' ? new Ed25519(baseRelayUrl) : new ExportableEd25519(baseRelayUrl);
88
95
  case SigningAlgorithm.BIP340:
89
96
  return new BIP340(baseRelayUrl);
90
97
  case SigningAlgorithm.EDBLS12_377:
@@ -93,11 +100,12 @@ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD
93
100
  throw new Error(`Unsupported signing algorithm: ${signingAlgorithm}`);
94
101
  }
95
102
  };
96
- const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig })=>{
103
+ const getMPCSigner = ({ chainName, baseRelayUrl, bitcoinConfig, variant })=>{
97
104
  const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
98
105
  const signatureScheme = getMPCSignatureScheme({
99
106
  signingAlgorithm: chainConfig.signingAlgorithm,
100
- baseRelayUrl
107
+ baseRelayUrl,
108
+ variant
101
109
  });
102
110
  return signatureScheme;
103
111
  };
@@ -1487,6 +1495,37 @@ const NON_RETRYABLE_CEREMONY_ERROR_MESSAGES = [
1487
1495
  ERROR_INCORRECT_PASSWORD,
1488
1496
  ERROR_MULTIPLE_WALLETS_PER_CHAIN
1489
1497
  ];
1498
+ /**
1499
+ * True when the backend rejected a create because the user already has a wallet
1500
+ * on that chain — an expected business-rule outcome, not a system failure.
1501
+ *
1502
+ * The string never appears on `error.message`: the backend returns it in the 400
1503
+ * response body as `{ existingWalletId, error: '<this string>' }`, so an
1504
+ * `AxiosError` carries axios's own "Request failed with status code 400" message
1505
+ * and `handleAxiosError` maps the status to "Invalid request". `WalletApiError`
1506
+ * does preserve the body value, but on `code` (via `extractServerErrorCode`),
1507
+ * not `message`. A `.message` comparison therefore never matches, which is why
1508
+ * the intended downgrade at the keyGen catch site had never fired in production.
1509
+ *
1510
+ * `WalletApiError` is matched on `name` as well as `instanceof`, for the reason
1511
+ * documented on `getWalletApiErrorStatus` in services/logger.ts: core is inlined
1512
+ * into more than one bundle, so a second copy of the class defeats `instanceof`.
1513
+ * A foreign copy has no `response.data` and the generic "Invalid request"
1514
+ * message, so an instanceof-only check misses every branch below.
1515
+ */ const isMultipleWalletsPerChainRejection = (error)=>{
1516
+ var _error_response;
1517
+ const isWalletApiError = error instanceof WalletApiError || error instanceof Error && error.name === 'WalletApiError';
1518
+ if (isWalletApiError && error.code === ERROR_MULTIPLE_WALLETS_PER_CHAIN) return true;
1519
+ // Raw AxiosError: the rejection reaches the keyGen catch before
1520
+ // `handleAxiosError` has converted it, so read the response body directly.
1521
+ const body = error == null ? void 0 : (_error_response = error.response) == null ? void 0 : _error_response.data;
1522
+ if (body && typeof body === 'object' && body['error'] === ERROR_MULTIPLE_WALLETS_PER_CHAIN) {
1523
+ return true;
1524
+ }
1525
+ // SSE transport assigns the body onto a plain Error.
1526
+ if ((error == null ? void 0 : error.error) === ERROR_MULTIPLE_WALLETS_PER_CHAIN) return true;
1527
+ return error instanceof Error && error.message === ERROR_MULTIPLE_WALLETS_PER_CHAIN;
1528
+ };
1490
1529
  /**
1491
1530
  * Marks an error as non-retryable in place so `retryPromise` will skip
1492
1531
  * remaining attempts. Used at ceremony retry boundaries.
@@ -2282,14 +2321,33 @@ const classifyOne = (error)=>{
2282
2321
  return fallback;
2283
2322
  };
2284
2323
  const MAX_DESCRIBED_LENGTH = 200;
2324
+ /**
2325
+ * Keys safe to echo when a thrown object exposes no recognised message field.
2326
+ * Deliberately an allowlist: the alternative — serialising the whole object —
2327
+ * cannot be made safe, because a thrown MPC signer rejection or API body may
2328
+ * carry key-share material, a raw transaction body or an auth token.
2329
+ */ const SAFE_DETAIL_KEYS = [
2330
+ 'status',
2331
+ 'statusCode',
2332
+ 'code',
2333
+ 'error_code',
2334
+ 'errorCode',
2335
+ 'name',
2336
+ 'type'
2337
+ ];
2338
+ const isScalar = (value)=>typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
2285
2339
  /**
2286
2340
  * Builds a human-readable message for a thrown value that is not an `Error` —
2287
2341
  * most often an API response body, e.g.
2288
2342
  * `{ existingWalletId, error: 'Multiple wallets per chain not allowed' }`.
2289
2343
  *
2290
2344
  * `String(value)` would collapse that to `[object Object]`, losing the only
2291
- * diagnostic content it had, so prefer a recognisable message field and fall
2292
- * back to bounded JSON.
2345
+ * diagnostic content it had, so a recognised message field is preferred.
2346
+ *
2347
+ * Never serialises an unrecognised object wholesale. Where no message field
2348
+ * exists, only `SAFE_DETAIL_KEYS` scalars are echoed, and failing that a fixed
2349
+ * placeholder — so a value carrying secrets can never reach the logging backend
2350
+ * just because it lacked a `message`.
2293
2351
  */ const describeNonError = (value)=>{
2294
2352
  if (typeof value === 'string') return value.slice(0, MAX_DESCRIBED_LENGTH);
2295
2353
  if (value !== null && typeof value === 'object') {
@@ -2300,11 +2358,8 @@ const MAX_DESCRIBED_LENGTH = 200;
2300
2358
  record['detail']
2301
2359
  ].find((candidate)=>typeof candidate === 'string' && candidate.length > 0);
2302
2360
  if (named) return named.slice(0, MAX_DESCRIBED_LENGTH);
2303
- try {
2304
- return JSON.stringify(value).slice(0, MAX_DESCRIBED_LENGTH);
2305
- } catch (e) {
2306
- // Circular or non-serialisable — fall through to String().
2307
- }
2361
+ const safeDetails = SAFE_DETAIL_KEYS.filter((key)=>isScalar(record[key])).map((key)=>`${key}=${String(record[key])}`);
2362
+ return safeDetails.length > 0 ? safeDetails.join(' ').slice(0, MAX_DESCRIBED_LENGTH) : 'Non-Error value thrown with no recognised message field';
2308
2363
  }
2309
2364
  return String(value).slice(0, MAX_DESCRIBED_LENGTH);
2310
2365
  };
@@ -2320,27 +2375,38 @@ const MAX_DESCRIBED_LENGTH = 200;
2320
2375
  * under `operationError` instead, and a real `Error` is synthesised so the log
2321
2376
  * still gets a stack and an `error.kind`.
2322
2377
  *
2378
+ * A non-`Error` is recorded as its bounded `describeNonError` description only,
2379
+ * never the raw value — the same rule `logError` applies when it logs a cause as
2380
+ * `{ name, message }` — while `operationError` remains its own attribute so "the
2381
+ * thrown value was not an Error" stays queryable.
2382
+ *
2323
2383
  * Returned as a plain object so this is unit-testable without constructing a
2324
2384
  * wallet client (which pulls the MPC WASM bundle).
2325
2385
  */ const buildOperationFailureLog = (error)=>{
2326
2386
  // Classify the original value: an Error's `cause` chain carries the severity,
2327
2387
  // and normalising first would discard it.
2328
2388
  const level = resolveLogLevel(error, 'error');
2329
- return error instanceof Error ? {
2389
+ if (error instanceof Error) return {
2330
2390
  level,
2331
2391
  context: undefined,
2332
2392
  error
2333
- } : {
2393
+ };
2394
+ const described = describeNonError(error);
2395
+ return {
2334
2396
  level,
2335
2397
  context: {
2336
- operationError: error
2398
+ operationError: described
2337
2399
  },
2338
- error: new Error(describeNonError(error))
2400
+ error: new Error(described)
2339
2401
  };
2340
2402
  };
2341
2403
  const logError = ({ message, error, context, level = 'error' })=>{
2342
2404
  if (error instanceof AxiosError) {
2343
- handleAxiosError(error, message, context);
2405
+ // Forward the caller's severity hint. It used to be dropped here, so a call
2406
+ // site that knew its failure was expected still got logged at `error`.
2407
+ handleAxiosError(error, message, context, {
2408
+ level
2409
+ });
2344
2410
  return;
2345
2411
  }
2346
2412
  const resolvedLevel = resolveLogLevel(error, level);
@@ -3378,28 +3444,34 @@ class DynamicWalletClient {
3378
3444
  }, this.getTraceContext(traceContext))));
3379
3445
  throw new Error('Timed out waiting for wallet creation ceremony to complete');
3380
3446
  }
3381
- async clientInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig }) {
3447
+ async clientInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig, variant }) {
3382
3448
  // Get the mpc signer
3383
3449
  const mpcSigner = getMPCSigner({
3384
3450
  chainName,
3385
3451
  baseRelayUrl: this.baseMPCRelayApiUrl,
3386
- bitcoinConfig
3452
+ bitcoinConfig,
3453
+ variant
3387
3454
  });
3388
3455
  const clientThreshold = getClientThreshold(thresholdSignatureScheme);
3389
3456
  const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
3390
3457
  return keygenInitResults;
3391
3458
  }
3392
- async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig }) {
3459
+ async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig, variant }) {
3393
3460
  const mpcSigner = getMPCSigner({
3394
3461
  chainName,
3395
3462
  baseRelayUrl: this.baseMPCRelayApiUrl,
3396
- bitcoinConfig
3463
+ bitcoinConfig,
3464
+ variant
3397
3465
  });
3398
3466
  let publicKey;
3399
3467
  if (mpcSigner instanceof Ecdsa) {
3400
3468
  publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
3401
3469
  } else if (mpcSigner instanceof ExportableEd25519) {
3402
3470
  publicKey = await mpcSigner.getPubkey(keyShare);
3471
+ } else if (mpcSigner instanceof Ed25519) {
3472
+ // Non-exportable Ed25519 (raw-scalar-imported wallets). Derivation is not
3473
+ // standardized for ed25519, so the root pubkey is taken with an undefined path.
3474
+ publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
3403
3475
  } else if (mpcSigner instanceof BIP340) {
3404
3476
  publicKey = await mpcSigner.deriveTweakPubkey(keyShare, derivationPath);
3405
3477
  }
@@ -3728,7 +3800,7 @@ class DynamicWalletClient {
3728
3800
  // rejection (the user already has a wallet for this chain), not a system
3729
3801
  // failure — log it at warn so it doesn't flood error dashboards. All
3730
3802
  // other keygen failures stay at error.
3731
- const isExpectedRejection = error instanceof Error && error.message === ERROR_MULTIPLE_WALLETS_PER_CHAIN;
3803
+ const isExpectedRejection = isMultipleWalletsPerChainRejection(error);
3732
3804
  // On the HTTP transport, tag the failure with the cross-repo join keys and
3733
3805
  // a stable errorKind so it lines up with the redcoast worker ceremony logs.
3734
3806
  const usedHttpTransport = this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && !!idempotencyKey;
@@ -3763,7 +3835,7 @@ class DynamicWalletClient {
3763
3835
  shouldRetry: (error)=>!isNonRetryableCeremonyError(error)
3764
3836
  });
3765
3837
  }
3766
- async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId }) {
3838
+ async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId, isRawScalarImport }) {
3767
3839
  const dynamicRequestId = v4();
3768
3840
  try {
3769
3841
  this.assertPasswordRequired(password);
@@ -3771,15 +3843,21 @@ class DynamicWalletClient {
3771
3843
  password,
3772
3844
  signedSessionId
3773
3845
  });
3846
+ // A raw-scalar import must run on the non-exportable `Ed25519` protocol for
3847
+ // the whole ceremony (init keygen, import, pubkey derivation) so the produced
3848
+ // key shares are consistent. Non-raw imports keep the default ('ed25519Exportable').
3849
+ const variant = isRawScalarImport ? 'ed25519Standard' : undefined;
3774
3850
  const mpcSigner = getMPCSigner({
3775
3851
  chainName,
3776
3852
  baseRelayUrl: this.baseMPCRelayApiUrl,
3777
- bitcoinConfig
3853
+ bitcoinConfig,
3854
+ variant
3778
3855
  });
3779
3856
  const clientKeygenInitResults = await this.clientInitializeKeyGen({
3780
3857
  chainName,
3781
3858
  thresholdSignatureScheme,
3782
- bitcoinConfig
3859
+ bitcoinConfig,
3860
+ variant
3783
3861
  });
3784
3862
  const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
3785
3863
  this.logger.info('[DynamicWaasWalletClient] Client key generation initialized', _extends({
@@ -3797,7 +3875,9 @@ class DynamicWalletClient {
3797
3875
  bitcoinConfig,
3798
3876
  onError,
3799
3877
  onCeremonyComplete,
3800
- legacyWalletId
3878
+ legacyWalletId,
3879
+ // Persist the protocol variant server-side so later sign/reshare read it back.
3880
+ variant
3801
3881
  });
3802
3882
  this.logger.info('[DynamicWaasWalletClient] Server key generation initialized', _extends({
3803
3883
  roomId,
@@ -3815,7 +3895,9 @@ class DynamicWalletClient {
3815
3895
  ...serverKeygenIds,
3816
3896
  ...otherClientKeygenIds
3817
3897
  ];
3818
- const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
3898
+ const importerKeygenResult = isRawScalarImport ? // uses the 32 bytes directly as the signing scalar instead of
3899
+ // SHA-512-expanding them as an RFC-8032 seed.
3900
+ await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds, true) : await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
3819
3901
  return importerKeygenResult;
3820
3902
  } else {
3821
3903
  const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
@@ -3832,7 +3914,8 @@ class DynamicWalletClient {
3832
3914
  chainName,
3833
3915
  keyShare: clientKeygenResult,
3834
3916
  derivationPath,
3835
- bitcoinConfig
3917
+ bitcoinConfig,
3918
+ variant
3836
3919
  });
3837
3920
  this.logger.info('[DynamicWaasWalletClient] Completed import of raw private key', _extends({
3838
3921
  rawPublicKey,
@@ -3867,6 +3950,8 @@ class DynamicWalletClient {
3867
3950
  if (typeof message !== 'string') {
3868
3951
  message = `0x${Buffer.from(message).toString('hex')}`;
3869
3952
  }
3953
+ // Note: the server signing party (wallet-service) selects the ed25519
3954
+ // protocol variant from its own persisted EAC, so it is NOT threaded here.
3870
3955
  const serializedContext = context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined;
3871
3956
  const useHttpTransport = this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT] === true && this.featureFlags[FEATURE_FLAGS.ENABLE_HTTP_WAAS_TRANSPORT_SIGNMESSAGE] === true;
3872
3957
  const params = {
@@ -3930,7 +4015,7 @@ class DynamicWalletClient {
3930
4015
  }
3931
4016
  return signatureBytes;
3932
4017
  }
3933
- async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext, bitcoinConfig }) {
4018
+ async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext, bitcoinConfig, variant }) {
3934
4019
  try {
3935
4020
  const chainConfig = getMPCChainConfig(chainName, bitcoinConfig);
3936
4021
  const signingAlgo = chainConfig.signingAlgorithm;
@@ -3943,7 +4028,7 @@ class DynamicWalletClient {
3943
4028
  });
3944
4029
  const tweak = this.convertTweakForBIP340(bitcoinConfig == null ? void 0 : bitcoinConfig.tweak);
3945
4030
  this.logger.info('Forward MPC enabled, signing message with forward MPC (new)', this.getTraceContext(traceContext));
3946
- const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage({
4031
+ const { signature: signatureBytes } = await this.apiClient.forwardMPCClient.signMessage(_extends({
3947
4032
  keyshare: keyShare,
3948
4033
  message: messageToSign,
3949
4034
  relayDomain: this.baseMPCRelayApiUrl || '',
@@ -3955,7 +4040,9 @@ class DynamicWalletClient {
3955
4040
  traceContext,
3956
4041
  userId: this.userId,
3957
4042
  environmentId: this.environmentId
3958
- });
4043
+ }, variant === 'ed25519Standard' ? {
4044
+ ed25519Variant: 'standard'
4045
+ } : {}));
3959
4046
  if (!(signatureBytes instanceof Uint8Array)) {
3960
4047
  throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
3961
4048
  }
@@ -3975,15 +4062,22 @@ class DynamicWalletClient {
3975
4062
  throw error;
3976
4063
  }
3977
4064
  }
3978
- async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, refreshRoom }) {
4065
+ async clientSign({ chainName, message, roomId: initialRoomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext, bitcoinConfig, variant, refreshRoom }) {
3979
4066
  // Reassigned when forward MPC fails and we mint a fresh room for the
3980
4067
  // relay-based fallback below.
3981
4068
  let roomId = initialRoomId;
4069
+ // Non-exportable Ed25519 (raw-scalar import) derives its address at the
4070
+ // root — see derivePrivateKeyFromExport's identical guard. Unlike
4071
+ // ExportableEd25519 (which ignores this arg), Ed25519.sign() *applies* a
4072
+ // non-undefined path, so forwarding the wallet's stored derivation path
4073
+ // here would sign under a derived child key instead of the wallet's own.
4074
+ const effectiveDerivationPath = variant === 'ed25519Standard' ? undefined : derivationPath;
3982
4075
  try {
3983
4076
  const mpcSigner = getMPCSigner({
3984
4077
  chainName,
3985
4078
  baseRelayUrl: this.baseMPCRelayApiUrl,
3986
- bitcoinConfig
4079
+ bitcoinConfig,
4080
+ variant
3987
4081
  });
3988
4082
  const formattedMessage = isFormatted ? new MessageHash(message) : formatMessage(chainName, message);
3989
4083
  this.logger.debug('[DynamicWaasWalletClient] Starting client sign', _extends({
@@ -4007,12 +4101,13 @@ class DynamicWalletClient {
4007
4101
  message,
4008
4102
  roomId,
4009
4103
  keyShare,
4010
- derivationPath,
4104
+ derivationPath: effectiveDerivationPath,
4011
4105
  formattedMessage,
4012
4106
  dynamicRequestId,
4013
4107
  isFormatted,
4014
4108
  traceContext,
4015
- bitcoinConfig
4109
+ bitcoinConfig,
4110
+ variant
4016
4111
  });
4017
4112
  } catch (error) {
4018
4113
  const errorInfo = classifyForwardMpcError(error);
@@ -4056,7 +4151,7 @@ class DynamicWalletClient {
4056
4151
  if (!('sign' in mpcSigner)) {
4057
4152
  throw new TypeError(`Message signing is not supported for chain ${chainName} — use signTransaction instead`);
4058
4153
  }
4059
- const signature = await mpcSigner.sign(roomId, keyShare, messageToSign, derivationPath, tweak);
4154
+ const signature = await mpcSigner.sign(roomId, keyShare, messageToSign, effectiveDerivationPath, tweak);
4060
4155
  return signature;
4061
4156
  } catch (error) {
4062
4157
  logError({
@@ -4140,7 +4235,8 @@ class DynamicWalletClient {
4140
4235
  chainName,
4141
4236
  localShares,
4142
4237
  recordedKeygenIds,
4143
- bitcoinConfig
4238
+ bitcoinConfig,
4239
+ variant: this.ed25519VariantForWallet(accountAddress)
4144
4240
  });
4145
4241
  localKeygenIds = comparison.localKeygenIds;
4146
4242
  staleness = comparison.fresh ? 'fresh' : 'stale';
@@ -4206,11 +4302,14 @@ class DynamicWalletClient {
4206
4302
  * the server recorded for the wallet's Dynamic backups. Shared by the
4207
4303
  * stale-share heal (verifyAndRecoverStaleShare) and the backup preflight
4208
4304
  * (ensureLocalSharesAreFresh).
4209
- */ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig }) {
4305
+ */ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig, variant }) {
4210
4306
  const mpcSigner = getMPCSigner({
4211
4307
  chainName,
4212
4308
  baseRelayUrl: this.baseMPCRelayApiUrl,
4213
- bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress)
4309
+ bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress),
4310
+ // Raw-scalar (non-exportable Ed25519) wallets: build the matching signer so
4311
+ // getKeygenIdForShare deserializes the Ed25519 share (else "Invalid discriminant").
4312
+ variant
4214
4313
  });
4215
4314
  const localKeygenIds = await Promise.all(localShares.map((share)=>this.getKeygenIdForShare(mpcSigner, share)));
4216
4315
  const recordedSet = new Set(recordedKeygenIds);
@@ -4368,6 +4467,7 @@ class DynamicWalletClient {
4368
4467
  dynamicRequestId,
4369
4468
  traceContext,
4370
4469
  bitcoinConfig,
4470
+ variant: wallet.variant,
4371
4471
  // On forward-MPC fallback, re-run the server sign with no roomId so a
4372
4472
  // brand-new server-joined room is created for the relay ceremony.
4373
4473
  refreshRoom: async ()=>{
@@ -4667,7 +4767,11 @@ class DynamicWalletClient {
4667
4767
  const mpcSigner = getMPCSigner({
4668
4768
  chainName,
4669
4769
  baseRelayUrl: this.baseMPCRelayApiUrl,
4670
- bitcoinConfig
4770
+ bitcoinConfig,
4771
+ // Raw-scalar (non-exportable Ed25519) wallets must refresh on the same
4772
+ // protocol their shares were created with, or the ceremony fails to
4773
+ // deserialize them ("Invalid discriminant").
4774
+ variant: this.ed25519VariantForWallet(accountAddress)
4671
4775
  });
4672
4776
  // Ensure client key shares exist before hitting the API
4673
4777
  const clientKeyShares = await this.ensureClientShare(accountAddress);
@@ -4849,11 +4953,26 @@ class DynamicWalletClient {
4849
4953
  }
4850
4954
  return mpcSigner.exportID(share);
4851
4955
  }
4852
- async getExportId({ chainName, clientKeyShare, bitcoinConfig }) {
4956
+ /**
4957
+ * The ed25519 protocol variant for an existing wallet, read from the wallet map
4958
+ * (sourced from `walletProperties.settings.variant`, persisted at import).
4959
+ *
4960
+ * It is per-wallet protocol metadata — like `thresholdSignatureScheme` or
4961
+ * `addressType` — and the single source of truth for which signer to build when
4962
+ * operating on a wallet's key shares. Wallets imported from a raw ed25519 scalar
4963
+ * (e.g. Fireblocks Embedded Wallets) are `'ed25519Standard'` (non-exportable `Ed25519`);
4964
+ * everything else is `undefined` → the default exportable protocol. Using the
4965
+ * wrong one fails to deserialize the key share with "Invalid discriminant".
4966
+ */ ed25519VariantForWallet(accountAddress) {
4967
+ var _this_getWalletFromMap;
4968
+ return (_this_getWalletFromMap = this.getWalletFromMap(accountAddress)) == null ? void 0 : _this_getWalletFromMap.variant;
4969
+ }
4970
+ async getExportId({ chainName, clientKeyShare, bitcoinConfig, accountAddress }) {
4853
4971
  const mpcSigner = getMPCSigner({
4854
4972
  chainName,
4855
4973
  baseRelayUrl: this.baseMPCRelayApiUrl,
4856
- bitcoinConfig
4974
+ bitcoinConfig,
4975
+ variant: accountAddress ? this.ed25519VariantForWallet(accountAddress) : undefined
4857
4976
  });
4858
4977
  try {
4859
4978
  const exportId = await this.getKeygenIdForShare(mpcSigner, clientKeyShare);
@@ -4901,7 +5020,8 @@ class DynamicWalletClient {
4901
5020
  return this.getExportId({
4902
5021
  chainName,
4903
5022
  clientKeyShare,
4904
- bitcoinConfig
5023
+ bitcoinConfig,
5024
+ accountAddress
4905
5025
  });
4906
5026
  }
4907
5027
  /**
@@ -4942,7 +5062,8 @@ class DynamicWalletClient {
4942
5062
  const existingClientKeygenIds = await Promise.all(existingClientKeyShares.map(async (keyShare)=>await this.getExportId({
4943
5063
  chainName,
4944
5064
  clientKeyShare: keyShare,
4945
- bitcoinConfig
5065
+ bitcoinConfig,
5066
+ accountAddress
4946
5067
  })));
4947
5068
  return {
4948
5069
  newClientInitKeygenResults,
@@ -5011,7 +5132,8 @@ class DynamicWalletClient {
5011
5132
  const existingClientKeygenId = await this.getExportId({
5012
5133
  chainName,
5013
5134
  clientKeyShare: existingClientShare,
5014
- bitcoinConfig
5135
+ bitcoinConfig,
5136
+ accountAddress
5015
5137
  });
5016
5138
  // Gate matches internalRefresh/Reshare — SSE error rejects awaitTerminal.
5017
5139
  let newShareSetId;
@@ -5620,7 +5742,11 @@ class DynamicWalletClient {
5620
5742
  const mpcSigner = getMPCSigner({
5621
5743
  chainName,
5622
5744
  baseRelayUrl: this.baseMPCRelayApiUrl,
5623
- bitcoinConfig
5745
+ bitcoinConfig,
5746
+ // Raw-scalar (non-exportable Ed25519) wallets must reshare on the same
5747
+ // protocol their shares were created with, or the ceremony fails to
5748
+ // deserialize them ("Invalid discriminant").
5749
+ variant: this.ed25519VariantForWallet(accountAddress)
5624
5750
  });
5625
5751
  let existingReshareResults;
5626
5752
  let newReshareResults;
@@ -5984,6 +6110,9 @@ class DynamicWalletClient {
5984
6110
  if (mpcSigner instanceof ExportableEd25519) {
5985
6111
  return new ExportableEd25519KeygenResult(extractedPubkey, secretShare);
5986
6112
  }
6113
+ if (mpcSigner instanceof Ed25519) {
6114
+ return new Ed25519KeygenResult(extractedPubkey, secretShare);
6115
+ }
5987
6116
  return new BIP340KeygenResult(extractedPubkey, secretShare);
5988
6117
  }
5989
6118
  async exportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext }) {
@@ -6014,13 +6143,15 @@ class DynamicWalletClient {
6014
6143
  const mpcSigner = getMPCSigner({
6015
6144
  chainName,
6016
6145
  baseRelayUrl: this.baseMPCRelayApiUrl,
6017
- bitcoinConfig
6146
+ bitcoinConfig,
6147
+ variant: wallet.variant
6018
6148
  });
6019
6149
  const reconstructedKeyShare = await this.getReconstructedKeyShare(accountAddress, mpcSigner);
6020
6150
  const exportId = await this.getExportId({
6021
6151
  chainName,
6022
6152
  clientKeyShare: reconstructedKeyShare,
6023
- bitcoinConfig
6153
+ bitcoinConfig,
6154
+ accountAddress: wallet.accountAddress
6024
6155
  });
6025
6156
  const data = await this.apiClient.exportKey({
6026
6157
  walletId: wallet.walletId,
@@ -6163,20 +6294,35 @@ class DynamicWalletClient {
6163
6294
  if (mpcSigner instanceof Ecdsa) {
6164
6295
  return mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
6165
6296
  } else if (mpcSigner instanceof ExportableEd25519) {
6297
+ // ExportableEd25519's exportFullPrivateKey already returns the RFC-8032
6298
+ // form (hex of seed||pubkey); pass it straight through.
6166
6299
  return keyExportRaw;
6300
+ } else if (mpcSigner instanceof Ed25519) {
6301
+ // Non-exportable Ed25519's exportFullPrivateKey returns an `spriv`
6302
+ // (extended private key), NOT a raw hex key — it must be parsed to the
6303
+ // actual private scalar. Returning the spriv verbatim makes the chain
6304
+ // client hex-decode it into an empty buffer (blank export field).
6305
+ //
6306
+ // This protocol is only used for raw-scalar import, whose public key is
6307
+ // the ROOT scalar (`A = s·B`, derived at import via derivePubkey(_, undefined)).
6308
+ // The chain-config derivation path stored on the wallet (e.g. Solana's
6309
+ // [44,501,0,0,0]) must NOT be applied here — deriving a child scalar would
6310
+ // export a key that doesn't correspond to the wallet's address.
6311
+ return mpcSigner.derivePrivateKeyFromSpriv(keyExportRaw);
6167
6312
  } else if (mpcSigner instanceof BIP340) {
6168
6313
  return mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
6169
6314
  }
6170
6315
  return undefined;
6171
6316
  }
6172
- async offlineExportKey({ chainName, keyShares, derivationPath }) {
6317
+ async offlineExportKey({ chainName, keyShares, derivationPath, variant }) {
6173
6318
  try {
6174
6319
  if (!keyShares || keyShares.length < 2) {
6175
6320
  throw new Error(`Must provide at least min threshold of key shares`);
6176
6321
  }
6177
6322
  const mpcSigner = getMPCSigner({
6178
6323
  chainName,
6179
- baseRelayUrl: this.baseMPCRelayApiUrl
6324
+ baseRelayUrl: this.baseMPCRelayApiUrl,
6325
+ variant
6180
6326
  });
6181
6327
  const walletKeyShares = keyShares.map((keyShare)=>{
6182
6328
  if (!('pubkey' in keyShare)) {
@@ -6195,6 +6341,10 @@ class DynamicWalletClient {
6195
6341
  derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
6196
6342
  } else if (mpcSigner instanceof ExportableEd25519) {
6197
6343
  derivedPrivateKey = keyExportRaw;
6344
+ } else if (mpcSigner instanceof Ed25519) {
6345
+ // Non-exportable Ed25519 (raw-scalar) returns an `spriv`, not raw hex —
6346
+ // parse it to the root scalar (see derivePrivateKeyFromExport).
6347
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromSpriv(keyExportRaw);
6198
6348
  } else if (mpcSigner instanceof BIP340) {
6199
6349
  derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
6200
6350
  }
@@ -6400,7 +6550,7 @@ class DynamicWalletClient {
6400
6550
  * @param thresholdSignatureScheme - The TSS scheme used
6401
6551
  * @param derivationPath - Optional derivation path (will be computed from chainConfig if not provided)
6402
6552
  * @param additionalProps - Any chain-specific additional properties to merge
6403
- */ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, shareSetId, shareSetType, otherShareSets, additionalProps = {} }) {
6553
+ */ initializeWalletMapEntry({ accountAddress, walletId, chainName, thresholdSignatureScheme, derivationPath, shareSetId, shareSetType, otherShareSets, variant, additionalProps = {} }) {
6404
6554
  this.updateWalletMap(accountAddress, _extends({
6405
6555
  accountAddress,
6406
6556
  walletId,
@@ -6414,6 +6564,8 @@ class DynamicWalletClient {
6414
6564
  shareSetType
6415
6565
  } : {}, otherShareSets ? {
6416
6566
  otherShareSets
6567
+ } : {}, variant ? {
6568
+ variant
6417
6569
  } : {}, {
6418
6570
  clientKeySharesBackupInfo: getClientKeyShareBackupInfo()
6419
6571
  }, additionalProps));
@@ -6633,7 +6785,7 @@ class DynamicWalletClient {
6633
6785
  tokenSource: googleDriveTokenSource
6634
6786
  } : {});
6635
6787
  }
6636
- async publishDelegatedShare({ walletId, shareSetId, delegatedShare, signedSessionId, dynamicRequestId, chainName, bitcoinConfig, isPasswordEncrypted }) {
6788
+ async publishDelegatedShare({ walletId, shareSetId, delegatedShare, signedSessionId, dynamicRequestId, chainName, accountAddress, bitcoinConfig, isPasswordEncrypted }) {
6637
6789
  var _publicKey_key, _publicKey_key1, _publicKey_key2;
6638
6790
  const publicKey = await this.apiClient.getDelegatedEncryptionKey({
6639
6791
  environmentId: this.environmentId
@@ -6658,7 +6810,8 @@ class DynamicWalletClient {
6658
6810
  chainName,
6659
6811
  clientKeyShare: delegatedShare,
6660
6812
  bitcoinConfig,
6661
- walletId
6813
+ walletId,
6814
+ accountAddress
6662
6815
  });
6663
6816
  return {
6664
6817
  location: BackupLocation.DELEGATED,
@@ -6824,6 +6977,7 @@ class DynamicWalletClient {
6824
6977
  signedSessionId: resolvedSignedSessionId,
6825
6978
  dynamicRequestId,
6826
6979
  chainName: walletData.chainName,
6980
+ accountAddress,
6827
6981
  bitcoinConfig,
6828
6982
  isPasswordEncrypted
6829
6983
  }), {
@@ -6992,7 +7146,8 @@ class DynamicWalletClient {
6992
7146
  accountAddress,
6993
7147
  chainName,
6994
7148
  localShares,
6995
- recordedKeygenIds
7149
+ recordedKeygenIds,
7150
+ variant: walletData.variant
6996
7151
  });
6997
7152
  if (fresh) return;
6998
7153
  this.logger.warn('[storeEncryptedBackupByWallet] Stale local shares detected; refreshing local storage', {
@@ -8417,9 +8572,17 @@ class DynamicWalletClient {
8417
8572
  const user = await this.getUserWithEnvCheck(dynamicRequestId);
8418
8573
  const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
8419
8574
  for (const vc of waasWallets != null ? waasWallets : []){
8575
+ var _props_settings, _this_getWalletFromMap;
8420
8576
  const addr = vc.address;
8421
8577
  const props = vc.walletProperties;
8422
- this.updateWalletMap(addr, {
8578
+ var _props_settings_variant, _ref;
8579
+ // Resolve the ed25519 protocol variant: server-surfaced (walletProperties
8580
+ // .settings or top-level) first, then any locally-seeded value (set at raw
8581
+ // import). Required on a fresh session (no local seed) so recover/sign
8582
+ // rebuild the non-exportable Ed25519 signer for raw-scalar wallets instead
8583
+ // of defaulting to exportable → "Invalid discriminant".
8584
+ const resolvedVariant = (_ref = (_props_settings_variant = props == null ? void 0 : (_props_settings = props.settings) == null ? void 0 : _props_settings.variant) != null ? _props_settings_variant : props == null ? void 0 : props.variant) != null ? _ref : (_this_getWalletFromMap = this.getWalletFromMap(addr)) == null ? void 0 : _this_getWalletFromMap.variant;
8585
+ this.updateWalletMap(addr, _extends({
8423
8586
  walletId: vc.id,
8424
8587
  chainName: verifiedCredentialNameToChainEnum[vc.chain],
8425
8588
  accountAddress: addr,
@@ -8432,7 +8595,9 @@ class DynamicWalletClient {
8432
8595
  shareSetId: props == null ? void 0 : props.shareSetId,
8433
8596
  shareSetType: props == null ? void 0 : props.shareSetType,
8434
8597
  otherShareSets: props == null ? void 0 : props.otherShareSets
8435
- });
8598
+ }, resolvedVariant ? {
8599
+ variant: resolvedVariant
8600
+ } : {}));
8436
8601
  }
8437
8602
  if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
8438
8603
  accountAddress,
@@ -8810,8 +8975,8 @@ class DynamicWalletClient {
8810
8975
  this.userId = user.id;
8811
8976
  const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
8812
8977
  const wallets = waasWallets.map((vc)=>{
8813
- var _this_getWalletFromMap, _vc_walletProperties, _vc_walletProperties1, _vc_walletProperties2, _vc_walletProperties3, _vc_walletProperties4, _vc_walletProperties5, _vc_walletProperties6;
8814
- var _this_getWalletFromMap_derivationPath;
8978
+ var _this_getWalletFromMap, _vc_walletProperties, _vc_walletProperties1, _vc_walletProperties2, _vc_walletProperties3, _vc_walletProperties4, _vc_walletProperties5, _vc_walletProperties6, _vc_walletProperties_settings, _vc_walletProperties7, _vc_walletProperties8, _this_getWalletFromMap1;
8979
+ var _this_getWalletFromMap_derivationPath, _vc_walletProperties_settings_variant, _ref;
8815
8980
  return {
8816
8981
  walletId: vc.id,
8817
8982
  chainName: verifiedCredentialNameToChainEnum[vc.chain],
@@ -8829,14 +8994,18 @@ class DynamicWalletClient {
8829
8994
  // On-sign-on settings (shouldRefreshOnNextSignOn / reshareOnNextSignOn /
8830
8995
  // revokeOnNextSignOn). Surfaced so the SDK can self-drive the on-sign-on
8831
8996
  // orchestration instead of the host reading these flags.
8832
- settings: (_vc_walletProperties6 = vc.walletProperties) == null ? void 0 : _vc_walletProperties6.settings
8997
+ settings: (_vc_walletProperties6 = vc.walletProperties) == null ? void 0 : _vc_walletProperties6.settings,
8998
+ // Prefer the server-persisted variant (surfaced via walletProperties.settings),
8999
+ // then the locally-known value (set at raw import) so same-session
9000
+ // import → sign works before the server has surfaced it.
9001
+ variant: (_ref = (_vc_walletProperties_settings_variant = (_vc_walletProperties7 = vc.walletProperties) == null ? void 0 : (_vc_walletProperties_settings = _vc_walletProperties7.settings) == null ? void 0 : _vc_walletProperties_settings.variant) != null ? _vc_walletProperties_settings_variant : (_vc_walletProperties8 = vc.walletProperties) == null ? void 0 : _vc_walletProperties8.variant) != null ? _ref : (_this_getWalletFromMap1 = this.getWalletFromMap(vc.address)) == null ? void 0 : _this_getWalletFromMap1.variant
8833
9002
  };
8834
9003
  });
8835
9004
  const existingWalletMap = this.walletMap;
8836
9005
  this.walletMap = wallets.reduce((acc, wallet)=>{
8837
9006
  const normalizedAddress = normalizeAddress(wallet.accountAddress);
8838
9007
  const existingWallet = existingWalletMap[normalizedAddress];
8839
- acc[normalizedAddress] = {
9008
+ acc[normalizedAddress] = _extends({
8840
9009
  walletId: wallet.walletId,
8841
9010
  chainName: wallet.chainName,
8842
9011
  accountAddress: wallet.accountAddress,
@@ -8849,7 +9018,9 @@ class DynamicWalletClient {
8849
9018
  shareSetType: wallet.shareSetType,
8850
9019
  businessAccountId: wallet.businessAccountId,
8851
9020
  otherShareSets: wallet.otherShareSets
8852
- };
9021
+ }, wallet.variant ? {
9022
+ variant: wallet.variant
9023
+ } : {});
8853
9024
  return acc;
8854
9025
  }, {});
8855
9026
  this.logger.info('[walletMap] getWallets: walletMap updated from API', {
@@ -9303,4 +9474,44 @@ DynamicWalletClient.roomsPersistDirty = false;
9303
9474
  // rooms back into the new user's storage (TOCTOU on the create→merge gap).
9304
9475
  DynamicWalletClient.roomsGeneration = 0;
9305
9476
 
9306
- export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, HEAVY_QUEUE_OPERATIONS, OperationSource, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };
9477
+ /**
9478
+ * Derive the 32-byte ed25519 public key from a **raw** 32-byte signing scalar
9479
+ * (`A = s·B`).
9480
+ *
9481
+ * This is NOT the same as deriving a public key from an RFC-8032 seed: the
9482
+ * standard ed25519 key-generation step takes a 32-byte seed and SHA-512-expands
9483
+ * it (then clamps) to obtain the scalar. A scalar exported from an external MPC
9484
+ * system (e.g. Fireblocks Embedded Wallets) has no preimage seed, so it must be
9485
+ * used directly as the scalar. Using `Keypair.fromSecretKey` / `keyPairFromSeed`
9486
+ * on such bytes would re-expand them and produce the wrong public key/address.
9487
+ *
9488
+ * @param scalar Raw 32-byte ed25519 scalar, little-endian, as hex (with or
9489
+ * without a `0x` prefix) or as raw bytes.
9490
+ * @returns The compressed 32-byte ed25519 public key.
9491
+ */ const getEd25519PublicKeyFromRawScalar = (scalar)=>{
9492
+ let bytes;
9493
+ if (typeof scalar === 'string') {
9494
+ const hex = scalar.replace(/^0x/, '');
9495
+ // Validate before hexToBytes so malformed input fails with a clear, own
9496
+ // error rather than an opaque/implementation-specific one from the decoder.
9497
+ if (!/^[0-9a-fA-F]+$/.test(hex)) {
9498
+ throw new Error('Invalid raw ed25519 scalar: expected a hex string');
9499
+ }
9500
+ bytes = hexToBytes(hex);
9501
+ } else {
9502
+ bytes = scalar;
9503
+ }
9504
+ if (bytes.length !== 32) {
9505
+ throw new Error(`Invalid raw ed25519 scalar length: ${bytes.length}, expected 32`);
9506
+ }
9507
+ // Interpret the scalar little-endian and reduce mod the curve order. We do NOT
9508
+ // clamp here: the caller provides the actual signing scalar `s`, and the public
9509
+ // key is exactly `s·B`.
9510
+ const reduced = bytesToNumberLE(bytes) % ed25519.CURVE.n;
9511
+ if (reduced === 0n) {
9512
+ throw new Error('Invalid raw ed25519 scalar: reduces to zero mod the curve order');
9513
+ }
9514
+ return ed25519.ExtendedPoint.BASE.multiply(reduced).toRawBytes();
9515
+ };
9516
+
9517
+ export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, HEAVY_QUEUE_OPERATIONS, OperationSource, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getEd25519PublicKeyFromRawScalar, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isMultipleWalletsPerChainRejection, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };