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.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",
@@ -2064,6 +2102,21 @@ var ApiClient = class {
2064
2102
  })
2065
2103
  });
2066
2104
  }
2105
+ // ── Transaction history ───────────────────────────────────────────────────
2106
+ async recordTransaction(jwt, tx) {
2107
+ const body = await this._request("/transactions", {
2108
+ method: "POST",
2109
+ headers: { Authorization: `Bearer ${jwt}` },
2110
+ body: JSON.stringify(tx)
2111
+ });
2112
+ return body.transaction;
2113
+ }
2114
+ async getTransactions(jwt, limit = 50) {
2115
+ const body = await this._request(`/transactions?limit=${encodeURIComponent(String(limit))}`, {
2116
+ headers: { Authorization: `Bearer ${jwt}` }
2117
+ });
2118
+ return body.transactions;
2119
+ }
2067
2120
  // ── WebAuthn registration (additive to the PRF-only passkey flow) ─────────
2068
2121
  async getWebAuthnRegisterOptions(jwt, rpId, rpName) {
2069
2122
  return this._request(`/webauthn/register-options?rpId=${encodeURIComponent(rpId)}&rpName=${encodeURIComponent(rpName)}`, {
@@ -2339,6 +2392,13 @@ var TeeClient = class {
2339
2392
  requireAssertionPerSignature
2340
2393
  })
2341
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
+ }
2342
2402
  if (!res.ok) throw new Error("session open failed");
2343
2403
  const body = await res.json();
2344
2404
  this.handle = body.handle;
@@ -2398,6 +2458,45 @@ var TeeClient = class {
2398
2458
  }
2399
2459
  return { challengeId, assertion };
2400
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
+ }
2401
2500
  /** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
2402
2501
  async rotateShares(jwt) {
2403
2502
  if (!this.handle) throw new Error("no session");
@@ -2784,6 +2883,79 @@ async function verifyEmailCode(api, email, code) {
2784
2883
  return api.authEmailVerify(email, code);
2785
2884
  }
2786
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
+
2787
2959
  // src/social/signers/evm.ts
2788
2960
  var import_ethers2 = require("ethers");
2789
2961
  async function signEvmTransaction(tee, chain, tx, jwt) {
@@ -3146,7 +3318,7 @@ async function recoverDeviceShare(input) {
3146
3318
  prfSalt: backup.prfSalt,
3147
3319
  version: 1
3148
3320
  });
3149
- return deviceShare;
3321
+ return { deviceShare, flow: "passkey-sync" };
3150
3322
  } catch (e) {
3151
3323
  input.onProgress?.({
3152
3324
  type: "passkey-failed",
@@ -3174,7 +3346,7 @@ async function recoverDeviceShare(input) {
3174
3346
  input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
3175
3347
  await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
3176
3348
  zeroize(result.recoveryShare);
3177
- return result.deviceShare;
3349
+ return { deviceShare: result.deviceShare, flow: "recovery-file" };
3178
3350
  }
3179
3351
 
3180
3352
  // src/social/recovery/portableBackup.ts
@@ -3292,6 +3464,11 @@ async function createDecaneConnect(config) {
3292
3464
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
3293
3465
  if (!config.chains?.length)
3294
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
+ }
3295
3472
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
3296
3473
  const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
3297
3474
  const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
@@ -3311,6 +3488,7 @@ async function createDecaneConnect(config) {
3311
3488
  let persisted = loadPersisted(PERSIST_KEY);
3312
3489
  let ethProvider = null;
3313
3490
  const persistSession = config.persistSession !== false;
3491
+ const serverAssistedRecovery = config.serverAssistedRecovery !== false;
3314
3492
  const SESSION_KEY = sessionPersistKey(config.apiKey);
3315
3493
  function persistLiveSession() {
3316
3494
  if (!persistSession) return;
@@ -3536,6 +3714,17 @@ async function createDecaneConnect(config) {
3536
3714
  console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
3537
3715
  }
3538
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
+ }
3539
3728
  zeroize(shareSet.recovery);
3540
3729
  return addresses;
3541
3730
  }
@@ -3544,13 +3733,36 @@ async function createDecaneConnect(config) {
3544
3733
  const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
3545
3734
  const stored = await storage.getShare(userId);
3546
3735
  let deviceShare;
3736
+ let joinedViaPasskeySync = false;
3547
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
+ }
3548
3760
  if (!config.onRecoveryRotated) {
3549
3761
  throw new CallSequenceError(
3550
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."
3551
3763
  );
3552
3764
  }
3553
- deviceShare = await recoverDeviceShare({
3765
+ const recovered = await recoverDeviceShare({
3554
3766
  userId,
3555
3767
  jwt,
3556
3768
  rpId,
@@ -3568,6 +3780,8 @@ async function createDecaneConnect(config) {
3568
3780
  await api.markRecoveryShareSaved(jwt);
3569
3781
  }
3570
3782
  });
3783
+ deviceShare = recovered.deviceShare;
3784
+ joinedViaPasskeySync = recovered.flow === "passkey-sync";
3571
3785
  if (!await storage.getShare(userId) && isPasskeySupported()) {
3572
3786
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
3573
3787
  }
@@ -3583,15 +3797,66 @@ async function createDecaneConnect(config) {
3583
3797
  throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
3584
3798
  }
3585
3799
  }
3586
- 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
+ }
3587
3813
  zeroize(deviceShare);
3588
3814
  if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
3589
3815
  console.warn(
3590
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.`
3591
3817
  );
3592
3818
  }
3819
+ if (joinedViaPasskeySync && config.rotateOnNewDeviceRecovery) {
3820
+ await rotateForNewDevice(userId, jwt);
3821
+ }
3593
3822
  return addresses;
3594
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
+ }
3595
3860
  async function passkeyLoginCandidate() {
3596
3861
  if (!persisted?.userId || !isPasskeySupported()) return null;
3597
3862
  const stored = await storage.getShare(persisted.userId);
@@ -3666,6 +3931,7 @@ async function createDecaneConnect(config) {
3666
3931
  const method = config.authMethods[0];
3667
3932
  if (!method) throw new CallSequenceError("No authMethods configured");
3668
3933
  if (method === "google") return client.connectWithGoogle();
3934
+ if (method === "kingschat") return client.connectWithKingsChat();
3669
3935
  if (method === "email")
3670
3936
  throw new CallSequenceError(
3671
3937
  "Use connectWithEmail(email) for email auth"
@@ -3695,6 +3961,25 @@ async function createDecaneConnect(config) {
3695
3961
  return new Promise(() => {
3696
3962
  });
3697
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
+ },
3698
3983
  async connectWithToken(token, opts) {
3699
3984
  if (!token) throw new CallSequenceError("connectWithToken requires a token");
3700
3985
  const result = await api.authExternal(token, opts?.providerId);
@@ -3806,6 +4091,16 @@ async function createDecaneConnect(config) {
3806
4091
  };
3807
4092
  const signedTx = await signEvmTransaction(tee, req.chain, tx, auth?.jwt);
3808
4093
  const txResponse = await provider.broadcastTransaction(signedTx);
4094
+ if (auth?.jwt) {
4095
+ void api.recordTransaction(auth.jwt, {
4096
+ chain: req.chain,
4097
+ txHash: txResponse.hash,
4098
+ to: req.to,
4099
+ value: (req.value ?? 0n).toString(),
4100
+ kind: "send"
4101
+ }).catch(() => {
4102
+ });
4103
+ }
3809
4104
  return txResponse.hash;
3810
4105
  },
3811
4106
  async signTypedData(req) {
@@ -3838,6 +4133,14 @@ async function createDecaneConnect(config) {
3838
4133
  getAccessToken() {
3839
4134
  return auth?.jwt ?? null;
3840
4135
  },
4136
+ async recordTransaction(tx) {
4137
+ if (!auth?.jwt) throw new CallSequenceError("Not connected \u2014 call connect() first");
4138
+ return api.recordTransaction(auth.jwt, tx);
4139
+ },
4140
+ async getTransactionHistory(limit = 50) {
4141
+ if (!auth?.jwt) throw new CallSequenceError("Not connected \u2014 call connect() first");
4142
+ return api.getTransactions(auth.jwt, limit);
4143
+ },
3841
4144
  getEthereumProvider(opts) {
3842
4145
  if (!ethProvider) {
3843
4146
  const firstEvm = config.chains.find((c) => c.startsWith("evm:"));
@@ -4449,6 +4752,7 @@ function WalletSelector({
4449
4752
  mode = "wallets",
4450
4753
  socialAuthMethods = ["google", "email"],
4451
4754
  onGoogleSignIn,
4755
+ onKingsChatSignIn,
4452
4756
  onEmailStart,
4453
4757
  onEmailVerify,
4454
4758
  customAuthLabel,
@@ -4526,6 +4830,18 @@ function WalletSelector({
4526
4830
  setSocialLoad(false);
4527
4831
  }
4528
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
+ }
4529
4845
  async function handleCustomAuthClick() {
4530
4846
  if (!onCustomAuthSignIn) return;
4531
4847
  setSocialLoad(true);
@@ -4948,6 +5264,38 @@ function WalletSelector({
4948
5264
  },
4949
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" })),
4950
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"
4951
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(
4952
5300
  "input",
4953
5301
  {
@@ -5194,6 +5542,7 @@ function AddressRow({
5194
5542
  }
5195
5543
  function SignInView({
5196
5544
  onGoogleSignIn,
5545
+ onKingsChatSignIn,
5197
5546
  onEmailStart,
5198
5547
  onEmailVerify,
5199
5548
  authMethods,
@@ -5205,6 +5554,7 @@ function SignInView({
5205
5554
  onPasskeySignIn
5206
5555
  }) {
5207
5556
  const [googleLoading, setGoogleLoading] = (0, import_react3.useState)(false);
5557
+ const [kingsChatLoading, setKingsChatLoading] = (0, import_react3.useState)(false);
5208
5558
  const [email, setEmail] = (0, import_react3.useState)("");
5209
5559
  const [code, setCode] = (0, import_react3.useState)("");
5210
5560
  const [emailSent, setEmailSent] = (0, import_react3.useState)(false);
@@ -5239,6 +5589,18 @@ function SignInView({
5239
5589
  setGoogleLoading(false);
5240
5590
  }
5241
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
+ }
5242
5604
  async function handleEmailStart(e) {
5243
5605
  e.preventDefault();
5244
5606
  setError("");
@@ -5349,6 +5711,27 @@ function SignInView({
5349
5711
  }
5350
5712
  },
5351
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"
5352
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(
5353
5736
  "input",
5354
5737
  {
@@ -5522,6 +5905,7 @@ function SocialWalletModal({
5522
5905
  onUnlock,
5523
5906
  onDisconnect,
5524
5907
  onGoogleSignIn,
5908
+ onKingsChatSignIn,
5525
5909
  onEmailStart,
5526
5910
  onEmailVerify,
5527
5911
  authMethods = ["google", "email"],
@@ -5655,6 +6039,7 @@ function SocialWalletModal({
5655
6039
  SignInView,
5656
6040
  {
5657
6041
  onGoogleSignIn,
6042
+ onKingsChatSignIn,
5658
6043
  onEmailStart,
5659
6044
  onEmailVerify,
5660
6045
  authMethods,
@@ -5976,6 +6361,12 @@ function DecaneKit({ config, children }) {
5976
6361
  if (!sdk) throw new Error("Social sign-in not configured");
5977
6362
  await sdk.connectWithGoogle();
5978
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]);
5979
6370
  const connectWithEmail = (0, import_react5.useCallback)(async (email) => {
5980
6371
  const sdk = socialRef.current;
5981
6372
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -6055,6 +6446,16 @@ function DecaneKit({ config, children }) {
6055
6446
  if (!sdk) throw new Error("Social wallet not connected");
6056
6447
  return sdk.signTronTransaction(rawDataHex);
6057
6448
  }, []);
6449
+ const socialRecordTransaction = (0, import_react5.useCallback)(async (tx) => {
6450
+ const sdk = socialRef.current;
6451
+ if (!sdk) throw new Error("Social wallet not connected");
6452
+ return sdk.recordTransaction(tx);
6453
+ }, []);
6454
+ const socialGetTransactionHistory = (0, import_react5.useCallback)(async (limit) => {
6455
+ const sdk = socialRef.current;
6456
+ if (!sdk) throw new Error("Social wallet not connected");
6457
+ return sdk.getTransactionHistory(limit);
6458
+ }, []);
6058
6459
  const socialSignTypedData = (0, import_react5.useCallback)(async (req) => {
6059
6460
  const sdk = socialRef.current;
6060
6461
  if (!sdk) throw new Error("Social wallet not connected");
@@ -6132,6 +6533,7 @@ function DecaneKit({ config, children }) {
6132
6533
  socialAuth,
6133
6534
  signInWithPasskey,
6134
6535
  connectWithGoogle,
6536
+ connectWithKingsChat,
6135
6537
  connectWithEmail,
6136
6538
  verifyEmailCode: verifyEmailCode2,
6137
6539
  connectWithToken,
@@ -6146,6 +6548,8 @@ function DecaneKit({ config, children }) {
6146
6548
  socialGetEthereumProvider,
6147
6549
  socialSignAuthorization,
6148
6550
  socialSignTronTransaction,
6551
+ socialRecordTransaction,
6552
+ socialGetTransactionHistory,
6149
6553
  socialGetAccessToken,
6150
6554
  socialSignSolanaTransaction,
6151
6555
  socialSignTypedData,
@@ -6172,6 +6576,7 @@ function DecaneKit({ config, children }) {
6172
6576
  mode,
6173
6577
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
6174
6578
  onGoogleSignIn: connectWithGoogle,
6579
+ onKingsChatSignIn: connectWithKingsChat,
6175
6580
  onEmailStart: connectWithEmail,
6176
6581
  onEmailVerify: verifyEmailCode2,
6177
6582
  canUsePasskey: socialAuth.canUsePasskey,
@@ -6193,6 +6598,7 @@ function DecaneKit({ config, children }) {
6193
6598
  customAuthLabel: customAuthConfig?.label,
6194
6599
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
6195
6600
  onGoogleSignIn: connectWithGoogle,
6601
+ onKingsChatSignIn: connectWithKingsChat,
6196
6602
  onEmailStart: connectWithEmail,
6197
6603
  onEmailVerify: verifyEmailCode2,
6198
6604
  authMethods: config?.social?.authMethods ?? ["google", "email"],
@@ -6367,6 +6773,8 @@ function useSocialWallet() {
6367
6773
  socialSendTransaction,
6368
6774
  socialSignSolanaTransaction,
6369
6775
  socialSignTronTransaction,
6776
+ socialRecordTransaction,
6777
+ socialGetTransactionHistory,
6370
6778
  socialSignTypedData,
6371
6779
  socialGetBalance,
6372
6780
  socialGetBalances,
@@ -6413,6 +6821,14 @@ function useSocialWallet() {
6413
6821
  (rawDataHex) => run(() => socialSignTronTransaction(rawDataHex)),
6414
6822
  [socialSignTronTransaction]
6415
6823
  );
6824
+ const recordTransaction = (0, import_react6.useCallback)(
6825
+ (tx) => run(() => socialRecordTransaction(tx)),
6826
+ [socialRecordTransaction]
6827
+ );
6828
+ const getTransactionHistory = (0, import_react6.useCallback)(
6829
+ (limit) => socialGetTransactionHistory(limit),
6830
+ [socialGetTransactionHistory]
6831
+ );
6416
6832
  const signTypedData = (0, import_react6.useCallback)(
6417
6833
  (req) => run(() => socialSignTypedData(req)),
6418
6834
  [socialSignTypedData]
@@ -6495,6 +6911,8 @@ function useSocialWallet() {
6495
6911
  sendTransaction,
6496
6912
  signSolanaTransaction: signSolanaTransaction2,
6497
6913
  signTronTransaction,
6914
+ recordTransaction,
6915
+ getTransactionHistory,
6498
6916
  signTypedData,
6499
6917
  // Balances (native token; read-only, works while locked)
6500
6918
  getBalance,
@@ -6825,6 +7243,7 @@ function useSignTypedData() {
6825
7243
  SessionExpiredError,
6826
7244
  ShareCorruptedError,
6827
7245
  SocialWalletModal,
7246
+ StaleDeviceShareError,
6828
7247
  UnlockRequiredError,
6829
7248
  UserCancelledError,
6830
7249
  WalletSelector,
@@ -6833,6 +7252,7 @@ function useSignTypedData() {
6833
7252
  createDecane,
6834
7253
  createDecaneConnect,
6835
7254
  createEthereumProvider,
7255
+ loginWithKingsChatPopup,
6836
7256
  startEmailAuth,
6837
7257
  useChainSwitch,
6838
7258
  useDecane,