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.cjs CHANGED
@@ -49,12 +49,13 @@ __export(errors_exports, {
49
49
  RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
50
50
  SessionExpiredError: () => SessionExpiredError,
51
51
  ShareCorruptedError: () => ShareCorruptedError,
52
+ StaleDeviceShareError: () => StaleDeviceShareError,
52
53
  UnlockRequiredError: () => UnlockRequiredError,
53
54
  UserCancelledError: () => UserCancelledError,
54
55
  WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
55
56
  WrongPasswordError: () => WrongPasswordError
56
57
  });
57
- var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
58
+ var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
58
59
  var init_errors = __esm({
59
60
  "src/social/errors.ts"() {
60
61
  "use strict";
@@ -130,6 +131,15 @@ var init_errors = __esm({
130
131
  super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
131
132
  }
132
133
  };
134
+ StaleDeviceShareError = class extends DecaneConnectError {
135
+ constructor() {
136
+ super(
137
+ "STALE_DEVICE_SHARE",
138
+ "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.",
139
+ true
140
+ );
141
+ }
142
+ };
133
143
  PromptPinNotConfiguredError = class extends DecaneConnectError {
134
144
  constructor() {
135
145
  super("PROMPT_PIN_NOT_CONFIGURED", "Passkey unavailable and no promptPin callback was provided in config");
@@ -200,6 +210,7 @@ __export(index_exports, {
200
210
  SessionExpiredError: () => SessionExpiredError,
201
211
  ShareCorruptedError: () => ShareCorruptedError,
202
212
  SocialWalletModal: () => SocialWalletModal,
213
+ StaleDeviceShareError: () => StaleDeviceShareError,
203
214
  UnlockRequiredError: () => UnlockRequiredError,
204
215
  UserCancelledError: () => UserCancelledError,
205
216
  WalletSelector: () => WalletSelector,
@@ -208,6 +219,7 @@ __export(index_exports, {
208
219
  createDecane: () => createDecane,
209
220
  createDecaneConnect: () => createDecaneConnect,
210
221
  createEthereumProvider: () => createEthereumProvider,
222
+ loginWithKingsChatPopup: () => loginWithKingsChatPopup,
211
223
  startEmailAuth: () => startEmailAuth,
212
224
  useChainSwitch: () => useChainSwitch,
213
225
  useDecane: () => useDecane,
@@ -2030,6 +2042,32 @@ var ApiClient = class {
2030
2042
  async getGoogleInitUrl() {
2031
2043
  return this._request("/auth/google/init", { method: "GET" });
2032
2044
  }
2045
+ /**
2046
+ * Exchange a Google ID token the app already holds for a Decane session.
2047
+ * Resolves to the SAME identity as the redirect flow, so one Google account
2048
+ * means one wallet across web and mobile.
2049
+ */
2050
+ async authGoogleToken(idToken) {
2051
+ return this._request("/auth/google/token", {
2052
+ method: "POST",
2053
+ body: JSON.stringify({ idToken })
2054
+ });
2055
+ }
2056
+ /** The project's KingsChat clientId, so the dApp never has to hardcode it. */
2057
+ async getKingsChatConfig() {
2058
+ return this._request("/auth/kingschat/config", { method: "GET" });
2059
+ }
2060
+ /**
2061
+ * Exchange a KingsChat access token for a Decane session. The backend spends
2062
+ * the token against KingsChat's own API to establish the identity — it is
2063
+ * never trusted on its face, and never stored.
2064
+ */
2065
+ async authKingsChat(accessToken) {
2066
+ return this._request("/auth/kingschat", {
2067
+ method: "POST",
2068
+ body: JSON.stringify({ accessToken })
2069
+ });
2070
+ }
2033
2071
  async authEmailStart(email) {
2034
2072
  await this._request("/auth/email/start", {
2035
2073
  method: "POST",
@@ -2354,6 +2392,13 @@ var TeeClient = class {
2354
2392
  requireAssertionPerSignature
2355
2393
  })
2356
2394
  });
2395
+ if (res.status === 403) {
2396
+ const body2 = await res.json().catch(() => ({}));
2397
+ if (body2?.error === "share_mismatch") {
2398
+ const { StaleDeviceShareError: StaleDeviceShareError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
2399
+ throw new StaleDeviceShareError2();
2400
+ }
2401
+ }
2357
2402
  if (!res.ok) throw new Error("session open failed");
2358
2403
  const body = await res.json();
2359
2404
  this.handle = body.handle;
@@ -2413,6 +2458,45 @@ var TeeClient = class {
2413
2458
  }
2414
2459
  return { challengeId, assertion };
2415
2460
  }
2461
+ /**
2462
+ * Enrol this wallet for server-assisted provisioning by handing the enclave
2463
+ * the recovery share to seal.
2464
+ *
2465
+ * After this, a new device gets a working share from login alone — no passkey
2466
+ * sync, no recovery file. The enclave seals the share under a key only it can
2467
+ * derive, so the backend stores ciphertext it cannot read; but the enclave
2468
+ * then holds two of three shares and can reconstruct on its own. That is the
2469
+ * trade this call makes, which is why it is opt-out rather than implicit.
2470
+ */
2471
+ async enrolAuthShare(jwt, recoveryShare) {
2472
+ await this.ensureAttested();
2473
+ const res = await fetch(`${this.teeUrl}/signer/auth-share`, {
2474
+ method: "POST",
2475
+ headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2476
+ body: JSON.stringify({ share: bytesToBase64(recoveryShare) })
2477
+ });
2478
+ if (!res.ok) throw new Error("auth share enrolment failed");
2479
+ }
2480
+ /**
2481
+ * Ask the enclave for a fresh device share for this authenticated user.
2482
+ * Returns null when the wallet was never enrolled — the signal to fall back
2483
+ * to passkey sync or the recovery file rather than fail.
2484
+ *
2485
+ * Every successful call re-splits the set, so any share this device or any
2486
+ * other held is now dead. Intentional: a leaked device share stops working the
2487
+ * next time the user signs in anywhere.
2488
+ */
2489
+ async provisionDeviceShare(jwt) {
2490
+ await this.ensureAttested();
2491
+ const res = await fetch(`${this.teeUrl}/signer/provision`, {
2492
+ method: "POST",
2493
+ headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
2494
+ });
2495
+ if (res.status === 409) return null;
2496
+ if (!res.ok) throw new Error("device share provisioning failed");
2497
+ const body = await res.json();
2498
+ return base64ToBytes(body.deviceShare);
2499
+ }
2416
2500
  /** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
2417
2501
  async rotateShares(jwt) {
2418
2502
  if (!this.handle) throw new Error("no session");
@@ -2799,6 +2883,79 @@ async function verifyEmailCode(api, email, code) {
2799
2883
  return api.authEmailVerify(email, code);
2800
2884
  }
2801
2885
 
2886
+ // src/social/auth/kingschat.ts
2887
+ init_errors();
2888
+ var KINGSCHAT_AUTH_ORIGIN = "https://accounts.kingsch.at";
2889
+ function readTokens(data) {
2890
+ if (!data || typeof data !== "object") return null;
2891
+ const d = data;
2892
+ const accessToken = d.accessToken ?? d.access_token;
2893
+ if (typeof accessToken !== "string" || !accessToken) return null;
2894
+ const refreshToken = d.refreshToken ?? d.refresh_token;
2895
+ const expires = d.expiresInMillis ?? d.expires_in_millis;
2896
+ return {
2897
+ accessToken,
2898
+ refreshToken: typeof refreshToken === "string" ? refreshToken : void 0,
2899
+ expiresInMillis: typeof expires === "number" ? expires : void 0
2900
+ };
2901
+ }
2902
+ function buildAuthUrl(clientId, scopes) {
2903
+ const url = new URL(KINGSCHAT_AUTH_ORIGIN);
2904
+ url.searchParams.set("client_id", clientId);
2905
+ url.searchParams.set("scopes", JSON.stringify(scopes));
2906
+ url.searchParams.set("redirect_uri", window.location.origin);
2907
+ url.searchParams.set("post_message", "1");
2908
+ return url.toString();
2909
+ }
2910
+ function loginWithKingsChatPopup(opts) {
2911
+ const scopes = opts.scopes ?? ["send_chat_message"];
2912
+ const popup = window.open(
2913
+ buildAuthUrl(opts.clientId, scopes),
2914
+ "decane-kingschat",
2915
+ "width=480,height=720,menubar=no,toolbar=no"
2916
+ );
2917
+ if (!popup) {
2918
+ throw new CallSequenceError(
2919
+ "The KingsChat sign-in popup was blocked. Call connectWithKingsChat() directly from a click handler."
2920
+ );
2921
+ }
2922
+ return new Promise((resolve, reject) => {
2923
+ let settled = false;
2924
+ const finish = (fn) => {
2925
+ if (settled) return;
2926
+ settled = true;
2927
+ window.removeEventListener("message", onMessage);
2928
+ clearInterval(closedPoll);
2929
+ clearTimeout(timeout);
2930
+ try {
2931
+ popup.close();
2932
+ } catch {
2933
+ }
2934
+ fn();
2935
+ };
2936
+ const onMessage = (event) => {
2937
+ if (event.origin !== KINGSCHAT_AUTH_ORIGIN) return;
2938
+ const data = event.data;
2939
+ if (data && typeof data === "object" && data.error) {
2940
+ finish(() => reject(new CallSequenceError(`KingsChat sign-in failed: ${String(data.error)}`)));
2941
+ return;
2942
+ }
2943
+ const tokens2 = readTokens(event.data);
2944
+ if (tokens2) finish(() => resolve(tokens2));
2945
+ };
2946
+ window.addEventListener("message", onMessage);
2947
+ const closedPoll = setInterval(() => {
2948
+ if (popup.closed) finish(() => reject(new UserCancelledError()));
2949
+ }, 400);
2950
+ const timeout = setTimeout(
2951
+ () => finish(
2952
+ () => reject(new CallSequenceError("KingsChat sign-in timed out before returning a token."))
2953
+ ),
2954
+ opts.timeoutMs ?? 5 * 6e4
2955
+ );
2956
+ });
2957
+ }
2958
+
2802
2959
  // src/social/signers/evm.ts
2803
2960
  var import_ethers2 = require("ethers");
2804
2961
  async function signEvmTransaction(tee, chain, tx, jwt) {
@@ -3161,7 +3318,7 @@ async function recoverDeviceShare(input) {
3161
3318
  prfSalt: backup.prfSalt,
3162
3319
  version: 1
3163
3320
  });
3164
- return deviceShare;
3321
+ return { deviceShare, flow: "passkey-sync" };
3165
3322
  } catch (e) {
3166
3323
  input.onProgress?.({
3167
3324
  type: "passkey-failed",
@@ -3189,7 +3346,7 @@ async function recoverDeviceShare(input) {
3189
3346
  input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
3190
3347
  await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
3191
3348
  zeroize(result.recoveryShare);
3192
- return result.deviceShare;
3349
+ return { deviceShare: result.deviceShare, flow: "recovery-file" };
3193
3350
  }
3194
3351
 
3195
3352
  // src/social/recovery/portableBackup.ts
@@ -3307,6 +3464,11 @@ async function createDecaneConnect(config) {
3307
3464
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
3308
3465
  if (!config.chains?.length)
3309
3466
  throw new Error("decane-connect-kit: at least one chain is required");
3467
+ if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
3468
+ throw new Error(
3469
+ "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."
3470
+ );
3471
+ }
3310
3472
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
3311
3473
  const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
3312
3474
  const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
@@ -3326,6 +3488,7 @@ async function createDecaneConnect(config) {
3326
3488
  let persisted = loadPersisted(PERSIST_KEY);
3327
3489
  let ethProvider = null;
3328
3490
  const persistSession = config.persistSession !== false;
3491
+ const serverAssistedRecovery = config.serverAssistedRecovery !== false;
3329
3492
  const SESSION_KEY = sessionPersistKey(config.apiKey);
3330
3493
  function persistLiveSession() {
3331
3494
  if (!persistSession) return;
@@ -3551,6 +3714,17 @@ async function createDecaneConnect(config) {
3551
3714
  console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
3552
3715
  }
3553
3716
  }
3717
+ if (serverAssistedRecovery) {
3718
+ try {
3719
+ const tee = new TeeClient(teeUrl);
3720
+ await tee.enrolAuthShare(jwt, shareSet.recovery);
3721
+ } catch (e) {
3722
+ console.warn(
3723
+ "decane-connect-kit: could not enrol this wallet for server-assisted recovery; a recovery file will be required on a new device.",
3724
+ e
3725
+ );
3726
+ }
3727
+ }
3554
3728
  zeroize(shareSet.recovery);
3555
3729
  return addresses;
3556
3730
  }
@@ -3559,13 +3733,36 @@ async function createDecaneConnect(config) {
3559
3733
  const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
3560
3734
  const stored = await storage.getShare(userId);
3561
3735
  let deviceShare;
3736
+ let joinedViaPasskeySync = false;
3562
3737
  if (!stored) {
3738
+ if (serverAssistedRecovery) {
3739
+ try {
3740
+ const tee = new TeeClient(teeUrl);
3741
+ const provisioned = await tee.provisionDeviceShare(jwt);
3742
+ if (provisioned) {
3743
+ await registerPasskeyAndBackup(userId, jwt, provisioned).catch(() => {
3744
+ });
3745
+ let addrs;
3746
+ try {
3747
+ addrs = await session.start(provisioned, jwt, recordedAddresses);
3748
+ } finally {
3749
+ zeroize(provisioned);
3750
+ }
3751
+ return addrs;
3752
+ }
3753
+ } catch (e) {
3754
+ console.warn(
3755
+ "decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
3756
+ e
3757
+ );
3758
+ }
3759
+ }
3563
3760
  if (!config.onRecoveryRotated) {
3564
3761
  throw new CallSequenceError(
3565
3762
  "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."
3566
3763
  );
3567
3764
  }
3568
- deviceShare = await recoverDeviceShare({
3765
+ const recovered = await recoverDeviceShare({
3569
3766
  userId,
3570
3767
  jwt,
3571
3768
  rpId,
@@ -3583,6 +3780,8 @@ async function createDecaneConnect(config) {
3583
3780
  await api.markRecoveryShareSaved(jwt);
3584
3781
  }
3585
3782
  });
3783
+ deviceShare = recovered.deviceShare;
3784
+ joinedViaPasskeySync = recovered.flow === "passkey-sync";
3586
3785
  if (!await storage.getShare(userId) && isPasskeySupported()) {
3587
3786
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
3588
3787
  }
@@ -3598,15 +3797,66 @@ async function createDecaneConnect(config) {
3598
3797
  throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
3599
3798
  }
3600
3799
  }
3601
- const addresses = await session.start(deviceShare, jwt, recordedAddresses);
3800
+ let addresses;
3801
+ try {
3802
+ addresses = await session.start(deviceShare, jwt, recordedAddresses);
3803
+ } catch (e) {
3804
+ if (e instanceof StaleDeviceShareError) {
3805
+ zeroize(deviceShare);
3806
+ await storage.deleteShare(userId);
3807
+ if (stored) return returningUserUnlock(userId, jwt);
3808
+ } else {
3809
+ zeroize(deviceShare);
3810
+ }
3811
+ throw e;
3812
+ }
3602
3813
  zeroize(deviceShare);
3603
3814
  if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
3604
3815
  console.warn(
3605
3816
  `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.`
3606
3817
  );
3607
3818
  }
3819
+ if (joinedViaPasskeySync && config.rotateOnNewDeviceRecovery) {
3820
+ await rotateForNewDevice(userId, jwt);
3821
+ }
3608
3822
  return addresses;
3609
3823
  }
3824
+ async function rotateForNewDevice(userId, jwt) {
3825
+ const tee = session.requireActive();
3826
+ const result = await tee.rotateShares(jwt);
3827
+ try {
3828
+ await persistRotatedDeviceShare(userId, result.deviceShare);
3829
+ const refreshed = await storage.getShare(userId);
3830
+ if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
3831
+ await api.putRecoveryBackup(jwt, {
3832
+ wrappedDeviceShare: refreshed.wrappedDeviceShare,
3833
+ iv: refreshed.iv,
3834
+ unlockMethod: "passkey",
3835
+ credentialId: refreshed.credentialId,
3836
+ prfSalt: refreshed.prfSalt
3837
+ }).catch((e) => {
3838
+ console.warn("decane-connect-kit: backup refresh after device rotation failed; the next new device will need the recovery file.", e);
3839
+ });
3840
+ }
3841
+ const addrs = await deriveAddressesFromShares(result.deviceShare, result.recoveryShare);
3842
+ const { password, passwordHint } = await config.onRecoveryRotated({
3843
+ evmAddress: addrs.evm,
3844
+ solanaAddress: addrs.solana
3845
+ });
3846
+ const file = await buildRecoveryShareFile(
3847
+ result.recoveryShare,
3848
+ password,
3849
+ addrs,
3850
+ result.shareSetVersion,
3851
+ passwordHint
3852
+ );
3853
+ await config.onRecoveryFileReady?.(file, recoveryShareFileName(addrs.evm));
3854
+ await api.markRecoveryShareSaved(jwt);
3855
+ } finally {
3856
+ zeroize(result.deviceShare);
3857
+ zeroize(result.recoveryShare);
3858
+ }
3859
+ }
3610
3860
  async function passkeyLoginCandidate() {
3611
3861
  if (!persisted?.userId || !isPasskeySupported()) return null;
3612
3862
  const stored = await storage.getShare(persisted.userId);
@@ -3681,6 +3931,7 @@ async function createDecaneConnect(config) {
3681
3931
  const method = config.authMethods[0];
3682
3932
  if (!method) throw new CallSequenceError("No authMethods configured");
3683
3933
  if (method === "google") return client.connectWithGoogle();
3934
+ if (method === "kingschat") return client.connectWithKingsChat();
3684
3935
  if (method === "email")
3685
3936
  throw new CallSequenceError(
3686
3937
  "Use connectWithEmail(email) for email auth"
@@ -3710,6 +3961,25 @@ async function createDecaneConnect(config) {
3710
3961
  return new Promise(() => {
3711
3962
  });
3712
3963
  },
3964
+ async connectWithGoogleToken(idToken) {
3965
+ if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
3966
+ return handleAuthResult(await api.authGoogleToken(idToken));
3967
+ },
3968
+ async connectWithKingsChat() {
3969
+ const { clientId } = await api.getKingsChatConfig();
3970
+ const tokens2 = await loginWithKingsChatPopup({
3971
+ clientId,
3972
+ scopes: config.kingsChatScopes
3973
+ });
3974
+ return client.connectWithKingsChatToken(tokens2.accessToken);
3975
+ },
3976
+ async connectWithKingsChatToken(accessToken) {
3977
+ if (!accessToken) {
3978
+ throw new CallSequenceError("connectWithKingsChatToken requires an access token");
3979
+ }
3980
+ const result = await api.authKingsChat(accessToken);
3981
+ return handleAuthResult(result);
3982
+ },
3713
3983
  async connectWithToken(token, opts) {
3714
3984
  if (!token) throw new CallSequenceError("connectWithToken requires a token");
3715
3985
  const result = await api.authExternal(token, opts?.providerId);
@@ -4482,6 +4752,7 @@ function WalletSelector({
4482
4752
  mode = "wallets",
4483
4753
  socialAuthMethods = ["google", "email"],
4484
4754
  onGoogleSignIn,
4755
+ onKingsChatSignIn,
4485
4756
  onEmailStart,
4486
4757
  onEmailVerify,
4487
4758
  customAuthLabel,
@@ -4559,6 +4830,18 @@ function WalletSelector({
4559
4830
  setSocialLoad(false);
4560
4831
  }
4561
4832
  }
4833
+ async function handleKingsChatClick() {
4834
+ if (!onKingsChatSignIn) return;
4835
+ setSocialLoad(true);
4836
+ setSocialErr("");
4837
+ try {
4838
+ await onKingsChatSignIn();
4839
+ } catch (e) {
4840
+ setSocialErr(e.message);
4841
+ } finally {
4842
+ setSocialLoad(false);
4843
+ }
4844
+ }
4562
4845
  async function handleCustomAuthClick() {
4563
4846
  if (!onCustomAuthSignIn) return;
4564
4847
  setSocialLoad(true);
@@ -4981,6 +5264,38 @@ function WalletSelector({
4981
5264
  },
4982
5265
  /* @__PURE__ */ import_react2.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none" }, /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.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__ */ import_react2.default.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__ */ import_react2.default.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" })),
4983
5266
  socialLoad ? "Redirecting\u2026" : "Continue with Google"
5267
+ ), !passkeyFirst && socialAuthMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ import_react2.default.createElement(
5268
+ "button",
5269
+ {
5270
+ onClick: handleKingsChatClick,
5271
+ disabled: socialLoad,
5272
+ style: {
5273
+ display: "flex",
5274
+ alignItems: "center",
5275
+ justifyContent: "center",
5276
+ gap: 10,
5277
+ width: "100%",
5278
+ padding: "11px 16px",
5279
+ background: "var(--dc-surface)",
5280
+ border: "1px solid var(--dc-border)",
5281
+ borderRadius: 10,
5282
+ cursor: socialLoad ? "not-allowed" : "pointer",
5283
+ fontSize: 14,
5284
+ fontWeight: 600,
5285
+ color: "var(--dc-text)",
5286
+ fontFamily: "inherit",
5287
+ transition: "border-color 0.12s, background 0.12s",
5288
+ opacity: socialLoad ? 0.6 : 1
5289
+ },
5290
+ onMouseEnter: (e) => {
5291
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
5292
+ },
5293
+ onMouseLeave: (e) => {
5294
+ e.currentTarget.style.borderColor = "var(--dc-border)";
5295
+ }
5296
+ },
5297
+ /* @__PURE__ */ import_react2.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "9", cy: "9", r: "9", fill: "var(--dc-accent)" }), /* @__PURE__ */ import_react2.default.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)" })),
5298
+ socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
4984
5299
  ), !passkeyFirst && socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ import_react2.default.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), !passkeyFirst && socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ import_react2.default.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react2.default.createElement(
4985
5300
  "input",
4986
5301
  {
@@ -5227,6 +5542,7 @@ function AddressRow({
5227
5542
  }
5228
5543
  function SignInView({
5229
5544
  onGoogleSignIn,
5545
+ onKingsChatSignIn,
5230
5546
  onEmailStart,
5231
5547
  onEmailVerify,
5232
5548
  authMethods,
@@ -5238,6 +5554,7 @@ function SignInView({
5238
5554
  onPasskeySignIn
5239
5555
  }) {
5240
5556
  const [googleLoading, setGoogleLoading] = (0, import_react3.useState)(false);
5557
+ const [kingsChatLoading, setKingsChatLoading] = (0, import_react3.useState)(false);
5241
5558
  const [email, setEmail] = (0, import_react3.useState)("");
5242
5559
  const [code, setCode] = (0, import_react3.useState)("");
5243
5560
  const [emailSent, setEmailSent] = (0, import_react3.useState)(false);
@@ -5272,6 +5589,18 @@ function SignInView({
5272
5589
  setGoogleLoading(false);
5273
5590
  }
5274
5591
  }
5592
+ async function handleKingsChat() {
5593
+ if (!onKingsChatSignIn) return;
5594
+ setError("");
5595
+ setKingsChatLoading(true);
5596
+ try {
5597
+ await onKingsChatSignIn();
5598
+ } catch (e) {
5599
+ setError(e.message);
5600
+ } finally {
5601
+ setKingsChatLoading(false);
5602
+ }
5603
+ }
5275
5604
  async function handleEmailStart(e) {
5276
5605
  e.preventDefault();
5277
5606
  setError("");
@@ -5382,6 +5711,27 @@ function SignInView({
5382
5711
  }
5383
5712
  },
5384
5713
  googleLoading ? "Redirecting\u2026" : "Continue with Google"
5714
+ ), !passkeyFirst && authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ import_react3.default.createElement(
5715
+ "button",
5716
+ {
5717
+ onClick: handleKingsChat,
5718
+ disabled: kingsChatLoading,
5719
+ style: {
5720
+ width: "100%",
5721
+ padding: "10px 0",
5722
+ marginBottom: 10,
5723
+ background: "var(--dc-surface)",
5724
+ color: "var(--dc-text)",
5725
+ border: "1px solid var(--dc-border)",
5726
+ borderRadius: 10,
5727
+ fontSize: 13,
5728
+ fontWeight: 700,
5729
+ cursor: kingsChatLoading ? "not-allowed" : "pointer",
5730
+ opacity: kingsChatLoading ? 0.7 : 1,
5731
+ fontFamily: "inherit"
5732
+ }
5733
+ },
5734
+ kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
5385
5735
  ), !passkeyFirst && authMethods.includes("email") && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, authMethods.includes("google") && /* @__PURE__ */ import_react3.default.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), !emailSent ? /* @__PURE__ */ import_react3.default.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", gap: 8 } }, /* @__PURE__ */ import_react3.default.createElement(
5386
5736
  "input",
5387
5737
  {
@@ -5555,6 +5905,7 @@ function SocialWalletModal({
5555
5905
  onUnlock,
5556
5906
  onDisconnect,
5557
5907
  onGoogleSignIn,
5908
+ onKingsChatSignIn,
5558
5909
  onEmailStart,
5559
5910
  onEmailVerify,
5560
5911
  authMethods = ["google", "email"],
@@ -5688,6 +6039,7 @@ function SocialWalletModal({
5688
6039
  SignInView,
5689
6040
  {
5690
6041
  onGoogleSignIn,
6042
+ onKingsChatSignIn,
5691
6043
  onEmailStart,
5692
6044
  onEmailVerify,
5693
6045
  authMethods,
@@ -6009,6 +6361,12 @@ function DecaneKit({ config, children }) {
6009
6361
  if (!sdk) throw new Error("Social sign-in not configured");
6010
6362
  await sdk.connectWithGoogle();
6011
6363
  }, []);
6364
+ const connectWithKingsChat = (0, import_react5.useCallback)(async () => {
6365
+ const sdk = socialRef.current;
6366
+ if (!sdk) throw new Error("Social sign-in not configured");
6367
+ const result = await sdk.connectWithKingsChat();
6368
+ applyResult(result);
6369
+ }, [applyResult]);
6012
6370
  const connectWithEmail = (0, import_react5.useCallback)(async (email) => {
6013
6371
  const sdk = socialRef.current;
6014
6372
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -6175,6 +6533,7 @@ function DecaneKit({ config, children }) {
6175
6533
  socialAuth,
6176
6534
  signInWithPasskey,
6177
6535
  connectWithGoogle,
6536
+ connectWithKingsChat,
6178
6537
  connectWithEmail,
6179
6538
  verifyEmailCode: verifyEmailCode2,
6180
6539
  connectWithToken,
@@ -6217,6 +6576,7 @@ function DecaneKit({ config, children }) {
6217
6576
  mode,
6218
6577
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
6219
6578
  onGoogleSignIn: connectWithGoogle,
6579
+ onKingsChatSignIn: connectWithKingsChat,
6220
6580
  onEmailStart: connectWithEmail,
6221
6581
  onEmailVerify: verifyEmailCode2,
6222
6582
  canUsePasskey: socialAuth.canUsePasskey,
@@ -6238,6 +6598,7 @@ function DecaneKit({ config, children }) {
6238
6598
  customAuthLabel: customAuthConfig?.label,
6239
6599
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
6240
6600
  onGoogleSignIn: connectWithGoogle,
6601
+ onKingsChatSignIn: connectWithKingsChat,
6241
6602
  onEmailStart: connectWithEmail,
6242
6603
  onEmailVerify: verifyEmailCode2,
6243
6604
  authMethods: config?.social?.authMethods ?? ["google", "email"],
@@ -6882,6 +7243,7 @@ function useSignTypedData() {
6882
7243
  SessionExpiredError,
6883
7244
  ShareCorruptedError,
6884
7245
  SocialWalletModal,
7246
+ StaleDeviceShareError,
6885
7247
  UnlockRequiredError,
6886
7248
  UserCancelledError,
6887
7249
  WalletSelector,
@@ -6890,6 +7252,7 @@ function useSignTypedData() {
6890
7252
  createDecane,
6891
7253
  createDecaneConnect,
6892
7254
  createEthereumProvider,
7255
+ loginWithKingsChatPopup,
6893
7256
  startEmailAuth,
6894
7257
  useChainSwitch,
6895
7258
  useDecane,