decane-connect-kit 2.26.1 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(errors_exports, {
36
36
  AttestationError: () => AttestationError,
37
37
  CallSequenceError: () => CallSequenceError,
38
38
  DecaneConnectError: () => DecaneConnectError,
39
+ EnclaveHttpError: () => EnclaveHttpError,
39
40
  ExportNotAuthorizedError: () => ExportNotAuthorizedError,
40
41
  InvalidAccessTokenError: () => InvalidAccessTokenError,
41
42
  InvalidApiKeyError: () => InvalidApiKeyError,
@@ -46,6 +47,7 @@ __export(errors_exports, {
46
47
  OriginNotAllowedError: () => OriginNotAllowedError,
47
48
  PromptPasswordNotConfiguredError: () => PromptPasswordNotConfiguredError,
48
49
  PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
50
+ ProvisioningRateLimitedError: () => ProvisioningRateLimitedError,
49
51
  ProvisioningTimeoutError: () => ProvisioningTimeoutError,
50
52
  ReconnectRequiredError: () => ReconnectRequiredError,
51
53
  RecoveryFailedError: () => RecoveryFailedError,
@@ -56,11 +58,13 @@ __export(errors_exports, {
56
58
  UnlockRequiredError: () => UnlockRequiredError,
57
59
  UnlockTokenRejectedError: () => UnlockTokenRejectedError,
58
60
  UserCancelledError: () => UserCancelledError,
61
+ WalletEnrolmentError: () => WalletEnrolmentError,
62
+ WalletExistsError: () => WalletExistsError,
59
63
  WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
60
64
  WrongPasswordError: () => WrongPasswordError,
61
65
  WrongUnlockPasswordError: () => WrongUnlockPasswordError
62
66
  });
63
- var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
67
+ var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError, WalletExistsError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
64
68
  var init_errors = __esm({
65
69
  "src/social/errors.ts"() {
66
70
  "use strict";
@@ -209,6 +213,36 @@ var init_errors = __esm({
209
213
  super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
210
214
  }
211
215
  };
216
+ WalletExistsError = class extends DecaneConnectError {
217
+ constructor() {
218
+ super("WALLET_EXISTS", "This account already has a wallet; opening it instead.", true);
219
+ }
220
+ };
221
+ WalletEnrolmentError = class extends DecaneConnectError {
222
+ constructor(cause) {
223
+ super(
224
+ "WALLET_ENROLMENT_FAILED",
225
+ "Your wallet could not be set up: the signing enclave did not accept it. Nothing was created \u2014 sign in again to try again.",
226
+ true
227
+ );
228
+ if (cause !== void 0) this.cause = cause;
229
+ }
230
+ };
231
+ ProvisioningRateLimitedError = class extends DecaneConnectError {
232
+ constructor() {
233
+ super(
234
+ "PROVISIONING_RATE_LIMITED",
235
+ "This wallet has been opened on too many devices or tabs in the last hour. Wait a little and sign in again.",
236
+ true
237
+ );
238
+ }
239
+ };
240
+ EnclaveHttpError = class extends DecaneConnectError {
241
+ constructor(status, message) {
242
+ super("ENCLAVE_HTTP", message, status >= 500);
243
+ this.status = status;
244
+ }
245
+ };
212
246
  }
213
247
  });
214
248
 
@@ -226,6 +260,7 @@ __export(index_exports, {
226
260
  DecaneLogo: () => DecaneLogo,
227
261
  DecaneMark: () => DecaneMark,
228
262
  DecaneWordmark: () => DecaneWordmark,
263
+ EnclaveHttpError: () => EnclaveHttpError,
229
264
  ExportNotAuthorizedError: () => ExportNotAuthorizedError,
230
265
  InvalidAccessTokenError: () => InvalidAccessTokenError,
231
266
  InvalidApiKeyError: () => InvalidApiKeyError,
@@ -237,6 +272,7 @@ __export(index_exports, {
237
272
  OriginNotAllowedError: () => OriginNotAllowedError,
238
273
  PromptPasswordNotConfiguredError: () => PromptPasswordNotConfiguredError,
239
274
  PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
275
+ ProvisioningRateLimitedError: () => ProvisioningRateLimitedError,
240
276
  ProvisioningTimeoutError: () => ProvisioningTimeoutError,
241
277
  ReconnectRequiredError: () => ReconnectRequiredError,
242
278
  RecoveryFailedError: () => RecoveryFailedError,
@@ -248,6 +284,8 @@ __export(index_exports, {
248
284
  UnlockRequiredError: () => UnlockRequiredError,
249
285
  UnlockTokenRejectedError: () => UnlockTokenRejectedError,
250
286
  UserCancelledError: () => UserCancelledError,
287
+ WalletEnrolmentError: () => WalletEnrolmentError,
288
+ WalletExistsError: () => WalletExistsError,
251
289
  WalletSelector: () => WalletSelector,
252
290
  WeakPasswordError: () => WeakPasswordError,
253
291
  WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
@@ -2078,7 +2116,9 @@ var ERROR_MAP = {
2078
2116
  // The backend's authMiddleware code for an expired/revoked JWT. Surfacing it
2079
2117
  // as ReconnectRequiredError routes callers to the sign-in UI instead of a
2080
2118
  // generic failure message.
2081
- UNAUTHORIZED: ReconnectRequiredError
2119
+ UNAUTHORIZED: ReconnectRequiredError,
2120
+ // A restorable wallet already exists: the client provisions instead of creating.
2121
+ WALLET_EXISTS: WalletExistsError
2082
2122
  };
2083
2123
  var REQUEST_TIMEOUT_MS = 3e4;
2084
2124
  var ApiClient = class {
@@ -2197,7 +2237,8 @@ var ApiClient = class {
2197
2237
  wrappedShareMeta: btoa(JSON.stringify(share.wrappedShareMeta)),
2198
2238
  evmAddress: share.evmAddress,
2199
2239
  solanaAddress: share.solanaAddress,
2200
- tronAddress: share.tronAddress
2240
+ tronAddress: share.tronAddress,
2241
+ ...share.pendingEnrolment === void 0 ? {} : { pendingEnrolment: share.pendingEnrolment }
2201
2242
  })
2202
2243
  });
2203
2244
  }
@@ -2477,7 +2518,7 @@ function dropCollateral(teeUrl) {
2477
2518
  // src/social/attestation.ts
2478
2519
  init_errors();
2479
2520
 
2480
- // ../../infra/tee-measurement.json
2521
+ // ../infra/tee-measurement.json
2481
2522
  var tee_measurement_default = {
2482
2523
  signerVersion: "0.2.10",
2483
2524
  acceptedMrtd: [
@@ -2776,7 +2817,8 @@ var TeeClient = class {
2776
2817
  });
2777
2818
  if (!res.ok) {
2778
2819
  const detail = await res.text().catch(() => "");
2779
- throw new Error(
2820
+ throw new EnclaveHttpError(
2821
+ res.status,
2780
2822
  `auth share enrolment failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
2781
2823
  );
2782
2824
  }
@@ -2797,9 +2839,11 @@ var TeeClient = class {
2797
2839
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
2798
2840
  });
2799
2841
  if (res.status === 409) return null;
2842
+ if (res.status === 429) throw new ProvisioningRateLimitedError();
2800
2843
  if (!res.ok) {
2801
2844
  const detail = await res.text().catch(() => "");
2802
- throw new Error(
2845
+ throw new EnclaveHttpError(
2846
+ res.status,
2803
2847
  `device share provisioning failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
2804
2848
  );
2805
2849
  }
@@ -3014,6 +3058,35 @@ var SessionManager = class {
3014
3058
  }
3015
3059
  };
3016
3060
 
3061
+ // src/social/identityTier.ts
3062
+ function resolveProtectionTier(config) {
3063
+ if (config.protection) return config.protection;
3064
+ return config.deferDeviceProtection === true ? "identity" : "device";
3065
+ }
3066
+ function resumeAcrossTabsDefault(config, identityTier) {
3067
+ return config.resumeSessionAcrossTabs ?? identityTier;
3068
+ }
3069
+ var ENROL_BACKOFF_MS = [0, 1e3, 3e3];
3070
+ function enrolmentRetryable(status) {
3071
+ return status !== 429 && status !== 403;
3072
+ }
3073
+ function shouldTryProvisionFirst(identityTier, hasStoredShare) {
3074
+ return identityTier || !hasStoredShare;
3075
+ }
3076
+ function routeAfterProvision(input) {
3077
+ switch (input.outcome) {
3078
+ case "share":
3079
+ return "session";
3080
+ case "rate-limited":
3081
+ return "fail";
3082
+ case "not-enrolled":
3083
+ if (!input.identityTier) return "recover";
3084
+ return input.hasStoredShare ? "migrate" : "new-device";
3085
+ case "error":
3086
+ return input.identityTier ? "fail" : "recover";
3087
+ }
3088
+ }
3089
+
3017
3090
  // src/social/crypto/shares.ts
3018
3091
  var import_secp256k1 = require("@noble/curves/secp256k1");
3019
3092
  var import_shamir_secret_sharing = require("shamir-secret-sharing");
@@ -3924,7 +3997,8 @@ function clearSessionPersist(key) {
3924
3997
  async function createDecaneConnect(config) {
3925
3998
  if (!config.appId) throw new Error("decane-connect-kit: appId is required");
3926
3999
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
3927
- useCrossTabSessionStore(config.resumeSessionAcrossTabs === true);
4000
+ const identityTier = resolveProtectionTier(config) === "identity";
4001
+ useCrossTabSessionStore(resumeAcrossTabsDefault(config, identityTier));
3928
4002
  if (!config.chains?.length)
3929
4003
  throw new Error("decane-connect-kit: at least one chain is required");
3930
4004
  if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
@@ -3962,7 +4036,6 @@ async function createDecaneConnect(config) {
3962
4036
  let lastUserId = loadLastUser(LAST_USER_KEY) ?? (LEGACY_LAST_USER_KEY !== LAST_USER_KEY ? loadLastUser(LEGACY_LAST_USER_KEY) : null);
3963
4037
  let ethProvider = null;
3964
4038
  const persistSession = config.persistSession !== false;
3965
- const deferDeviceProtection = config.deferDeviceProtection === true;
3966
4039
  const serverAssistedRecovery = true;
3967
4040
  const RECOVERY_OFFER_TIMEOUT_MS = 5 * 6e4;
3968
4041
  const selfHealAttempted = /* @__PURE__ */ new Set();
@@ -3983,22 +4056,27 @@ async function createDecaneConnect(config) {
3983
4056
  zeroize(share);
3984
4057
  }
3985
4058
  }
3986
- async function enrolInBackground(jwt, share) {
4059
+ async function enrolBlocking(jwt, share, budgetMs) {
4060
+ const startedAt = Date.now();
3987
4061
  try {
3988
- for (let attempt = 1; attempt <= 2; attempt++) {
4062
+ let lastError;
4063
+ for (const wait of ENROL_BACKOFF_MS) {
4064
+ if (wait) await new Promise((r) => setTimeout(r, wait));
3989
4065
  try {
3990
- await new TeeClient(teeUrl).enrolAuthShare(jwt, share);
4066
+ await withDeadline(
4067
+ new TeeClient(teeUrl).enrolAuthShare(jwt, share),
4068
+ budgetMs - (Date.now() - startedAt),
4069
+ "enrolling your wallet"
4070
+ );
3991
4071
  return;
3992
4072
  } catch (e) {
3993
- if (attempt === 2) {
3994
- events.emit("enrolment-failed");
3995
- console.warn(
3996
- "decane-connect-kit: could not enrol this wallet for server-assisted recovery. The wallet is fine and fully usable; it will be re-enrolled automatically on the next unlock. Until then a brand-new device would need a passkey backup or a recovery file.",
3997
- e
3998
- );
3999
- }
4073
+ if (e instanceof AttestationError || e instanceof ProvisioningTimeoutError) throw e;
4074
+ lastError = e;
4075
+ if (!enrolmentRetryable(e.status)) break;
4000
4076
  }
4001
4077
  }
4078
+ events.emit("enrolment-failed");
4079
+ throw new WalletEnrolmentError(lastError);
4002
4080
  } finally {
4003
4081
  zeroize(share);
4004
4082
  }
@@ -4094,6 +4172,7 @@ async function createDecaneConnect(config) {
4094
4172
  else scheduleAccessTokenExpiry(saved.jwt, saved.expiresAt);
4095
4173
  auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
4096
4174
  persisted = { userId: saved.userId, addresses: saved.addresses };
4175
+ if (saved.handle) armTokenRefresh(saved.jwt);
4097
4176
  } catch {
4098
4177
  dropLiveSession();
4099
4178
  }
@@ -4103,6 +4182,13 @@ async function createDecaneConnect(config) {
4103
4182
  if (LEGACY_SESSION_KEY !== SESSION_KEY) clearSessionPersist(LEGACY_SESSION_KEY);
4104
4183
  }
4105
4184
  events.on("session-expired", () => dropLiveSession());
4185
+ function armTokenRefresh(jwt) {
4186
+ const expiresAt = decodeAccessToken(jwt)?.expiresAt;
4187
+ if (expiresAt) scheduleTokenRefresh(jwt, expiresAt);
4188
+ }
4189
+ events.on("connected", () => {
4190
+ if (auth) armTokenRefresh(auth.jwt);
4191
+ });
4106
4192
  async function passwordSetup() {
4107
4193
  if (!config.promptUnlockPassword) throw new PromptPasswordNotConfiguredError();
4108
4194
  const password = await config.promptUnlockPassword({ kind: "set" });
@@ -4327,7 +4413,7 @@ async function createDecaneConnect(config) {
4327
4413
  let wrappingKey = null;
4328
4414
  let meta = {};
4329
4415
  let unlockTokenKey = null;
4330
- if (deferDeviceProtection) {
4416
+ if (identityTier) {
4331
4417
  } else if (isPasskeySupported()) {
4332
4418
  const prfSalt = generateRandomBytes(32);
4333
4419
  try {
@@ -4394,19 +4480,47 @@ async function createDecaneConnect(config) {
4394
4480
  if (unlockTokenKey) await attachUnlockToken(userId, jwt, unlockTokenKey);
4395
4481
  }
4396
4482
  const serverIv = generateRandomBytes(12);
4397
- await withDeadline(api.putShare(jwt, {
4398
- ciphertext: shareSet.server,
4399
- iv: serverIv,
4400
- wrappedShareMeta: {
4401
- // The server records this on the wallet_created event and nothing else.
4402
- unlockMethod: deferDeviceProtection ? "deferred" : unlockMethod
4403
- // credential IDs, prfSalt, pinSalt stay on-device — never sent to server
4404
- },
4405
- evmAddress,
4406
- solanaAddress,
4407
- tronAddress
4408
- }), remainingBudget(), "storing your wallet");
4483
+ try {
4484
+ await withDeadline(api.putShare(jwt, {
4485
+ ciphertext: shareSet.server,
4486
+ iv: serverIv,
4487
+ wrappedShareMeta: {
4488
+ // The server records this on the wallet_created event and nothing else.
4489
+ unlockMethod: identityTier ? "identity" : unlockMethod
4490
+ // credential IDs, prfSalt, pinSalt stay on-device — never sent to server
4491
+ },
4492
+ evmAddress,
4493
+ solanaAddress,
4494
+ tronAddress,
4495
+ // Provisional until the enclave has sealed the auth share, just below. A
4496
+ // backend that knows the flag keeps this row invisible to hasShare until
4497
+ // then, so a creation that fails past this point leaves no wallet behind
4498
+ // and the next sign-in simply creates again — with a new key, which is
4499
+ // why the backend also replaces the recorded addresses outright.
4500
+ pendingEnrolment: true
4501
+ }), remainingBudget(), "storing your wallet");
4502
+ } catch (e) {
4503
+ if (e instanceof WalletExistsError) {
4504
+ zeroize(shareSet.server);
4505
+ zeroize(shareSet.device);
4506
+ zeroize(shareSet.recovery);
4507
+ if (wrappingKey) await storage2.deleteShare(userId).catch(() => {
4508
+ });
4509
+ events.emit("wallet-unlocking");
4510
+ return returningUserUnlock(userId, jwt);
4511
+ }
4512
+ throw e;
4513
+ }
4409
4514
  zeroize(shareSet.server);
4515
+ try {
4516
+ await enrolBlocking(jwt, new Uint8Array(shareSet.recovery), remainingBudget());
4517
+ } catch (e) {
4518
+ zeroize(shareSet.device);
4519
+ zeroize(shareSet.recovery);
4520
+ if (wrappingKey) await storage2.deleteShare(userId).catch(() => {
4521
+ });
4522
+ throw e;
4523
+ }
4410
4524
  const addresses = { evm: evmAddress, solana: solanaAddress, tron: tronAddress };
4411
4525
  if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt && wrappedDeviceShare && iv) {
4412
4526
  try {
@@ -4435,10 +4549,7 @@ async function createDecaneConnect(config) {
4435
4549
  addresses.evm = evmAddress = confirmed.evm;
4436
4550
  addresses.solana = solanaAddress = confirmed.solana;
4437
4551
  addresses.tron = tronAddress = confirmed.tron ?? evmToTronAddress2(confirmed.evm);
4438
- if (serverAssistedRecovery) {
4439
- void enrolInBackground(jwt, new Uint8Array(shareSet.recovery));
4440
- }
4441
- if (config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
4552
+ if (!identityTier && config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
4442
4553
  void offerRecoveryShare(
4443
4554
  jwt,
4444
4555
  new Uint8Array(shareSet.recovery),
@@ -4485,31 +4596,44 @@ async function createDecaneConnect(config) {
4485
4596
  const stored = await storage2.getShare(userId);
4486
4597
  let deviceShare;
4487
4598
  let joinedViaPasskeySync = false;
4488
- if (!stored) {
4489
- if (serverAssistedRecovery) {
4599
+ let migrating = false;
4600
+ if (serverAssistedRecovery && shouldTryProvisionFirst(identityTier, !!stored)) {
4601
+ let provisioned = null;
4602
+ let failure;
4603
+ let outcome;
4604
+ try {
4605
+ provisioned = await new TeeClient(teeUrl).provisionDeviceShare(jwt);
4606
+ outcome = provisioned ? "share" : "not-enrolled";
4607
+ } catch (e) {
4608
+ failure = e;
4609
+ outcome = e instanceof ProvisioningRateLimitedError ? "rate-limited" : "error";
4610
+ }
4611
+ const route = routeAfterProvision({ identityTier, hasStoredShare: !!stored, outcome });
4612
+ if (route === "session" && provisioned) {
4613
+ if (!identityTier) await persistProvisionedShare(userId, jwt, provisioned);
4614
+ let addrs;
4490
4615
  try {
4491
- const tee = new TeeClient(teeUrl);
4492
- const provisioned = await tee.provisionDeviceShare(jwt);
4493
- if (provisioned) {
4494
- if (!deferDeviceProtection) await persistProvisionedShare(userId, jwt, provisioned);
4495
- let addrs;
4496
- try {
4497
- addrs = await session.start(provisioned, jwt, recordedAddresses);
4498
- } finally {
4499
- zeroize(provisioned);
4500
- }
4501
- return addrs;
4502
- }
4503
- console.warn(
4504
- "decane-connect-kit: this wallet is not enrolled for server-assisted provisioning (/signer/provision returned 409 not_enrolled), so a device share cannot be issued from the JWT alone. Falling back to passkey sync, then the recovery file."
4505
- );
4506
- } catch (e) {
4507
- console.warn(
4508
- "decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
4509
- e
4510
- );
4616
+ addrs = await session.start(provisioned, jwt, recordedAddresses);
4617
+ } finally {
4618
+ zeroize(provisioned);
4511
4619
  }
4620
+ return addrs;
4621
+ }
4622
+ if (route === "fail") throw failure;
4623
+ if (route === "new-device") throw new NewDeviceError();
4624
+ if (route === "migrate") migrating = true;
4625
+ if (route === "recover" && outcome === "not-enrolled") {
4626
+ console.warn(
4627
+ "decane-connect-kit: this wallet is not enrolled for server-assisted provisioning (/signer/provision returned 409 not_enrolled), so a device share cannot be issued from the JWT alone. Falling back to passkey sync, then the recovery file."
4628
+ );
4629
+ } else if (route === "recover") {
4630
+ console.warn(
4631
+ "decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
4632
+ failure
4633
+ );
4512
4634
  }
4635
+ }
4636
+ if (!stored) {
4513
4637
  if (!config.onRecoveryRotated) {
4514
4638
  throw new CallSequenceError(
4515
4639
  "Recovery rotates the share set and issues a new recovery file, but no onRecoveryRotated callback is configured to save it. Configure onRecoveryRotated before allowing recovery."
@@ -4535,7 +4659,7 @@ async function createDecaneConnect(config) {
4535
4659
  });
4536
4660
  deviceShare = recovered.deviceShare;
4537
4661
  joinedViaPasskeySync = recovered.flow === "passkey-sync";
4538
- if (!deferDeviceProtection && !await storage2.getShare(userId) && isPasskeySupported()) {
4662
+ if (!identityTier && !await storage2.getShare(userId) && isPasskeySupported()) {
4539
4663
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
4540
4664
  }
4541
4665
  } else {
@@ -4549,6 +4673,14 @@ async function createDecaneConnect(config) {
4549
4673
  } catch {
4550
4674
  throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
4551
4675
  }
4676
+ if (migrating) {
4677
+ try {
4678
+ await enrolBlocking(jwt, new Uint8Array(deviceShare), PROVISIONING_BUDGET_MS);
4679
+ } catch (e) {
4680
+ zeroize(deviceShare);
4681
+ throw e;
4682
+ }
4683
+ }
4552
4684
  }
4553
4685
  let addresses;
4554
4686
  try {
@@ -4563,7 +4695,11 @@ async function createDecaneConnect(config) {
4563
4695
  }
4564
4696
  throw e;
4565
4697
  }
4566
- void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
4698
+ if (migrating) {
4699
+ events.emit("migrated-to-identity");
4700
+ } else if (!identityTier) {
4701
+ void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
4702
+ }
4567
4703
  zeroize(deviceShare);
4568
4704
  if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
4569
4705
  console.warn(
@@ -4746,7 +4882,7 @@ async function createDecaneConnect(config) {
4746
4882
  if (auth && session.isActive()) {
4747
4883
  return { addresses: auth.addresses, isNewUser: false };
4748
4884
  }
4749
- const candidate = await passkeyLoginCandidate();
4885
+ const candidate = identityTier ? null : await passkeyLoginCandidate();
4750
4886
  if (candidate) {
4751
4887
  try {
4752
4888
  return await passkeyLogin(candidate);
@@ -4856,9 +4992,15 @@ async function createDecaneConnect(config) {
4856
4992
  return handleAuthResult(result);
4857
4993
  },
4858
4994
  async canSignInWithPasskey() {
4995
+ if (identityTier) return false;
4859
4996
  return canOfferPasskey();
4860
4997
  },
4861
4998
  async signInWithPasskey() {
4999
+ if (identityTier) {
5000
+ throw new CallSequenceError(
5001
+ "signInWithPasskey() is not available in the identity tier (protection: 'identity'); use connect()."
5002
+ );
5003
+ }
4862
5004
  const candidate = await passkeyDeviceCandidate();
4863
5005
  if (candidate) return passkeyLogin(candidate);
4864
5006
  const known = persisted?.userId ?? lastUserId;
@@ -4994,6 +5136,11 @@ async function createDecaneConnect(config) {
4994
5136
  return stored?.unlockMethod === "passkey";
4995
5137
  },
4996
5138
  async addPasskey() {
5139
+ if (identityTier) {
5140
+ throw new CallSequenceError(
5141
+ "addPasskey() is not available in the identity tier (protection: 'identity'): nothing is stored on the device for a passkey to protect."
5142
+ );
5143
+ }
4997
5144
  if (!auth) throw new ReconnectRequiredError();
4998
5145
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
4999
5146
  const stored = await storage2.getShare(auth.userId);
@@ -5032,6 +5179,7 @@ async function createDecaneConnect(config) {
5032
5179
  return await storage2.getShare(auth.userId) !== null;
5033
5180
  },
5034
5181
  async protectDevice() {
5182
+ if (identityTier) return false;
5035
5183
  if (!auth) throw new ReconnectRequiredError();
5036
5184
  if (await storage2.getShare(auth.userId)) return true;
5037
5185
  if (!serverAssistedRecovery) throw new NewDeviceError();
@@ -5047,7 +5195,7 @@ async function createDecaneConnect(config) {
5047
5195
  },
5048
5196
  async unlock() {
5049
5197
  if (!auth) {
5050
- const candidate = await passkeyLoginCandidate();
5198
+ const candidate = identityTier ? null : await passkeyLoginCandidate();
5051
5199
  if (candidate) {
5052
5200
  await passkeyLogin(candidate);
5053
5201
  return;
@@ -5216,6 +5364,8 @@ async function createDecaneConnect(config) {
5216
5364
  },
5217
5365
  async disconnect() {
5218
5366
  session.end();
5367
+ if (tokenRefresh) clearTimeout(tokenRefresh);
5368
+ tokenRefresh = null;
5219
5369
  if (auth?.jwt) {
5220
5370
  await api.revoke(auth.jwt).catch(() => {
5221
5371
  });
@@ -7666,6 +7816,7 @@ function DecaneKit({ config, children }) {
7666
7816
  chains,
7667
7817
  rpcUrls,
7668
7818
  requireAssertionPerSignature,
7819
+ protection,
7669
7820
  deferDeviceProtection,
7670
7821
  resumeSessionAcrossTabs,
7671
7822
  // Recovery callbacks. Forwarding these is what makes recovery
@@ -7688,6 +7839,7 @@ function DecaneKit({ config, children }) {
7688
7839
  promptPin,
7689
7840
  promptUnlockPassword,
7690
7841
  requireAssertionPerSignature,
7842
+ protection,
7691
7843
  deferDeviceProtection,
7692
7844
  resumeSessionAcrossTabs,
7693
7845
  offerRecoveryAtSignup,
@@ -8897,6 +9049,7 @@ function useSignTypedData() {
8897
9049
  DecaneLogo,
8898
9050
  DecaneMark,
8899
9051
  DecaneWordmark,
9052
+ EnclaveHttpError,
8900
9053
  ExportNotAuthorizedError,
8901
9054
  InvalidAccessTokenError,
8902
9055
  InvalidApiKeyError,
@@ -8908,6 +9061,7 @@ function useSignTypedData() {
8908
9061
  OriginNotAllowedError,
8909
9062
  PromptPasswordNotConfiguredError,
8910
9063
  PromptPinNotConfiguredError,
9064
+ ProvisioningRateLimitedError,
8911
9065
  ProvisioningTimeoutError,
8912
9066
  ReconnectRequiredError,
8913
9067
  RecoveryFailedError,
@@ -8919,6 +9073,8 @@ function useSignTypedData() {
8919
9073
  UnlockRequiredError,
8920
9074
  UnlockTokenRejectedError,
8921
9075
  UserCancelledError,
9076
+ WalletEnrolmentError,
9077
+ WalletExistsError,
8922
9078
  WalletSelector,
8923
9079
  WeakPasswordError,
8924
9080
  WebAuthnNotSupportedError,