decane-connect-kit 2.5.0 → 2.6.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.js CHANGED
@@ -27,12 +27,13 @@ __export(errors_exports, {
27
27
  RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
28
28
  SessionExpiredError: () => SessionExpiredError,
29
29
  ShareCorruptedError: () => ShareCorruptedError,
30
+ StaleDeviceShareError: () => StaleDeviceShareError,
30
31
  UnlockRequiredError: () => UnlockRequiredError,
31
32
  UserCancelledError: () => UserCancelledError,
32
33
  WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
33
34
  WrongPasswordError: () => WrongPasswordError
34
35
  });
35
- var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
36
+ var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
36
37
  var init_errors = __esm({
37
38
  "src/social/errors.ts"() {
38
39
  "use strict";
@@ -108,6 +109,15 @@ var init_errors = __esm({
108
109
  super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
109
110
  }
110
111
  };
112
+ StaleDeviceShareError = class extends DecaneConnectError {
113
+ constructor() {
114
+ super(
115
+ "STALE_DEVICE_SHARE",
116
+ "This device\u2019s wallet share is from a superseded share set (likely rotated from another device) and no longer matches this account. Reconnect to recover a fresh share.",
117
+ true
118
+ );
119
+ }
120
+ };
111
121
  PromptPinNotConfiguredError = class extends DecaneConnectError {
112
122
  constructor() {
113
123
  super("PROMPT_PIN_NOT_CONFIGURED", "Passkey unavailable and no promptPin callback was provided in config");
@@ -1957,6 +1967,32 @@ var ApiClient = class {
1957
1967
  async getGoogleInitUrl() {
1958
1968
  return this._request("/auth/google/init", { method: "GET" });
1959
1969
  }
1970
+ /**
1971
+ * Exchange a Google ID token the app already holds for a Decane session.
1972
+ * Resolves to the SAME identity as the redirect flow, so one Google account
1973
+ * means one wallet across web and mobile.
1974
+ */
1975
+ async authGoogleToken(idToken) {
1976
+ return this._request("/auth/google/token", {
1977
+ method: "POST",
1978
+ body: JSON.stringify({ idToken })
1979
+ });
1980
+ }
1981
+ /** The project's KingsChat clientId, so the dApp never has to hardcode it. */
1982
+ async getKingsChatConfig() {
1983
+ return this._request("/auth/kingschat/config", { method: "GET" });
1984
+ }
1985
+ /**
1986
+ * Exchange a KingsChat access token for a Decane session. The backend spends
1987
+ * the token against KingsChat's own API to establish the identity — it is
1988
+ * never trusted on its face, and never stored.
1989
+ */
1990
+ async authKingsChat(accessToken) {
1991
+ return this._request("/auth/kingschat", {
1992
+ method: "POST",
1993
+ body: JSON.stringify({ accessToken })
1994
+ });
1995
+ }
1960
1996
  async authEmailStart(email) {
1961
1997
  await this._request("/auth/email/start", {
1962
1998
  method: "POST",
@@ -2281,6 +2317,13 @@ var TeeClient = class {
2281
2317
  requireAssertionPerSignature
2282
2318
  })
2283
2319
  });
2320
+ if (res.status === 403) {
2321
+ const body2 = await res.json().catch(() => ({}));
2322
+ if (body2?.error === "share_mismatch") {
2323
+ const { StaleDeviceShareError: StaleDeviceShareError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
2324
+ throw new StaleDeviceShareError2();
2325
+ }
2326
+ }
2284
2327
  if (!res.ok) throw new Error("session open failed");
2285
2328
  const body = await res.json();
2286
2329
  this.handle = body.handle;
@@ -2340,6 +2383,45 @@ var TeeClient = class {
2340
2383
  }
2341
2384
  return { challengeId, assertion };
2342
2385
  }
2386
+ /**
2387
+ * Enrol this wallet for server-assisted provisioning by handing the enclave
2388
+ * the recovery share to seal.
2389
+ *
2390
+ * After this, a new device gets a working share from login alone — no passkey
2391
+ * sync, no recovery file. The enclave seals the share under a key only it can
2392
+ * derive, so the backend stores ciphertext it cannot read; but the enclave
2393
+ * then holds two of three shares and can reconstruct on its own. That is the
2394
+ * trade this call makes, which is why it is opt-out rather than implicit.
2395
+ */
2396
+ async enrolAuthShare(jwt, recoveryShare) {
2397
+ await this.ensureAttested();
2398
+ const res = await fetch(`${this.teeUrl}/signer/auth-share`, {
2399
+ method: "POST",
2400
+ headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2401
+ body: JSON.stringify({ share: bytesToBase64(recoveryShare) })
2402
+ });
2403
+ if (!res.ok) throw new Error("auth share enrolment failed");
2404
+ }
2405
+ /**
2406
+ * Ask the enclave for a fresh device share for this authenticated user.
2407
+ * Returns null when the wallet was never enrolled — the signal to fall back
2408
+ * to passkey sync or the recovery file rather than fail.
2409
+ *
2410
+ * Every successful call re-splits the set, so any share this device or any
2411
+ * other held is now dead. Intentional: a leaked device share stops working the
2412
+ * next time the user signs in anywhere.
2413
+ */
2414
+ async provisionDeviceShare(jwt) {
2415
+ await this.ensureAttested();
2416
+ const res = await fetch(`${this.teeUrl}/signer/provision`, {
2417
+ method: "POST",
2418
+ headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
2419
+ });
2420
+ if (res.status === 409) return null;
2421
+ if (!res.ok) throw new Error("device share provisioning failed");
2422
+ const body = await res.json();
2423
+ return base64ToBytes(body.deviceShare);
2424
+ }
2343
2425
  /** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
2344
2426
  async rotateShares(jwt) {
2345
2427
  if (!this.handle) throw new Error("no session");
@@ -2726,6 +2808,79 @@ async function verifyEmailCode(api, email, code) {
2726
2808
  return api.authEmailVerify(email, code);
2727
2809
  }
2728
2810
 
2811
+ // src/social/auth/kingschat.ts
2812
+ init_errors();
2813
+ var KINGSCHAT_AUTH_ORIGIN = "https://accounts.kingsch.at";
2814
+ function readTokens(data) {
2815
+ if (!data || typeof data !== "object") return null;
2816
+ const d = data;
2817
+ const accessToken = d.accessToken ?? d.access_token;
2818
+ if (typeof accessToken !== "string" || !accessToken) return null;
2819
+ const refreshToken = d.refreshToken ?? d.refresh_token;
2820
+ const expires = d.expiresInMillis ?? d.expires_in_millis;
2821
+ return {
2822
+ accessToken,
2823
+ refreshToken: typeof refreshToken === "string" ? refreshToken : void 0,
2824
+ expiresInMillis: typeof expires === "number" ? expires : void 0
2825
+ };
2826
+ }
2827
+ function buildAuthUrl(clientId, scopes) {
2828
+ const url = new URL(KINGSCHAT_AUTH_ORIGIN);
2829
+ url.searchParams.set("client_id", clientId);
2830
+ url.searchParams.set("scopes", JSON.stringify(scopes));
2831
+ url.searchParams.set("redirect_uri", window.location.origin);
2832
+ url.searchParams.set("post_message", "1");
2833
+ return url.toString();
2834
+ }
2835
+ function loginWithKingsChatPopup(opts) {
2836
+ const scopes = opts.scopes ?? ["send_chat_message"];
2837
+ const popup = window.open(
2838
+ buildAuthUrl(opts.clientId, scopes),
2839
+ "decane-kingschat",
2840
+ "width=480,height=720,menubar=no,toolbar=no"
2841
+ );
2842
+ if (!popup) {
2843
+ throw new CallSequenceError(
2844
+ "The KingsChat sign-in popup was blocked. Call connectWithKingsChat() directly from a click handler."
2845
+ );
2846
+ }
2847
+ return new Promise((resolve, reject) => {
2848
+ let settled = false;
2849
+ const finish = (fn) => {
2850
+ if (settled) return;
2851
+ settled = true;
2852
+ window.removeEventListener("message", onMessage);
2853
+ clearInterval(closedPoll);
2854
+ clearTimeout(timeout);
2855
+ try {
2856
+ popup.close();
2857
+ } catch {
2858
+ }
2859
+ fn();
2860
+ };
2861
+ const onMessage = (event) => {
2862
+ if (event.origin !== KINGSCHAT_AUTH_ORIGIN) return;
2863
+ const data = event.data;
2864
+ if (data && typeof data === "object" && data.error) {
2865
+ finish(() => reject(new CallSequenceError(`KingsChat sign-in failed: ${String(data.error)}`)));
2866
+ return;
2867
+ }
2868
+ const tokens2 = readTokens(event.data);
2869
+ if (tokens2) finish(() => resolve(tokens2));
2870
+ };
2871
+ window.addEventListener("message", onMessage);
2872
+ const closedPoll = setInterval(() => {
2873
+ if (popup.closed) finish(() => reject(new UserCancelledError()));
2874
+ }, 400);
2875
+ const timeout = setTimeout(
2876
+ () => finish(
2877
+ () => reject(new CallSequenceError("KingsChat sign-in timed out before returning a token."))
2878
+ ),
2879
+ opts.timeoutMs ?? 5 * 6e4
2880
+ );
2881
+ });
2882
+ }
2883
+
2729
2884
  // src/social/signers/evm.ts
2730
2885
  import { keccak256, Transaction, resolveProperties } from "ethers";
2731
2886
  async function signEvmTransaction(tee, chain, tx, jwt) {
@@ -3088,7 +3243,7 @@ async function recoverDeviceShare(input) {
3088
3243
  prfSalt: backup.prfSalt,
3089
3244
  version: 1
3090
3245
  });
3091
- return deviceShare;
3246
+ return { deviceShare, flow: "passkey-sync" };
3092
3247
  } catch (e) {
3093
3248
  input.onProgress?.({
3094
3249
  type: "passkey-failed",
@@ -3116,7 +3271,7 @@ async function recoverDeviceShare(input) {
3116
3271
  input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
3117
3272
  await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
3118
3273
  zeroize(result.recoveryShare);
3119
- return result.deviceShare;
3274
+ return { deviceShare: result.deviceShare, flow: "recovery-file" };
3120
3275
  }
3121
3276
 
3122
3277
  // src/social/recovery/portableBackup.ts
@@ -3234,6 +3389,11 @@ async function createDecaneConnect(config) {
3234
3389
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
3235
3390
  if (!config.chains?.length)
3236
3391
  throw new Error("decane-connect-kit: at least one chain is required");
3392
+ if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
3393
+ throw new Error(
3394
+ "decane-connect-kit: rotateOnNewDeviceRecovery requires an onRecoveryRotated callback \u2014 rotating on device join invalidates the user's existing recovery file, and the replacement must be saved."
3395
+ );
3396
+ }
3237
3397
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
3238
3398
  const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
3239
3399
  const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
@@ -3253,6 +3413,7 @@ async function createDecaneConnect(config) {
3253
3413
  let persisted = loadPersisted(PERSIST_KEY);
3254
3414
  let ethProvider = null;
3255
3415
  const persistSession = config.persistSession !== false;
3416
+ const serverAssistedRecovery = config.serverAssistedRecovery !== false;
3256
3417
  const SESSION_KEY = sessionPersistKey(config.apiKey);
3257
3418
  function persistLiveSession() {
3258
3419
  if (!persistSession) return;
@@ -3478,6 +3639,17 @@ async function createDecaneConnect(config) {
3478
3639
  console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
3479
3640
  }
3480
3641
  }
3642
+ if (serverAssistedRecovery) {
3643
+ try {
3644
+ const tee = new TeeClient(teeUrl);
3645
+ await tee.enrolAuthShare(jwt, shareSet.recovery);
3646
+ } catch (e) {
3647
+ console.warn(
3648
+ "decane-connect-kit: could not enrol this wallet for server-assisted recovery; a recovery file will be required on a new device.",
3649
+ e
3650
+ );
3651
+ }
3652
+ }
3481
3653
  zeroize(shareSet.recovery);
3482
3654
  return addresses;
3483
3655
  }
@@ -3486,13 +3658,36 @@ async function createDecaneConnect(config) {
3486
3658
  const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
3487
3659
  const stored = await storage.getShare(userId);
3488
3660
  let deviceShare;
3661
+ let joinedViaPasskeySync = false;
3489
3662
  if (!stored) {
3663
+ if (serverAssistedRecovery) {
3664
+ try {
3665
+ const tee = new TeeClient(teeUrl);
3666
+ const provisioned = await tee.provisionDeviceShare(jwt);
3667
+ if (provisioned) {
3668
+ await registerPasskeyAndBackup(userId, jwt, provisioned).catch(() => {
3669
+ });
3670
+ let addrs;
3671
+ try {
3672
+ addrs = await session.start(provisioned, jwt, recordedAddresses);
3673
+ } finally {
3674
+ zeroize(provisioned);
3675
+ }
3676
+ return addrs;
3677
+ }
3678
+ } catch (e) {
3679
+ console.warn(
3680
+ "decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
3681
+ e
3682
+ );
3683
+ }
3684
+ }
3490
3685
  if (!config.onRecoveryRotated) {
3491
3686
  throw new CallSequenceError(
3492
3687
  "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."
3493
3688
  );
3494
3689
  }
3495
- deviceShare = await recoverDeviceShare({
3690
+ const recovered = await recoverDeviceShare({
3496
3691
  userId,
3497
3692
  jwt,
3498
3693
  rpId,
@@ -3510,6 +3705,8 @@ async function createDecaneConnect(config) {
3510
3705
  await api.markRecoveryShareSaved(jwt);
3511
3706
  }
3512
3707
  });
3708
+ deviceShare = recovered.deviceShare;
3709
+ joinedViaPasskeySync = recovered.flow === "passkey-sync";
3513
3710
  if (!await storage.getShare(userId) && isPasskeySupported()) {
3514
3711
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
3515
3712
  }
@@ -3525,15 +3722,66 @@ async function createDecaneConnect(config) {
3525
3722
  throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
3526
3723
  }
3527
3724
  }
3528
- const addresses = await session.start(deviceShare, jwt, recordedAddresses);
3725
+ let addresses;
3726
+ try {
3727
+ addresses = await session.start(deviceShare, jwt, recordedAddresses);
3728
+ } catch (e) {
3729
+ if (e instanceof StaleDeviceShareError) {
3730
+ zeroize(deviceShare);
3731
+ await storage.deleteShare(userId);
3732
+ if (stored) return returningUserUnlock(userId, jwt);
3733
+ } else {
3734
+ zeroize(deviceShare);
3735
+ }
3736
+ throw e;
3737
+ }
3529
3738
  zeroize(deviceShare);
3530
3739
  if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
3531
3740
  console.warn(
3532
3741
  `decane-connect-kit: recorded EVM address (${recordedAddresses.evm}) does not match the signing key (${addresses.evm}). Using the signing key's address; the record has been corrected.`
3533
3742
  );
3534
3743
  }
3744
+ if (joinedViaPasskeySync && config.rotateOnNewDeviceRecovery) {
3745
+ await rotateForNewDevice(userId, jwt);
3746
+ }
3535
3747
  return addresses;
3536
3748
  }
3749
+ async function rotateForNewDevice(userId, jwt) {
3750
+ const tee = session.requireActive();
3751
+ const result = await tee.rotateShares(jwt);
3752
+ try {
3753
+ await persistRotatedDeviceShare(userId, result.deviceShare);
3754
+ const refreshed = await storage.getShare(userId);
3755
+ if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
3756
+ await api.putRecoveryBackup(jwt, {
3757
+ wrappedDeviceShare: refreshed.wrappedDeviceShare,
3758
+ iv: refreshed.iv,
3759
+ unlockMethod: "passkey",
3760
+ credentialId: refreshed.credentialId,
3761
+ prfSalt: refreshed.prfSalt
3762
+ }).catch((e) => {
3763
+ console.warn("decane-connect-kit: backup refresh after device rotation failed; the next new device will need the recovery file.", e);
3764
+ });
3765
+ }
3766
+ const addrs = await deriveAddressesFromShares(result.deviceShare, result.recoveryShare);
3767
+ const { password, passwordHint } = await config.onRecoveryRotated({
3768
+ evmAddress: addrs.evm,
3769
+ solanaAddress: addrs.solana
3770
+ });
3771
+ const file = await buildRecoveryShareFile(
3772
+ result.recoveryShare,
3773
+ password,
3774
+ addrs,
3775
+ result.shareSetVersion,
3776
+ passwordHint
3777
+ );
3778
+ await config.onRecoveryFileReady?.(file, recoveryShareFileName(addrs.evm));
3779
+ await api.markRecoveryShareSaved(jwt);
3780
+ } finally {
3781
+ zeroize(result.deviceShare);
3782
+ zeroize(result.recoveryShare);
3783
+ }
3784
+ }
3537
3785
  async function passkeyLoginCandidate() {
3538
3786
  if (!persisted?.userId || !isPasskeySupported()) return null;
3539
3787
  const stored = await storage.getShare(persisted.userId);
@@ -3608,6 +3856,7 @@ async function createDecaneConnect(config) {
3608
3856
  const method = config.authMethods[0];
3609
3857
  if (!method) throw new CallSequenceError("No authMethods configured");
3610
3858
  if (method === "google") return client.connectWithGoogle();
3859
+ if (method === "kingschat") return client.connectWithKingsChat();
3611
3860
  if (method === "email")
3612
3861
  throw new CallSequenceError(
3613
3862
  "Use connectWithEmail(email) for email auth"
@@ -3637,6 +3886,25 @@ async function createDecaneConnect(config) {
3637
3886
  return new Promise(() => {
3638
3887
  });
3639
3888
  },
3889
+ async connectWithGoogleToken(idToken) {
3890
+ if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
3891
+ return handleAuthResult(await api.authGoogleToken(idToken));
3892
+ },
3893
+ async connectWithKingsChat() {
3894
+ const { clientId } = await api.getKingsChatConfig();
3895
+ const tokens2 = await loginWithKingsChatPopup({
3896
+ clientId,
3897
+ scopes: config.kingsChatScopes
3898
+ });
3899
+ return client.connectWithKingsChatToken(tokens2.accessToken);
3900
+ },
3901
+ async connectWithKingsChatToken(accessToken) {
3902
+ if (!accessToken) {
3903
+ throw new CallSequenceError("connectWithKingsChatToken requires an access token");
3904
+ }
3905
+ const result = await api.authKingsChat(accessToken);
3906
+ return handleAuthResult(result);
3907
+ },
3640
3908
  async connectWithToken(token, opts) {
3641
3909
  if (!token) throw new CallSequenceError("connectWithToken requires a token");
3642
3910
  const result = await api.authExternal(token, opts?.providerId);
@@ -4409,6 +4677,7 @@ function WalletSelector({
4409
4677
  mode = "wallets",
4410
4678
  socialAuthMethods = ["google", "email"],
4411
4679
  onGoogleSignIn,
4680
+ onKingsChatSignIn,
4412
4681
  onEmailStart,
4413
4682
  onEmailVerify,
4414
4683
  customAuthLabel,
@@ -4486,6 +4755,18 @@ function WalletSelector({
4486
4755
  setSocialLoad(false);
4487
4756
  }
4488
4757
  }
4758
+ async function handleKingsChatClick() {
4759
+ if (!onKingsChatSignIn) return;
4760
+ setSocialLoad(true);
4761
+ setSocialErr("");
4762
+ try {
4763
+ await onKingsChatSignIn();
4764
+ } catch (e) {
4765
+ setSocialErr(e.message);
4766
+ } finally {
4767
+ setSocialLoad(false);
4768
+ }
4769
+ }
4489
4770
  async function handleCustomAuthClick() {
4490
4771
  if (!onCustomAuthSignIn) return;
4491
4772
  setSocialLoad(true);
@@ -4908,6 +5189,38 @@ function WalletSelector({
4908
5189
  },
4909
5190
  /* @__PURE__ */ React2.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none" }, /* @__PURE__ */ React2.createElement("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z", fill: "#4285F4" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z", fill: "#34A853" }), /* @__PURE__ */ React2.createElement("path", { d: "M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z", fill: "#FBBC05" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 6.29C4.672 4.163 6.656 3.58 9 3.58z", fill: "#EA4335" })),
4910
5191
  socialLoad ? "Redirecting\u2026" : "Continue with Google"
5192
+ ), !passkeyFirst && socialAuthMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ React2.createElement(
5193
+ "button",
5194
+ {
5195
+ onClick: handleKingsChatClick,
5196
+ disabled: socialLoad,
5197
+ style: {
5198
+ display: "flex",
5199
+ alignItems: "center",
5200
+ justifyContent: "center",
5201
+ gap: 10,
5202
+ width: "100%",
5203
+ padding: "11px 16px",
5204
+ background: "var(--dc-surface)",
5205
+ border: "1px solid var(--dc-border)",
5206
+ borderRadius: 10,
5207
+ cursor: socialLoad ? "not-allowed" : "pointer",
5208
+ fontSize: 14,
5209
+ fontWeight: 600,
5210
+ color: "var(--dc-text)",
5211
+ fontFamily: "inherit",
5212
+ transition: "border-color 0.12s, background 0.12s",
5213
+ opacity: socialLoad ? 0.6 : 1
5214
+ },
5215
+ onMouseEnter: (e) => {
5216
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
5217
+ },
5218
+ onMouseLeave: (e) => {
5219
+ e.currentTarget.style.borderColor = "var(--dc-border)";
5220
+ }
5221
+ },
5222
+ /* @__PURE__ */ React2.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("circle", { cx: "9", cy: "9", r: "9", fill: "var(--dc-accent)" }), /* @__PURE__ */ React2.createElement("path", { d: "M5.5 4.5h1.9v3.6l3.1-3.6h2.3l-3.3 3.8 3.5 5.2h-2.3l-2.5-3.8-.8.9v2.9H5.5V4.5z", fill: "var(--dc-accent-text)" })),
5223
+ socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
4911
5224
  ), !passkeyFirst && socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), !passkeyFirst && socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ React2.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React2.createElement(
4912
5225
  "input",
4913
5226
  {
@@ -5154,6 +5467,7 @@ function AddressRow({
5154
5467
  }
5155
5468
  function SignInView({
5156
5469
  onGoogleSignIn,
5470
+ onKingsChatSignIn,
5157
5471
  onEmailStart,
5158
5472
  onEmailVerify,
5159
5473
  authMethods,
@@ -5165,6 +5479,7 @@ function SignInView({
5165
5479
  onPasskeySignIn
5166
5480
  }) {
5167
5481
  const [googleLoading, setGoogleLoading] = useState2(false);
5482
+ const [kingsChatLoading, setKingsChatLoading] = useState2(false);
5168
5483
  const [email, setEmail] = useState2("");
5169
5484
  const [code, setCode] = useState2("");
5170
5485
  const [emailSent, setEmailSent] = useState2(false);
@@ -5199,6 +5514,18 @@ function SignInView({
5199
5514
  setGoogleLoading(false);
5200
5515
  }
5201
5516
  }
5517
+ async function handleKingsChat() {
5518
+ if (!onKingsChatSignIn) return;
5519
+ setError("");
5520
+ setKingsChatLoading(true);
5521
+ try {
5522
+ await onKingsChatSignIn();
5523
+ } catch (e) {
5524
+ setError(e.message);
5525
+ } finally {
5526
+ setKingsChatLoading(false);
5527
+ }
5528
+ }
5202
5529
  async function handleEmailStart(e) {
5203
5530
  e.preventDefault();
5204
5531
  setError("");
@@ -5309,6 +5636,27 @@ function SignInView({
5309
5636
  }
5310
5637
  },
5311
5638
  googleLoading ? "Redirecting\u2026" : "Continue with Google"
5639
+ ), !passkeyFirst && authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ React3.createElement(
5640
+ "button",
5641
+ {
5642
+ onClick: handleKingsChat,
5643
+ disabled: kingsChatLoading,
5644
+ style: {
5645
+ width: "100%",
5646
+ padding: "10px 0",
5647
+ marginBottom: 10,
5648
+ background: "var(--dc-surface)",
5649
+ color: "var(--dc-text)",
5650
+ border: "1px solid var(--dc-border)",
5651
+ borderRadius: 10,
5652
+ fontSize: 13,
5653
+ fontWeight: 700,
5654
+ cursor: kingsChatLoading ? "not-allowed" : "pointer",
5655
+ opacity: kingsChatLoading ? 0.7 : 1,
5656
+ fontFamily: "inherit"
5657
+ }
5658
+ },
5659
+ kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
5312
5660
  ), !passkeyFirst && authMethods.includes("email") && /* @__PURE__ */ React3.createElement(React3.Fragment, null, authMethods.includes("google") && /* @__PURE__ */ React3.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), !emailSent ? /* @__PURE__ */ React3.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", gap: 8 } }, /* @__PURE__ */ React3.createElement(
5313
5661
  "input",
5314
5662
  {
@@ -5482,6 +5830,7 @@ function SocialWalletModal({
5482
5830
  onUnlock,
5483
5831
  onDisconnect,
5484
5832
  onGoogleSignIn,
5833
+ onKingsChatSignIn,
5485
5834
  onEmailStart,
5486
5835
  onEmailVerify,
5487
5836
  authMethods = ["google", "email"],
@@ -5615,6 +5964,7 @@ function SocialWalletModal({
5615
5964
  SignInView,
5616
5965
  {
5617
5966
  onGoogleSignIn,
5967
+ onKingsChatSignIn,
5618
5968
  onEmailStart,
5619
5969
  onEmailVerify,
5620
5970
  authMethods,
@@ -5943,6 +6293,12 @@ function DecaneKit({ config, children }) {
5943
6293
  if (!sdk) throw new Error("Social sign-in not configured");
5944
6294
  await sdk.connectWithGoogle();
5945
6295
  }, []);
6296
+ const connectWithKingsChat = useCallback3(async () => {
6297
+ const sdk = socialRef.current;
6298
+ if (!sdk) throw new Error("Social sign-in not configured");
6299
+ const result = await sdk.connectWithKingsChat();
6300
+ applyResult(result);
6301
+ }, [applyResult]);
5946
6302
  const connectWithEmail = useCallback3(async (email) => {
5947
6303
  const sdk = socialRef.current;
5948
6304
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -6109,6 +6465,7 @@ function DecaneKit({ config, children }) {
6109
6465
  socialAuth,
6110
6466
  signInWithPasskey,
6111
6467
  connectWithGoogle,
6468
+ connectWithKingsChat,
6112
6469
  connectWithEmail,
6113
6470
  verifyEmailCode: verifyEmailCode2,
6114
6471
  connectWithToken,
@@ -6151,6 +6508,7 @@ function DecaneKit({ config, children }) {
6151
6508
  mode,
6152
6509
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
6153
6510
  onGoogleSignIn: connectWithGoogle,
6511
+ onKingsChatSignIn: connectWithKingsChat,
6154
6512
  onEmailStart: connectWithEmail,
6155
6513
  onEmailVerify: verifyEmailCode2,
6156
6514
  canUsePasskey: socialAuth.canUsePasskey,
@@ -6172,6 +6530,7 @@ function DecaneKit({ config, children }) {
6172
6530
  customAuthLabel: customAuthConfig?.label,
6173
6531
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
6174
6532
  onGoogleSignIn: connectWithGoogle,
6533
+ onKingsChatSignIn: connectWithKingsChat,
6175
6534
  onEmailStart: connectWithEmail,
6176
6535
  onEmailVerify: verifyEmailCode2,
6177
6536
  authMethods: config?.social?.authMethods ?? ["google", "email"],
@@ -6815,6 +7174,7 @@ export {
6815
7174
  SessionExpiredError,
6816
7175
  ShareCorruptedError,
6817
7176
  SocialWalletModal,
7177
+ StaleDeviceShareError,
6818
7178
  UnlockRequiredError,
6819
7179
  UserCancelledError,
6820
7180
  WalletSelector,
@@ -6823,6 +7183,7 @@ export {
6823
7183
  createDecane,
6824
7184
  createDecaneConnect,
6825
7185
  createEthereumProvider,
7186
+ loginWithKingsChatPopup,
6826
7187
  startEmailAuth,
6827
7188
  useChainSwitch,
6828
7189
  useDecane,