decane-connect-kit 2.18.0 → 2.20.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.d.cts CHANGED
@@ -92,7 +92,7 @@ interface DecaneConnectConfig {
92
92
  appId: string;
93
93
  apiKey: string;
94
94
  chains: Chain[];
95
- authMethods: Array<'google' | 'email' | 'phone' | 'kingschat'>;
95
+ authMethods: Array<'google' | 'email' | 'phone' | 'kingschat' | 'x'>;
96
96
  /**
97
97
  * Scopes requested from KingsChat at sign-in. Defaults to
98
98
  * `['send_chat_message']`, which is what KingsChat's own SDK asks for and is
@@ -303,6 +303,23 @@ interface DecaneConnect {
303
303
  * Decane session; it is never treated as one on its own.
304
304
  */
305
305
  connectWithKingsChat(): Promise<ConnectResult>;
306
+ /**
307
+ * Sign in with X (Twitter).
308
+ *
309
+ * A redirect, like Google and unlike KingsChat's popup: calling it navigates
310
+ * away, and the promise never resolves. Call it again on the page X returns
311
+ * to and it finishes the sign-in from the URL instead of starting a new one.
312
+ *
313
+ * X releases no email address, so `result.profile` carries a name and a
314
+ * picture and nothing else. Anything that needs an address must ask the user
315
+ * for one.
316
+ */
317
+ connectWithX(): Promise<ConnectResult>;
318
+ /**
319
+ * Finish a redirect sign-in already present in the URL, whichever provider
320
+ * started it. Throws when there is nothing to finish — it never starts one.
321
+ */
322
+ completeRedirectSignIn(): Promise<ConnectResult>;
306
323
  /**
307
324
  * Sign in with a KingsChat access token the app already holds — from
308
325
  * kingschat-web-sdk, a native SDK, or any other flow. Use when you want to
@@ -548,6 +565,14 @@ declare class ApiClient {
548
565
  getGoogleInitUrl(): Promise<{
549
566
  url: string;
550
567
  }>;
568
+ /**
569
+ * The X consent URL. Same contract as Google's: the backend holds the client
570
+ * secret and the PKCE verifier, and answers 503 X_NOT_CONFIGURED when the
571
+ * deployment has no X credentials.
572
+ */
573
+ getXInitUrl(): Promise<{
574
+ url: string;
575
+ }>;
551
576
  /**
552
577
  * Exchange a Google ID token the app already holds for a Decane session.
553
578
  * Resolves to the SAME identity as the redirect flow, so one Google account
@@ -592,7 +617,12 @@ declare class ApiClient {
592
617
  metadata?: Record<string, unknown>;
593
618
  }): Promise<TransactionRecord>;
594
619
  getTransactions(jwt: string, limit?: number): Promise<TransactionRecord[]>;
595
- getWebAuthnRegisterOptions(jwt: string, rpId: string, rpName: string): Promise<_simplewebauthn_browser.PublicKeyCredentialCreationOptionsJSON>;
620
+ /**
621
+ * `label` is what the user's password manager will show for this passkey.
622
+ * Without one they see the account's opaque id. Sent, used, and forgotten —
623
+ * the backend echoes it into the options and stores nothing.
624
+ */
625
+ getWebAuthnRegisterOptions(jwt: string, rpId: string, rpName: string, label?: string): Promise<_simplewebauthn_browser.PublicKeyCredentialCreationOptionsJSON>;
596
626
  postWebAuthnRegister(jwt: string, response: _simplewebauthn_browser.RegistrationResponseJSON, origin: string): Promise<void>;
597
627
  /**
598
628
  * Exchange a token from the developer's own auth system for a Decane
@@ -750,7 +780,7 @@ interface DecaneProvider {
750
780
  type ConnectMode = "all" | "social" | "wallets" | "evm" | "solana" | "tron" | "evm+social" | "solana+social" | "tron+social" | "evm+tron" | "evm+tron+social";
751
781
  interface SocialConfig {
752
782
  apiKey: string;
753
- authMethods?: ("google" | "email" | "phone" | "kingschat")[];
783
+ authMethods?: ("google" | "email" | "phone" | "kingschat" | "x")[];
754
784
  /**
755
785
  * Scopes requested from KingsChat. Defaults to `['send_chat_message']`, which
756
786
  * is what KingsChat's own SDK asks for and is enough to establish identity.
@@ -1154,9 +1184,10 @@ interface WalletSelectorProps {
1154
1184
  theme?: "light" | "dark" | "auto";
1155
1185
  networks?: "evm" | "solana" | "tron" | "bitcoin" | "all";
1156
1186
  mode?: ConnectMode;
1157
- socialAuthMethods?: ("google" | "email" | "phone" | "kingschat")[];
1187
+ socialAuthMethods?: ("google" | "email" | "phone" | "kingschat" | "x")[];
1158
1188
  onGoogleSignIn?: () => Promise<void>;
1159
1189
  onKingsChatSignIn?: () => Promise<void>;
1190
+ onXSignIn?: () => Promise<void>;
1160
1191
  onEmailStart?: (email: string) => Promise<{
1161
1192
  codeRequired: true;
1162
1193
  }>;
@@ -1174,7 +1205,7 @@ interface WalletSelectorProps {
1174
1205
  passkeyAddress?: string | null;
1175
1206
  onPasskeySignIn?: () => Promise<unknown>;
1176
1207
  }
1177
- declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onKingsChatSignIn, onEmailStart, onEmailVerify, onPhoneStart, onPhoneVerify, customAuthLabel, onCustomAuthSignIn, canUsePasskey, passkeyAddress, onPasskeySignIn, }: WalletSelectorProps): React.ReactPortal | null;
1208
+ declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onKingsChatSignIn, onXSignIn, onEmailStart, onEmailVerify, onPhoneStart, onPhoneVerify, customAuthLabel, onCustomAuthSignIn, canUsePasskey, passkeyAddress, onPasskeySignIn, }: WalletSelectorProps): React.ReactPortal | null;
1178
1209
 
1179
1210
  type Theme = "light" | "dark" | "auto";
1180
1211
  interface SocialAuthState {
@@ -1238,6 +1269,7 @@ interface SocialWalletModalProps {
1238
1269
  onDisconnect: () => Promise<void>;
1239
1270
  onGoogleSignIn: () => Promise<void>;
1240
1271
  onKingsChatSignIn?: () => Promise<void>;
1272
+ onXSignIn?: () => Promise<void>;
1241
1273
  onEmailStart: (email: string) => Promise<{
1242
1274
  codeRequired: true;
1243
1275
  }>;
@@ -1246,7 +1278,7 @@ interface SocialWalletModalProps {
1246
1278
  codeRequired: true;
1247
1279
  }>;
1248
1280
  onPhoneVerify?: (phone: string, code: string) => Promise<void>;
1249
- authMethods?: Array<"google" | "email" | "phone" | "kingschat">;
1281
+ authMethods?: Array<"google" | "email" | "phone" | "kingschat" | "x">;
1250
1282
  customAuthLabel?: string;
1251
1283
  onCustomAuthSignIn?: () => Promise<void>;
1252
1284
  canUsePasskey?: boolean;
@@ -1295,6 +1327,7 @@ declare function useSocialAuth(): {
1295
1327
  needsReconnect: boolean;
1296
1328
  googleLoading: boolean;
1297
1329
  kingschatLoading: boolean;
1330
+ xLoading: boolean;
1298
1331
  emailLoading: boolean;
1299
1332
  phoneLoading: boolean;
1300
1333
  tokenLoading: boolean;
@@ -1304,6 +1337,7 @@ declare function useSocialAuth(): {
1304
1337
  clearError: () => void;
1305
1338
  signInWithGoogle: () => Promise<void>;
1306
1339
  signInWithKingsChat: () => Promise<void>;
1340
+ signInWithX: () => Promise<void>;
1307
1341
  sendEmailCode: (email: string) => Promise<{
1308
1342
  codeRequired: true;
1309
1343
  }>;
package/dist/index.d.ts CHANGED
@@ -92,7 +92,7 @@ interface DecaneConnectConfig {
92
92
  appId: string;
93
93
  apiKey: string;
94
94
  chains: Chain[];
95
- authMethods: Array<'google' | 'email' | 'phone' | 'kingschat'>;
95
+ authMethods: Array<'google' | 'email' | 'phone' | 'kingschat' | 'x'>;
96
96
  /**
97
97
  * Scopes requested from KingsChat at sign-in. Defaults to
98
98
  * `['send_chat_message']`, which is what KingsChat's own SDK asks for and is
@@ -303,6 +303,23 @@ interface DecaneConnect {
303
303
  * Decane session; it is never treated as one on its own.
304
304
  */
305
305
  connectWithKingsChat(): Promise<ConnectResult>;
306
+ /**
307
+ * Sign in with X (Twitter).
308
+ *
309
+ * A redirect, like Google and unlike KingsChat's popup: calling it navigates
310
+ * away, and the promise never resolves. Call it again on the page X returns
311
+ * to and it finishes the sign-in from the URL instead of starting a new one.
312
+ *
313
+ * X releases no email address, so `result.profile` carries a name and a
314
+ * picture and nothing else. Anything that needs an address must ask the user
315
+ * for one.
316
+ */
317
+ connectWithX(): Promise<ConnectResult>;
318
+ /**
319
+ * Finish a redirect sign-in already present in the URL, whichever provider
320
+ * started it. Throws when there is nothing to finish — it never starts one.
321
+ */
322
+ completeRedirectSignIn(): Promise<ConnectResult>;
306
323
  /**
307
324
  * Sign in with a KingsChat access token the app already holds — from
308
325
  * kingschat-web-sdk, a native SDK, or any other flow. Use when you want to
@@ -548,6 +565,14 @@ declare class ApiClient {
548
565
  getGoogleInitUrl(): Promise<{
549
566
  url: string;
550
567
  }>;
568
+ /**
569
+ * The X consent URL. Same contract as Google's: the backend holds the client
570
+ * secret and the PKCE verifier, and answers 503 X_NOT_CONFIGURED when the
571
+ * deployment has no X credentials.
572
+ */
573
+ getXInitUrl(): Promise<{
574
+ url: string;
575
+ }>;
551
576
  /**
552
577
  * Exchange a Google ID token the app already holds for a Decane session.
553
578
  * Resolves to the SAME identity as the redirect flow, so one Google account
@@ -592,7 +617,12 @@ declare class ApiClient {
592
617
  metadata?: Record<string, unknown>;
593
618
  }): Promise<TransactionRecord>;
594
619
  getTransactions(jwt: string, limit?: number): Promise<TransactionRecord[]>;
595
- getWebAuthnRegisterOptions(jwt: string, rpId: string, rpName: string): Promise<_simplewebauthn_browser.PublicKeyCredentialCreationOptionsJSON>;
620
+ /**
621
+ * `label` is what the user's password manager will show for this passkey.
622
+ * Without one they see the account's opaque id. Sent, used, and forgotten —
623
+ * the backend echoes it into the options and stores nothing.
624
+ */
625
+ getWebAuthnRegisterOptions(jwt: string, rpId: string, rpName: string, label?: string): Promise<_simplewebauthn_browser.PublicKeyCredentialCreationOptionsJSON>;
596
626
  postWebAuthnRegister(jwt: string, response: _simplewebauthn_browser.RegistrationResponseJSON, origin: string): Promise<void>;
597
627
  /**
598
628
  * Exchange a token from the developer's own auth system for a Decane
@@ -750,7 +780,7 @@ interface DecaneProvider {
750
780
  type ConnectMode = "all" | "social" | "wallets" | "evm" | "solana" | "tron" | "evm+social" | "solana+social" | "tron+social" | "evm+tron" | "evm+tron+social";
751
781
  interface SocialConfig {
752
782
  apiKey: string;
753
- authMethods?: ("google" | "email" | "phone" | "kingschat")[];
783
+ authMethods?: ("google" | "email" | "phone" | "kingschat" | "x")[];
754
784
  /**
755
785
  * Scopes requested from KingsChat. Defaults to `['send_chat_message']`, which
756
786
  * is what KingsChat's own SDK asks for and is enough to establish identity.
@@ -1154,9 +1184,10 @@ interface WalletSelectorProps {
1154
1184
  theme?: "light" | "dark" | "auto";
1155
1185
  networks?: "evm" | "solana" | "tron" | "bitcoin" | "all";
1156
1186
  mode?: ConnectMode;
1157
- socialAuthMethods?: ("google" | "email" | "phone" | "kingschat")[];
1187
+ socialAuthMethods?: ("google" | "email" | "phone" | "kingschat" | "x")[];
1158
1188
  onGoogleSignIn?: () => Promise<void>;
1159
1189
  onKingsChatSignIn?: () => Promise<void>;
1190
+ onXSignIn?: () => Promise<void>;
1160
1191
  onEmailStart?: (email: string) => Promise<{
1161
1192
  codeRequired: true;
1162
1193
  }>;
@@ -1174,7 +1205,7 @@ interface WalletSelectorProps {
1174
1205
  passkeyAddress?: string | null;
1175
1206
  onPasskeySignIn?: () => Promise<unknown>;
1176
1207
  }
1177
- declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onKingsChatSignIn, onEmailStart, onEmailVerify, onPhoneStart, onPhoneVerify, customAuthLabel, onCustomAuthSignIn, canUsePasskey, passkeyAddress, onPasskeySignIn, }: WalletSelectorProps): React.ReactPortal | null;
1208
+ declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onKingsChatSignIn, onXSignIn, onEmailStart, onEmailVerify, onPhoneStart, onPhoneVerify, customAuthLabel, onCustomAuthSignIn, canUsePasskey, passkeyAddress, onPasskeySignIn, }: WalletSelectorProps): React.ReactPortal | null;
1178
1209
 
1179
1210
  type Theme = "light" | "dark" | "auto";
1180
1211
  interface SocialAuthState {
@@ -1238,6 +1269,7 @@ interface SocialWalletModalProps {
1238
1269
  onDisconnect: () => Promise<void>;
1239
1270
  onGoogleSignIn: () => Promise<void>;
1240
1271
  onKingsChatSignIn?: () => Promise<void>;
1272
+ onXSignIn?: () => Promise<void>;
1241
1273
  onEmailStart: (email: string) => Promise<{
1242
1274
  codeRequired: true;
1243
1275
  }>;
@@ -1246,7 +1278,7 @@ interface SocialWalletModalProps {
1246
1278
  codeRequired: true;
1247
1279
  }>;
1248
1280
  onPhoneVerify?: (phone: string, code: string) => Promise<void>;
1249
- authMethods?: Array<"google" | "email" | "phone" | "kingschat">;
1281
+ authMethods?: Array<"google" | "email" | "phone" | "kingschat" | "x">;
1250
1282
  customAuthLabel?: string;
1251
1283
  onCustomAuthSignIn?: () => Promise<void>;
1252
1284
  canUsePasskey?: boolean;
@@ -1295,6 +1327,7 @@ declare function useSocialAuth(): {
1295
1327
  needsReconnect: boolean;
1296
1328
  googleLoading: boolean;
1297
1329
  kingschatLoading: boolean;
1330
+ xLoading: boolean;
1298
1331
  emailLoading: boolean;
1299
1332
  phoneLoading: boolean;
1300
1333
  tokenLoading: boolean;
@@ -1304,6 +1337,7 @@ declare function useSocialAuth(): {
1304
1337
  clearError: () => void;
1305
1338
  signInWithGoogle: () => Promise<void>;
1306
1339
  signInWithKingsChat: () => Promise<void>;
1340
+ signInWithX: () => Promise<void>;
1307
1341
  sendEmailCode: (email: string) => Promise<{
1308
1342
  codeRequired: true;
1309
1343
  }>;
package/dist/index.js CHANGED
@@ -1946,6 +1946,37 @@ var EventEmitter = class {
1946
1946
  }
1947
1947
  };
1948
1948
 
1949
+ // src/social/auth/redirectResult.ts
1950
+ function readRedirectOutcome(search) {
1951
+ const params = new URLSearchParams(search);
1952
+ const error = params.get("decane_error");
1953
+ if (error) return { kind: "error", error };
1954
+ const jwt = params.get("decane_jwt");
1955
+ if (!jwt) return { kind: "none" };
1956
+ const name = params.get("decane_name");
1957
+ const email = params.get("decane_email");
1958
+ const picture = params.get("decane_picture");
1959
+ const profile = {
1960
+ ...name ? { name } : {},
1961
+ ...email ? { email } : {},
1962
+ ...picture ? { picture } : {}
1963
+ };
1964
+ return {
1965
+ kind: "result",
1966
+ result: {
1967
+ jwt,
1968
+ userId: params.get("decane_user_id") ?? "",
1969
+ isNewUser: params.get("decane_is_new_user") === "true",
1970
+ hasShare: params.get("decane_has_share") === "true",
1971
+ // Omitted entirely when the provider released nothing, so callers can
1972
+ // test `result.profile` rather than an object that is always present but
1973
+ // sometimes empty. X never sends an email, so this is the normal case
1974
+ // there rather than an edge one.
1975
+ ...Object.keys(profile).length ? { profile } : {}
1976
+ }
1977
+ };
1978
+ }
1979
+
1949
1980
  // src/social/api.ts
1950
1981
  init_errors();
1951
1982
  var ERROR_MAP = {
@@ -1997,6 +2028,14 @@ var ApiClient = class {
1997
2028
  async getGoogleInitUrl() {
1998
2029
  return this._request("/auth/google/init", { method: "GET" });
1999
2030
  }
2031
+ /**
2032
+ * The X consent URL. Same contract as Google's: the backend holds the client
2033
+ * secret and the PKCE verifier, and answers 503 X_NOT_CONFIGURED when the
2034
+ * deployment has no X credentials.
2035
+ */
2036
+ async getXInitUrl() {
2037
+ return this._request("/auth/x/init", { method: "GET" });
2038
+ }
2000
2039
  /**
2001
2040
  * Exchange a Google ID token the app already holds for a Decane session.
2002
2041
  * Resolves to the SAME identity as the redirect flow, so one Google account
@@ -2085,8 +2124,14 @@ var ApiClient = class {
2085
2124
  return body.transactions;
2086
2125
  }
2087
2126
  // ── WebAuthn registration (additive to the PRF-only passkey flow) ─────────
2088
- async getWebAuthnRegisterOptions(jwt, rpId, rpName) {
2089
- return this._request(`/webauthn/register-options?rpId=${encodeURIComponent(rpId)}&rpName=${encodeURIComponent(rpName)}`, {
2127
+ /**
2128
+ * `label` is what the user's password manager will show for this passkey.
2129
+ * Without one they see the account's opaque id. Sent, used, and forgotten —
2130
+ * the backend echoes it into the options and stores nothing.
2131
+ */
2132
+ async getWebAuthnRegisterOptions(jwt, rpId, rpName, label) {
2133
+ const query = `rpId=${encodeURIComponent(rpId)}&rpName=${encodeURIComponent(rpName)}` + (label ? `&label=${encodeURIComponent(label)}` : "");
2134
+ return this._request(`/webauthn/register-options?${query}`, {
2090
2135
  headers: { Authorization: `Bearer ${jwt}` }
2091
2136
  });
2092
2137
  }
@@ -2970,7 +3015,12 @@ function bufFromBytes(u) {
2970
3015
  return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
2971
3016
  }
2972
3017
  async function registerWebAuthnCredential(opts) {
2973
- const baseOptions = await opts.api.getWebAuthnRegisterOptions(opts.jwt, opts.rpId, opts.rpName);
3018
+ const baseOptions = await opts.api.getWebAuthnRegisterOptions(
3019
+ opts.jwt,
3020
+ opts.rpId,
3021
+ opts.rpName,
3022
+ opts.label
3023
+ );
2974
3024
  if (opts.allowDuplicate) delete baseOptions.excludeCredentials;
2975
3025
  const optionsJSON = {
2976
3026
  ...baseOptions,
@@ -3776,12 +3826,14 @@ async function createDecaneConnect(config) {
3776
3826
  let credentialId;
3777
3827
  let prfOutput;
3778
3828
  try {
3829
+ const label = (await storage.getShare(userId))?.profile?.email;
3779
3830
  ({ credentialId, prfOutput } = await registerWebAuthnCredential({
3780
3831
  api,
3781
3832
  jwt,
3782
3833
  rpId,
3783
3834
  rpName,
3784
3835
  prfSalt,
3836
+ ...label ? { label } : {},
3785
3837
  ...opts.allowDuplicate ? { allowDuplicate: true } : {}
3786
3838
  }));
3787
3839
  } catch (e) {
@@ -4283,6 +4335,21 @@ async function createDecaneConnect(config) {
4283
4335
  ...stored.profile ? { profile: stored.profile } : {}
4284
4336
  };
4285
4337
  }
4338
+ async function connectViaRedirect(label, getInitUrl) {
4339
+ const outcome = readRedirectOutcome(window.location.search);
4340
+ if (outcome.kind === "error") {
4341
+ window.history.replaceState({}, "", window.location.pathname);
4342
+ throw new CallSequenceError(`${label} sign-in failed: ${outcome.error}`);
4343
+ }
4344
+ if (outcome.kind === "result") {
4345
+ window.history.replaceState({}, "", window.location.pathname);
4346
+ return handleAuthResult(outcome.result);
4347
+ }
4348
+ const { url } = await getInitUrl();
4349
+ window.location.href = url;
4350
+ return new Promise(() => {
4351
+ });
4352
+ }
4286
4353
  async function handleAuthResult(result) {
4287
4354
  let addresses;
4288
4355
  if (!result.hasShare) {
@@ -4329,39 +4396,33 @@ async function createDecaneConnect(config) {
4329
4396
  throw new CallSequenceError(`Unknown auth method: ${method}`);
4330
4397
  },
4331
4398
  async connectWithGoogle() {
4332
- const params = new URLSearchParams(window.location.search);
4333
- const decaneJwt = params.get("decane_jwt");
4334
- const decaneError = params.get("decane_error");
4335
- if (decaneError) {
4336
- window.history.replaceState({}, "", window.location.pathname);
4337
- throw new CallSequenceError(`Google sign-in failed: ${decaneError}`);
4338
- }
4339
- if (decaneJwt) {
4340
- const name = params.get("decane_name");
4341
- const email = params.get("decane_email");
4342
- const picture = params.get("decane_picture");
4343
- const profile = {
4344
- ...name ? { name } : {},
4345
- ...email ? { email } : {},
4346
- ...picture ? { picture } : {}
4347
- };
4348
- const result = {
4349
- jwt: decaneJwt,
4350
- userId: params.get("decane_user_id") ?? "",
4351
- isNewUser: params.get("decane_is_new_user") === "true",
4352
- hasShare: params.get("decane_has_share") === "true",
4353
- // Omitted entirely when Google released nothing, so callers can test
4354
- // `result.profile` rather than an object that is always present but
4355
- // sometimes empty.
4356
- ...Object.keys(profile).length ? { profile } : {}
4357
- };
4358
- window.history.replaceState({}, "", window.location.pathname);
4359
- return handleAuthResult(result);
4399
+ return connectViaRedirect("Google", () => api.getGoogleInitUrl());
4400
+ },
4401
+ /**
4402
+ * Finish a redirect sign-in that is already sitting in the URL, whichever
4403
+ * provider started it.
4404
+ *
4405
+ * Google and X bounce back with the same decane_* params, so the page that
4406
+ * receives them cannot tell which one ran — and should not have to. Calling
4407
+ * connectWithGoogle() here would work by accident and then report an X
4408
+ * failure as "Google sign-in failed".
4409
+ *
4410
+ * Throws if there is nothing to finish: unlike the connectWith* methods,
4411
+ * this never starts a flow.
4412
+ */
4413
+ async completeRedirectSignIn() {
4414
+ const outcome = readRedirectOutcome(window.location.search);
4415
+ if (outcome.kind === "none") {
4416
+ throw new CallSequenceError("No sign-in result in the URL to complete");
4360
4417
  }
4361
- const { url } = await api.getGoogleInitUrl();
4362
- window.location.href = url;
4363
- return new Promise(() => {
4364
- });
4418
+ window.history.replaceState({}, "", window.location.pathname);
4419
+ if (outcome.kind === "error") {
4420
+ throw new CallSequenceError(`Sign-in failed: ${outcome.error}`);
4421
+ }
4422
+ return handleAuthResult(outcome.result);
4423
+ },
4424
+ async connectWithX() {
4425
+ return connectViaRedirect("X", () => api.getXInitUrl());
4365
4426
  },
4366
4427
  async connectWithGoogleToken(idToken) {
4367
4428
  if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
@@ -5185,6 +5246,20 @@ function ChainIconStack({ support, size = 16 }) {
5185
5246
  /* @__PURE__ */ React.createElement(ChainCircleIcon, { chain, size })
5186
5247
  )));
5187
5248
  }
5249
+ function XLogo({ size = 14 }) {
5250
+ return /* @__PURE__ */ React.createElement(
5251
+ "svg",
5252
+ {
5253
+ width: size,
5254
+ height: size,
5255
+ viewBox: "0 0 24 24",
5256
+ fill: "currentColor",
5257
+ "aria-hidden": "true",
5258
+ focusable: "false"
5259
+ },
5260
+ /* @__PURE__ */ React.createElement("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" })
5261
+ );
5262
+ }
5188
5263
 
5189
5264
  // src/ui/WalletSelector.tsx
5190
5265
  var FONT_URL = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap";
@@ -5326,6 +5401,7 @@ function WalletSelector({
5326
5401
  socialAuthMethods = ["google", "email"],
5327
5402
  onGoogleSignIn,
5328
5403
  onKingsChatSignIn,
5404
+ onXSignIn,
5329
5405
  onEmailStart,
5330
5406
  onEmailVerify,
5331
5407
  onPhoneStart,
@@ -5420,6 +5496,17 @@ function WalletSelector({
5420
5496
  setSocialLoad(false);
5421
5497
  }
5422
5498
  }
5499
+ async function handleXClick() {
5500
+ if (!onXSignIn) return;
5501
+ setSocialLoad(true);
5502
+ setSocialErr("");
5503
+ try {
5504
+ await onXSignIn();
5505
+ } catch (e) {
5506
+ setSocialErr(e.message);
5507
+ setSocialLoad(false);
5508
+ }
5509
+ }
5423
5510
  async function handleCustomAuthClick() {
5424
5511
  if (!onCustomAuthSignIn) return;
5425
5512
  setSocialLoad(true);
@@ -5901,6 +5988,38 @@ function WalletSelector({
5901
5988
  },
5902
5989
  /* @__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)" })),
5903
5990
  socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
5991
+ ), !passkeyFirst && socialAuthMethods.includes("x") && onXSignIn && /* @__PURE__ */ React2.createElement(
5992
+ "button",
5993
+ {
5994
+ onClick: handleXClick,
5995
+ disabled: socialLoad,
5996
+ style: {
5997
+ display: "flex",
5998
+ alignItems: "center",
5999
+ justifyContent: "center",
6000
+ gap: 10,
6001
+ width: "100%",
6002
+ padding: "11px 16px",
6003
+ background: "var(--dc-surface)",
6004
+ border: "1px solid var(--dc-border)",
6005
+ borderRadius: 10,
6006
+ cursor: socialLoad ? "not-allowed" : "pointer",
6007
+ fontSize: 14,
6008
+ fontWeight: 600,
6009
+ color: "var(--dc-text)",
6010
+ fontFamily: "inherit",
6011
+ transition: "border-color 0.12s, background 0.12s",
6012
+ opacity: socialLoad ? 0.6 : 1
6013
+ },
6014
+ onMouseEnter: (e) => {
6015
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
6016
+ },
6017
+ onMouseLeave: (e) => {
6018
+ e.currentTarget.style.borderColor = "var(--dc-border)";
6019
+ }
6020
+ },
6021
+ /* @__PURE__ */ React2.createElement(XLogo, { size: 16 }),
6022
+ socialLoad ? "Redirecting\u2026" : "Continue with X"
5904
6023
  ), !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(
5905
6024
  "input",
5906
6025
  {
@@ -6268,6 +6387,7 @@ function AddressRow({
6268
6387
  function SignInView({
6269
6388
  onGoogleSignIn,
6270
6389
  onKingsChatSignIn,
6390
+ onXSignIn,
6271
6391
  onEmailStart,
6272
6392
  onEmailVerify,
6273
6393
  onPhoneStart,
@@ -6282,6 +6402,7 @@ function SignInView({
6282
6402
  }) {
6283
6403
  const [googleLoading, setGoogleLoading] = useState2(false);
6284
6404
  const [kingsChatLoading, setKingsChatLoading] = useState2(false);
6405
+ const [xLoading, setXLoading] = useState2(false);
6285
6406
  const [email, setEmail] = useState2("");
6286
6407
  const [code, setCode] = useState2("");
6287
6408
  const [emailSent, setEmailSent] = useState2(false);
@@ -6332,6 +6453,17 @@ function SignInView({
6332
6453
  setKingsChatLoading(false);
6333
6454
  }
6334
6455
  }
6456
+ async function handleX() {
6457
+ if (!onXSignIn) return;
6458
+ setError("");
6459
+ setXLoading(true);
6460
+ try {
6461
+ await onXSignIn();
6462
+ } catch (e) {
6463
+ setError(e.message);
6464
+ setXLoading(false);
6465
+ }
6466
+ }
6335
6467
  async function handleEmailStart(e) {
6336
6468
  e.preventDefault();
6337
6469
  setError("");
@@ -6490,6 +6622,32 @@ function SignInView({
6490
6622
  }
6491
6623
  },
6492
6624
  kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
6625
+ ), !passkeyFirst && authMethods.includes("x") && onXSignIn && /* @__PURE__ */ React3.createElement(
6626
+ "button",
6627
+ {
6628
+ onClick: handleX,
6629
+ disabled: xLoading,
6630
+ style: {
6631
+ width: "100%",
6632
+ padding: "10px 0",
6633
+ marginBottom: 10,
6634
+ background: "var(--dc-surface)",
6635
+ color: "var(--dc-text)",
6636
+ border: "1px solid var(--dc-border)",
6637
+ borderRadius: 10,
6638
+ fontSize: 13,
6639
+ fontWeight: 700,
6640
+ cursor: xLoading ? "not-allowed" : "pointer",
6641
+ opacity: xLoading ? 0.7 : 1,
6642
+ fontFamily: "inherit",
6643
+ display: "flex",
6644
+ alignItems: "center",
6645
+ justifyContent: "center",
6646
+ gap: 8
6647
+ }
6648
+ },
6649
+ /* @__PURE__ */ React3.createElement(XLogo, null),
6650
+ xLoading ? "Redirecting\u2026" : "Continue with X"
6493
6651
  ), !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(
6494
6652
  "input",
6495
6653
  {
@@ -7152,7 +7310,7 @@ function DecaneKit({ config, children }) {
7152
7310
  });
7153
7311
  const params = new URLSearchParams(window.location.search);
7154
7312
  if (params.has("decane_jwt") || params.has("decane_error")) {
7155
- sdk.connectWithGoogle().then(applyResult).catch((e) => {
7313
+ sdk.completeRedirectSignIn().then(applyResult).catch((e) => {
7156
7314
  setSocialAuth((p) => p.phase ? { ...p, phase: null } : p);
7157
7315
  console.error(e);
7158
7316
  });
@@ -7193,6 +7351,11 @@ function DecaneKit({ config, children }) {
7193
7351
  if (!sdk) throw new Error("Social sign-in not configured");
7194
7352
  await sdk.connectWithGoogle();
7195
7353
  }, []);
7354
+ const connectWithX = useCallback3(async () => {
7355
+ const sdk = socialRef.current;
7356
+ if (!sdk) throw new Error("Social sign-in not configured");
7357
+ await sdk.connectWithX();
7358
+ }, []);
7196
7359
  const connectWithKingsChat = useCallback3(async () => {
7197
7360
  const sdk = socialRef.current;
7198
7361
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -7413,6 +7576,7 @@ function DecaneKit({ config, children }) {
7413
7576
  signInWithPasskey,
7414
7577
  connectWithGoogle,
7415
7578
  connectWithKingsChat,
7579
+ connectWithX,
7416
7580
  connectWithEmail,
7417
7581
  verifyEmailCode: verifyEmailCode2,
7418
7582
  connectWithPhone,
@@ -7464,6 +7628,7 @@ function DecaneKit({ config, children }) {
7464
7628
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
7465
7629
  onGoogleSignIn: connectWithGoogle,
7466
7630
  onKingsChatSignIn: connectWithKingsChat,
7631
+ onXSignIn: connectWithX,
7467
7632
  onEmailStart: connectWithEmail,
7468
7633
  onEmailVerify: verifyEmailCode2,
7469
7634
  onPhoneStart: connectWithPhone,
@@ -7488,6 +7653,7 @@ function DecaneKit({ config, children }) {
7488
7653
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
7489
7654
  onGoogleSignIn: connectWithGoogle,
7490
7655
  onKingsChatSignIn: connectWithKingsChat,
7656
+ onXSignIn: connectWithX,
7491
7657
  onEmailStart: connectWithEmail,
7492
7658
  onEmailVerify: verifyEmailCode2,
7493
7659
  onPhoneStart: connectWithPhone,
@@ -7522,6 +7688,7 @@ function useSocialAuth() {
7522
7688
  socialAuth,
7523
7689
  connectWithGoogle,
7524
7690
  connectWithKingsChat,
7691
+ connectWithX,
7525
7692
  connectWithEmail,
7526
7693
  verifyEmailCode: verifyEmailCode2,
7527
7694
  connectWithPhone,
@@ -7535,6 +7702,7 @@ function useSocialAuth() {
7535
7702
  } = useDecaneContext();
7536
7703
  const [googleLoading, setGoogleLoading] = useState4(false);
7537
7704
  const [kingschatLoading, setKingschatLoading] = useState4(false);
7705
+ const [xLoading, setXLoading] = useState4(false);
7538
7706
  const [emailLoading, setEmailLoading] = useState4(false);
7539
7707
  const [phoneLoading, setPhoneLoading] = useState4(false);
7540
7708
  const [tokenLoading, setTokenLoading] = useState4(false);
@@ -7550,6 +7718,16 @@ function useSocialAuth() {
7550
7718
  setGoogleLoading(false);
7551
7719
  }
7552
7720
  }, [connectWithGoogle]);
7721
+ const signInWithX = useCallback4(async () => {
7722
+ setXLoading(true);
7723
+ setError(null);
7724
+ try {
7725
+ await connectWithX();
7726
+ } catch (e) {
7727
+ setError(e.message);
7728
+ setXLoading(false);
7729
+ }
7730
+ }, [connectWithX]);
7553
7731
  const signInWithKingsChat = useCallback4(async () => {
7554
7732
  setKingschatLoading(true);
7555
7733
  setError(null);
@@ -7687,6 +7865,7 @@ function useSocialAuth() {
7687
7865
  needsReconnect: socialAuth.needsReconnect,
7688
7866
  googleLoading,
7689
7867
  kingschatLoading,
7868
+ xLoading,
7690
7869
  emailLoading,
7691
7870
  phoneLoading,
7692
7871
  tokenLoading,
@@ -7696,6 +7875,7 @@ function useSocialAuth() {
7696
7875
  clearError: () => setError(null),
7697
7876
  signInWithGoogle,
7698
7877
  signInWithKingsChat,
7878
+ signInWithX,
7699
7879
  sendEmailCode,
7700
7880
  confirmEmailCode,
7701
7881
  sendPhoneCode,