decane-connect-kit 2.4.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",
@@ -1991,6 +2027,21 @@ var ApiClient = class {
1991
2027
  })
1992
2028
  });
1993
2029
  }
2030
+ // ── Transaction history ───────────────────────────────────────────────────
2031
+ async recordTransaction(jwt, tx) {
2032
+ const body = await this._request("/transactions", {
2033
+ method: "POST",
2034
+ headers: { Authorization: `Bearer ${jwt}` },
2035
+ body: JSON.stringify(tx)
2036
+ });
2037
+ return body.transaction;
2038
+ }
2039
+ async getTransactions(jwt, limit = 50) {
2040
+ const body = await this._request(`/transactions?limit=${encodeURIComponent(String(limit))}`, {
2041
+ headers: { Authorization: `Bearer ${jwt}` }
2042
+ });
2043
+ return body.transactions;
2044
+ }
1994
2045
  // ── WebAuthn registration (additive to the PRF-only passkey flow) ─────────
1995
2046
  async getWebAuthnRegisterOptions(jwt, rpId, rpName) {
1996
2047
  return this._request(`/webauthn/register-options?rpId=${encodeURIComponent(rpId)}&rpName=${encodeURIComponent(rpName)}`, {
@@ -2266,6 +2317,13 @@ var TeeClient = class {
2266
2317
  requireAssertionPerSignature
2267
2318
  })
2268
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
+ }
2269
2327
  if (!res.ok) throw new Error("session open failed");
2270
2328
  const body = await res.json();
2271
2329
  this.handle = body.handle;
@@ -2325,6 +2383,45 @@ var TeeClient = class {
2325
2383
  }
2326
2384
  return { challengeId, assertion };
2327
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
+ }
2328
2425
  /** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
2329
2426
  async rotateShares(jwt) {
2330
2427
  if (!this.handle) throw new Error("no session");
@@ -2711,6 +2808,79 @@ async function verifyEmailCode(api, email, code) {
2711
2808
  return api.authEmailVerify(email, code);
2712
2809
  }
2713
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
+
2714
2884
  // src/social/signers/evm.ts
2715
2885
  import { keccak256, Transaction, resolveProperties } from "ethers";
2716
2886
  async function signEvmTransaction(tee, chain, tx, jwt) {
@@ -3073,7 +3243,7 @@ async function recoverDeviceShare(input) {
3073
3243
  prfSalt: backup.prfSalt,
3074
3244
  version: 1
3075
3245
  });
3076
- return deviceShare;
3246
+ return { deviceShare, flow: "passkey-sync" };
3077
3247
  } catch (e) {
3078
3248
  input.onProgress?.({
3079
3249
  type: "passkey-failed",
@@ -3101,7 +3271,7 @@ async function recoverDeviceShare(input) {
3101
3271
  input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
3102
3272
  await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
3103
3273
  zeroize(result.recoveryShare);
3104
- return result.deviceShare;
3274
+ return { deviceShare: result.deviceShare, flow: "recovery-file" };
3105
3275
  }
3106
3276
 
3107
3277
  // src/social/recovery/portableBackup.ts
@@ -3219,6 +3389,11 @@ async function createDecaneConnect(config) {
3219
3389
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
3220
3390
  if (!config.chains?.length)
3221
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
+ }
3222
3397
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
3223
3398
  const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
3224
3399
  const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
@@ -3238,6 +3413,7 @@ async function createDecaneConnect(config) {
3238
3413
  let persisted = loadPersisted(PERSIST_KEY);
3239
3414
  let ethProvider = null;
3240
3415
  const persistSession = config.persistSession !== false;
3416
+ const serverAssistedRecovery = config.serverAssistedRecovery !== false;
3241
3417
  const SESSION_KEY = sessionPersistKey(config.apiKey);
3242
3418
  function persistLiveSession() {
3243
3419
  if (!persistSession) return;
@@ -3463,6 +3639,17 @@ async function createDecaneConnect(config) {
3463
3639
  console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
3464
3640
  }
3465
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
+ }
3466
3653
  zeroize(shareSet.recovery);
3467
3654
  return addresses;
3468
3655
  }
@@ -3471,13 +3658,36 @@ async function createDecaneConnect(config) {
3471
3658
  const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
3472
3659
  const stored = await storage.getShare(userId);
3473
3660
  let deviceShare;
3661
+ let joinedViaPasskeySync = false;
3474
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
+ }
3475
3685
  if (!config.onRecoveryRotated) {
3476
3686
  throw new CallSequenceError(
3477
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."
3478
3688
  );
3479
3689
  }
3480
- deviceShare = await recoverDeviceShare({
3690
+ const recovered = await recoverDeviceShare({
3481
3691
  userId,
3482
3692
  jwt,
3483
3693
  rpId,
@@ -3495,6 +3705,8 @@ async function createDecaneConnect(config) {
3495
3705
  await api.markRecoveryShareSaved(jwt);
3496
3706
  }
3497
3707
  });
3708
+ deviceShare = recovered.deviceShare;
3709
+ joinedViaPasskeySync = recovered.flow === "passkey-sync";
3498
3710
  if (!await storage.getShare(userId) && isPasskeySupported()) {
3499
3711
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
3500
3712
  }
@@ -3510,15 +3722,66 @@ async function createDecaneConnect(config) {
3510
3722
  throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
3511
3723
  }
3512
3724
  }
3513
- 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
+ }
3514
3738
  zeroize(deviceShare);
3515
3739
  if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
3516
3740
  console.warn(
3517
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.`
3518
3742
  );
3519
3743
  }
3744
+ if (joinedViaPasskeySync && config.rotateOnNewDeviceRecovery) {
3745
+ await rotateForNewDevice(userId, jwt);
3746
+ }
3520
3747
  return addresses;
3521
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
+ }
3522
3785
  async function passkeyLoginCandidate() {
3523
3786
  if (!persisted?.userId || !isPasskeySupported()) return null;
3524
3787
  const stored = await storage.getShare(persisted.userId);
@@ -3593,6 +3856,7 @@ async function createDecaneConnect(config) {
3593
3856
  const method = config.authMethods[0];
3594
3857
  if (!method) throw new CallSequenceError("No authMethods configured");
3595
3858
  if (method === "google") return client.connectWithGoogle();
3859
+ if (method === "kingschat") return client.connectWithKingsChat();
3596
3860
  if (method === "email")
3597
3861
  throw new CallSequenceError(
3598
3862
  "Use connectWithEmail(email) for email auth"
@@ -3622,6 +3886,25 @@ async function createDecaneConnect(config) {
3622
3886
  return new Promise(() => {
3623
3887
  });
3624
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
+ },
3625
3908
  async connectWithToken(token, opts) {
3626
3909
  if (!token) throw new CallSequenceError("connectWithToken requires a token");
3627
3910
  const result = await api.authExternal(token, opts?.providerId);
@@ -3733,6 +4016,16 @@ async function createDecaneConnect(config) {
3733
4016
  };
3734
4017
  const signedTx = await signEvmTransaction(tee, req.chain, tx, auth?.jwt);
3735
4018
  const txResponse = await provider.broadcastTransaction(signedTx);
4019
+ if (auth?.jwt) {
4020
+ void api.recordTransaction(auth.jwt, {
4021
+ chain: req.chain,
4022
+ txHash: txResponse.hash,
4023
+ to: req.to,
4024
+ value: (req.value ?? 0n).toString(),
4025
+ kind: "send"
4026
+ }).catch(() => {
4027
+ });
4028
+ }
3736
4029
  return txResponse.hash;
3737
4030
  },
3738
4031
  async signTypedData(req) {
@@ -3765,6 +4058,14 @@ async function createDecaneConnect(config) {
3765
4058
  getAccessToken() {
3766
4059
  return auth?.jwt ?? null;
3767
4060
  },
4061
+ async recordTransaction(tx) {
4062
+ if (!auth?.jwt) throw new CallSequenceError("Not connected \u2014 call connect() first");
4063
+ return api.recordTransaction(auth.jwt, tx);
4064
+ },
4065
+ async getTransactionHistory(limit = 50) {
4066
+ if (!auth?.jwt) throw new CallSequenceError("Not connected \u2014 call connect() first");
4067
+ return api.getTransactions(auth.jwt, limit);
4068
+ },
3768
4069
  getEthereumProvider(opts) {
3769
4070
  if (!ethProvider) {
3770
4071
  const firstEvm = config.chains.find((c) => c.startsWith("evm:"));
@@ -4376,6 +4677,7 @@ function WalletSelector({
4376
4677
  mode = "wallets",
4377
4678
  socialAuthMethods = ["google", "email"],
4378
4679
  onGoogleSignIn,
4680
+ onKingsChatSignIn,
4379
4681
  onEmailStart,
4380
4682
  onEmailVerify,
4381
4683
  customAuthLabel,
@@ -4453,6 +4755,18 @@ function WalletSelector({
4453
4755
  setSocialLoad(false);
4454
4756
  }
4455
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
+ }
4456
4770
  async function handleCustomAuthClick() {
4457
4771
  if (!onCustomAuthSignIn) return;
4458
4772
  setSocialLoad(true);
@@ -4875,6 +5189,38 @@ function WalletSelector({
4875
5189
  },
4876
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" })),
4877
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"
4878
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(
4879
5225
  "input",
4880
5226
  {
@@ -5121,6 +5467,7 @@ function AddressRow({
5121
5467
  }
5122
5468
  function SignInView({
5123
5469
  onGoogleSignIn,
5470
+ onKingsChatSignIn,
5124
5471
  onEmailStart,
5125
5472
  onEmailVerify,
5126
5473
  authMethods,
@@ -5132,6 +5479,7 @@ function SignInView({
5132
5479
  onPasskeySignIn
5133
5480
  }) {
5134
5481
  const [googleLoading, setGoogleLoading] = useState2(false);
5482
+ const [kingsChatLoading, setKingsChatLoading] = useState2(false);
5135
5483
  const [email, setEmail] = useState2("");
5136
5484
  const [code, setCode] = useState2("");
5137
5485
  const [emailSent, setEmailSent] = useState2(false);
@@ -5166,6 +5514,18 @@ function SignInView({
5166
5514
  setGoogleLoading(false);
5167
5515
  }
5168
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
+ }
5169
5529
  async function handleEmailStart(e) {
5170
5530
  e.preventDefault();
5171
5531
  setError("");
@@ -5276,6 +5636,27 @@ function SignInView({
5276
5636
  }
5277
5637
  },
5278
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"
5279
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(
5280
5661
  "input",
5281
5662
  {
@@ -5449,6 +5830,7 @@ function SocialWalletModal({
5449
5830
  onUnlock,
5450
5831
  onDisconnect,
5451
5832
  onGoogleSignIn,
5833
+ onKingsChatSignIn,
5452
5834
  onEmailStart,
5453
5835
  onEmailVerify,
5454
5836
  authMethods = ["google", "email"],
@@ -5582,6 +5964,7 @@ function SocialWalletModal({
5582
5964
  SignInView,
5583
5965
  {
5584
5966
  onGoogleSignIn,
5967
+ onKingsChatSignIn,
5585
5968
  onEmailStart,
5586
5969
  onEmailVerify,
5587
5970
  authMethods,
@@ -5910,6 +6293,12 @@ function DecaneKit({ config, children }) {
5910
6293
  if (!sdk) throw new Error("Social sign-in not configured");
5911
6294
  await sdk.connectWithGoogle();
5912
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]);
5913
6302
  const connectWithEmail = useCallback3(async (email) => {
5914
6303
  const sdk = socialRef.current;
5915
6304
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -5989,6 +6378,16 @@ function DecaneKit({ config, children }) {
5989
6378
  if (!sdk) throw new Error("Social wallet not connected");
5990
6379
  return sdk.signTronTransaction(rawDataHex);
5991
6380
  }, []);
6381
+ const socialRecordTransaction = useCallback3(async (tx) => {
6382
+ const sdk = socialRef.current;
6383
+ if (!sdk) throw new Error("Social wallet not connected");
6384
+ return sdk.recordTransaction(tx);
6385
+ }, []);
6386
+ const socialGetTransactionHistory = useCallback3(async (limit) => {
6387
+ const sdk = socialRef.current;
6388
+ if (!sdk) throw new Error("Social wallet not connected");
6389
+ return sdk.getTransactionHistory(limit);
6390
+ }, []);
5992
6391
  const socialSignTypedData = useCallback3(async (req) => {
5993
6392
  const sdk = socialRef.current;
5994
6393
  if (!sdk) throw new Error("Social wallet not connected");
@@ -6066,6 +6465,7 @@ function DecaneKit({ config, children }) {
6066
6465
  socialAuth,
6067
6466
  signInWithPasskey,
6068
6467
  connectWithGoogle,
6468
+ connectWithKingsChat,
6069
6469
  connectWithEmail,
6070
6470
  verifyEmailCode: verifyEmailCode2,
6071
6471
  connectWithToken,
@@ -6080,6 +6480,8 @@ function DecaneKit({ config, children }) {
6080
6480
  socialGetEthereumProvider,
6081
6481
  socialSignAuthorization,
6082
6482
  socialSignTronTransaction,
6483
+ socialRecordTransaction,
6484
+ socialGetTransactionHistory,
6083
6485
  socialGetAccessToken,
6084
6486
  socialSignSolanaTransaction,
6085
6487
  socialSignTypedData,
@@ -6106,6 +6508,7 @@ function DecaneKit({ config, children }) {
6106
6508
  mode,
6107
6509
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
6108
6510
  onGoogleSignIn: connectWithGoogle,
6511
+ onKingsChatSignIn: connectWithKingsChat,
6109
6512
  onEmailStart: connectWithEmail,
6110
6513
  onEmailVerify: verifyEmailCode2,
6111
6514
  canUsePasskey: socialAuth.canUsePasskey,
@@ -6127,6 +6530,7 @@ function DecaneKit({ config, children }) {
6127
6530
  customAuthLabel: customAuthConfig?.label,
6128
6531
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
6129
6532
  onGoogleSignIn: connectWithGoogle,
6533
+ onKingsChatSignIn: connectWithKingsChat,
6130
6534
  onEmailStart: connectWithEmail,
6131
6535
  onEmailVerify: verifyEmailCode2,
6132
6536
  authMethods: config?.social?.authMethods ?? ["google", "email"],
@@ -6301,6 +6705,8 @@ function useSocialWallet() {
6301
6705
  socialSendTransaction,
6302
6706
  socialSignSolanaTransaction,
6303
6707
  socialSignTronTransaction,
6708
+ socialRecordTransaction,
6709
+ socialGetTransactionHistory,
6304
6710
  socialSignTypedData,
6305
6711
  socialGetBalance,
6306
6712
  socialGetBalances,
@@ -6347,6 +6753,14 @@ function useSocialWallet() {
6347
6753
  (rawDataHex) => run(() => socialSignTronTransaction(rawDataHex)),
6348
6754
  [socialSignTronTransaction]
6349
6755
  );
6756
+ const recordTransaction = useCallback4(
6757
+ (tx) => run(() => socialRecordTransaction(tx)),
6758
+ [socialRecordTransaction]
6759
+ );
6760
+ const getTransactionHistory = useCallback4(
6761
+ (limit) => socialGetTransactionHistory(limit),
6762
+ [socialGetTransactionHistory]
6763
+ );
6350
6764
  const signTypedData = useCallback4(
6351
6765
  (req) => run(() => socialSignTypedData(req)),
6352
6766
  [socialSignTypedData]
@@ -6429,6 +6843,8 @@ function useSocialWallet() {
6429
6843
  sendTransaction,
6430
6844
  signSolanaTransaction: signSolanaTransaction2,
6431
6845
  signTronTransaction,
6846
+ recordTransaction,
6847
+ getTransactionHistory,
6432
6848
  signTypedData,
6433
6849
  // Balances (native token; read-only, works while locked)
6434
6850
  getBalance,
@@ -6758,6 +7174,7 @@ export {
6758
7174
  SessionExpiredError,
6759
7175
  ShareCorruptedError,
6760
7176
  SocialWalletModal,
7177
+ StaleDeviceShareError,
6761
7178
  UnlockRequiredError,
6762
7179
  UserCancelledError,
6763
7180
  WalletSelector,
@@ -6766,6 +7183,7 @@ export {
6766
7183
  createDecane,
6767
7184
  createDecaneConnect,
6768
7185
  createEthereumProvider,
7186
+ loginWithKingsChatPopup,
6769
7187
  startEmailAuth,
6770
7188
  useChainSwitch,
6771
7189
  useDecane,