decane-connect-kit 2.0.3 → 2.2.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
@@ -1935,7 +1935,12 @@ function startBitcoinDiscovery(registry) {
1935
1935
  }
1936
1936
 
1937
1937
  // src/factory.ts
1938
- function createDecane(config = {}) {
1938
+ function createDecane(config) {
1939
+ if (!config?.appId) {
1940
+ throw new Error(
1941
+ "decane-connect-kit: appId is required. Pass your dashboard project ID as config.appId."
1942
+ );
1943
+ }
1939
1944
  const emitter = createEmitter();
1940
1945
  const registry = createRegistry(emitter);
1941
1946
  const session = createSessionManager();
@@ -1981,9 +1986,10 @@ var ERROR_MAP = {
1981
1986
  ORIGIN_NOT_ALLOWED: OriginNotAllowedError
1982
1987
  };
1983
1988
  var ApiClient = class {
1984
- constructor(baseUrl, apiKey) {
1989
+ constructor(baseUrl, apiKey, appId) {
1985
1990
  this.baseUrl = baseUrl;
1986
1991
  this.apiKey = apiKey;
1992
+ this.appId = appId;
1987
1993
  }
1988
1994
  async _request(path, init) {
1989
1995
  let res;
@@ -1993,6 +1999,7 @@ var ApiClient = class {
1993
1999
  headers: {
1994
2000
  "Content-Type": "application/json",
1995
2001
  "X-API-Key": this.apiKey,
2002
+ "X-App-Id": this.appId,
1996
2003
  ...init?.headers
1997
2004
  }
1998
2005
  });
@@ -2059,6 +2066,35 @@ var ApiClient = class {
2059
2066
  body: JSON.stringify({ response, origin })
2060
2067
  });
2061
2068
  }
2069
+ /**
2070
+ * Exchange a token from the developer's own auth system for a Decane
2071
+ * session. `providerId` is only needed when the project has more than one
2072
+ * provider configured.
2073
+ */
2074
+ async authExternal(token, providerId) {
2075
+ return this._request("/auth/external", {
2076
+ method: "POST",
2077
+ body: JSON.stringify(providerId ? { token, providerId } : { token })
2078
+ });
2079
+ }
2080
+ /**
2081
+ * Returns null when this user has no server-registered passkey, which is the
2082
+ * expected answer for wallets created before passkey sign-in existed or for
2083
+ * PIN-only users — the caller falls back to the normal sign-in flow.
2084
+ */
2085
+ async getWebAuthnLoginOptions(userId, rpId) {
2086
+ return this._request("/webauthn/login-options", {
2087
+ method: "POST",
2088
+ body: JSON.stringify({ userId, rpId }),
2089
+ allow404: true
2090
+ });
2091
+ }
2092
+ async postWebAuthnLogin(userId, response, origin) {
2093
+ return this._request("/webauthn/login", {
2094
+ method: "POST",
2095
+ body: JSON.stringify({ userId, response, origin })
2096
+ });
2097
+ }
2062
2098
  async revoke(jwt) {
2063
2099
  await this._request("/auth/revoke", {
2064
2100
  method: "POST",
@@ -2231,6 +2267,12 @@ var TeeClient = class {
2231
2267
  this.teeUrl = teeUrl;
2232
2268
  this.handle = null;
2233
2269
  this.attested = false;
2270
+ /** Mirrors what the signer confirmed for this session, not what we asked for. */
2271
+ this.assertionPerSignature = false;
2272
+ }
2273
+ /** Whether this session actually got per-signature assertions. */
2274
+ requiresAssertionPerSignature() {
2275
+ return this.assertionPerSignature;
2234
2276
  }
2235
2277
  async ensureAttested() {
2236
2278
  if (this.attested) return;
@@ -2246,16 +2288,20 @@ var TeeClient = class {
2246
2288
  * couldn't derive them (older signer build, or a transient share-fetch
2247
2289
  * failure); callers fall back to the backend's addresses in that case.
2248
2290
  */
2249
- async openSession(deviceShare, jwt) {
2291
+ async openSession(deviceShare, jwt, requireAssertionPerSignature = false) {
2250
2292
  await this.ensureAttested();
2251
2293
  const res = await fetch(`${this.teeUrl}/signer/session`, {
2252
2294
  method: "POST",
2253
2295
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2254
- body: JSON.stringify({ deviceShare: bytesToBase64(deviceShare) })
2296
+ body: JSON.stringify({
2297
+ deviceShare: bytesToBase64(deviceShare),
2298
+ requireAssertionPerSignature
2299
+ })
2255
2300
  });
2256
2301
  if (!res.ok) throw new Error("session open failed");
2257
2302
  const body = await res.json();
2258
2303
  this.handle = body.handle;
2304
+ this.assertionPerSignature = body.requireAssertionPerSignature === true;
2259
2305
  return body.addresses ?? null;
2260
2306
  }
2261
2307
  /**
@@ -2346,14 +2392,33 @@ var TeeClient = class {
2346
2392
  hasSession() {
2347
2393
  return this.handle !== null;
2348
2394
  }
2349
- async sign(req) {
2395
+ /**
2396
+ * `jwt` is only needed when this session requires per-signature assertions —
2397
+ * it authenticates the challenge request. Passed per call rather than cached
2398
+ * on the instance deliberately: the backend's JWTs are shorter-lived than the
2399
+ * TEE session TTL, so a stored copy would go stale mid-session.
2400
+ */
2401
+ async sign(req, jwt) {
2350
2402
  if (!this.handle) throw new Error("no session");
2403
+ let gate = null;
2404
+ if (this.assertionPerSignature) {
2405
+ if (!jwt) throw new Error("sign requires a jwt when per-signature assertion is enabled");
2406
+ gate = await this.completeAssertion(jwt);
2407
+ }
2351
2408
  const res = await fetch(`${this.teeUrl}/signer/sign`, {
2352
2409
  method: "POST",
2353
2410
  headers: { "Content-Type": "application/json" },
2354
- body: JSON.stringify({ handle: this.handle, ...req })
2411
+ body: JSON.stringify({
2412
+ handle: this.handle,
2413
+ ...req,
2414
+ ...gate ? { ...gate, origin: window.location.origin } : {}
2415
+ })
2355
2416
  });
2356
2417
  if (res.status === 401) {
2418
+ const body = await res.clone().json().catch(() => ({}));
2419
+ if (body?.error === "assertion_required") {
2420
+ throw new ExportNotAuthorizedError();
2421
+ }
2357
2422
  this.handle = null;
2358
2423
  throw new SessionExpiredError();
2359
2424
  }
@@ -2376,10 +2441,11 @@ var TeeClient = class {
2376
2441
  // src/social/session.ts
2377
2442
  init_errors();
2378
2443
  var SessionManager = class {
2379
- constructor(events, durationMs, teeUrl) {
2444
+ constructor(events, durationMs, teeUrl, requireAssertionPerSignature = false) {
2380
2445
  this.events = events;
2381
2446
  this.durationMs = durationMs;
2382
2447
  this.teeUrl = teeUrl;
2448
+ this.requireAssertionPerSignature = requireAssertionPerSignature;
2383
2449
  this.tee = null;
2384
2450
  this.evmAddress = null;
2385
2451
  this.solanaAddress = null;
@@ -2399,7 +2465,18 @@ var SessionManager = class {
2399
2465
  */
2400
2466
  async start(deviceShare, jwt, fallback) {
2401
2467
  this.tee = new TeeClient(this.teeUrl);
2402
- const verified = await this.tee.openSession(deviceShare, jwt);
2468
+ const verified = await this.tee.openSession(
2469
+ deviceShare,
2470
+ jwt,
2471
+ this.requireAssertionPerSignature
2472
+ );
2473
+ if (this.requireAssertionPerSignature && !this.tee.requiresAssertionPerSignature()) {
2474
+ void this.tee.closeSession();
2475
+ this.tee = null;
2476
+ throw new Error(
2477
+ "requireAssertionPerSignature was requested but this signer does not support it \u2014 refusing to open a session with weaker authority than configured."
2478
+ );
2479
+ }
2403
2480
  const addresses = verified ?? fallback;
2404
2481
  if (!addresses) {
2405
2482
  this.tee = null;
@@ -2518,6 +2595,12 @@ function validatePin(pin) {
2518
2595
  // src/social/crypto/passkey.ts
2519
2596
  var import_browser2 = require("@simplewebauthn/browser");
2520
2597
  init_errors();
2598
+ function b64u(u) {
2599
+ return btoa(String.fromCharCode(...u)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2600
+ }
2601
+ function fromB64u(s) {
2602
+ return Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
2603
+ }
2521
2604
  function buf3(u) {
2522
2605
  return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
2523
2606
  }
@@ -2544,6 +2627,29 @@ async function registerPasskey(opts) {
2544
2627
  );
2545
2628
  return { credentialId, prfOutput, response };
2546
2629
  }
2630
+ async function authenticateWithPasskey(opts) {
2631
+ const optionsJSON = {
2632
+ ...opts.optionsJSON,
2633
+ extensions: {
2634
+ ...opts.optionsJSON.extensions,
2635
+ prf: { eval: { first: b64u(opts.prfSalt) } }
2636
+ }
2637
+ };
2638
+ let response;
2639
+ try {
2640
+ response = await (0, import_browser2.startAuthentication)({ optionsJSON });
2641
+ } catch (e) {
2642
+ if (e.name === "NotAllowedError") throw new UserCancelledError();
2643
+ throw e;
2644
+ }
2645
+ const ext = response.clientExtensionResults;
2646
+ const prfResults = ext?.["prf"]?.results;
2647
+ let prfOutput;
2648
+ if (prfResults?.first) {
2649
+ prfOutput = typeof prfResults.first === "string" ? fromB64u(prfResults.first) : new Uint8Array(prfResults.first);
2650
+ }
2651
+ return { response, prfOutput };
2652
+ }
2547
2653
  async function getPrfOutput(opts) {
2548
2654
  const challenge = crypto.getRandomValues(new Uint8Array(32));
2549
2655
  let cred;
@@ -2599,15 +2705,15 @@ async function verifyEmailCode(api, email, code) {
2599
2705
 
2600
2706
  // src/social/signers/evm.ts
2601
2707
  var import_ethers = require("ethers");
2602
- async function signEvmTransaction(tee, chain, tx) {
2708
+ async function signEvmTransaction(tee, chain, tx, jwt) {
2603
2709
  const resolved = await (0, import_ethers.resolveProperties)(tx);
2604
2710
  const unsigned = import_ethers.Transaction.from(resolved).unsignedSerialized;
2605
2711
  const payload = unsigned.startsWith("0x") ? unsigned.slice(2) : unsigned;
2606
2712
  const txHash = (0, import_ethers.keccak256)("0x" + payload);
2607
- return tee.sign({ chain, kind: "transaction", payload, txHash });
2713
+ return tee.sign({ chain, kind: "transaction", payload, txHash }, jwt);
2608
2714
  }
2609
- async function signEvmMessage(tee, chain, message) {
2610
- return tee.sign({ chain, kind: "message", payload: message });
2715
+ async function signEvmMessage(tee, chain, message, jwt) {
2716
+ return tee.sign({ chain, kind: "message", payload: message }, jwt);
2611
2717
  }
2612
2718
 
2613
2719
  // src/social/signers/solana.ts
@@ -2620,13 +2726,13 @@ function b64(bytes) {
2620
2726
  function unb64(s) {
2621
2727
  return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
2622
2728
  }
2623
- async function signSolanaTransaction(tee, chain, tx) {
2729
+ async function signSolanaTransaction(tee, chain, tx, jwt) {
2624
2730
  const bytes = tx instanceof import_web3.VersionedTransaction ? tx.serialize() : tx.serialize({ requireAllSignatures: false });
2625
- const signed = await tee.sign({ chain, kind: "transaction", payload: b64(bytes) });
2731
+ const signed = await tee.sign({ chain, kind: "transaction", payload: b64(bytes) }, jwt);
2626
2732
  return unb64(signed);
2627
2733
  }
2628
- async function signSolanaMessage(tee, chain, message) {
2629
- const sig = await tee.sign({ chain, kind: "message", payload: b64(message) });
2734
+ async function signSolanaMessage(tee, chain, message, jwt) {
2735
+ const sig = await tee.sign({ chain, kind: "message", payload: b64(message) }, jwt);
2630
2736
  return unb64(sig);
2631
2737
  }
2632
2738
 
@@ -2886,6 +2992,7 @@ function clearPersisted(key) {
2886
2992
  }
2887
2993
  }
2888
2994
  async function createDecaneConnect(config) {
2995
+ if (!config.appId) throw new Error("decane-connect-kit: appId is required");
2889
2996
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
2890
2997
  if (!config.chains?.length)
2891
2998
  throw new Error("decane-connect-kit: at least one chain is required");
@@ -2896,9 +3003,14 @@ async function createDecaneConnect(config) {
2896
3003
  const PERSIST_KEY = makePersistKey(config.apiKey);
2897
3004
  const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
2898
3005
  const events = new EventEmitter();
2899
- const api = new ApiClient(baseUrl, config.apiKey);
3006
+ const api = new ApiClient(baseUrl, config.apiKey, config.appId);
2900
3007
  const storage = new Storage();
2901
- const session = new SessionManager(events, durationMs, teeUrl);
3008
+ const session = new SessionManager(
3009
+ events,
3010
+ durationMs,
3011
+ teeUrl,
3012
+ config.requireAssertionPerSignature === true
3013
+ );
2902
3014
  let auth = null;
2903
3015
  let persisted = loadPersisted(PERSIST_KEY);
2904
3016
  async function pinSetup() {
@@ -3146,6 +3258,45 @@ async function createDecaneConnect(config) {
3146
3258
  }
3147
3259
  return addresses;
3148
3260
  }
3261
+ async function passkeyLoginCandidate() {
3262
+ if (!persisted?.userId || !isPasskeySupported()) return null;
3263
+ const stored = await storage.getShare(persisted.userId);
3264
+ if (!stored || stored.unlockMethod !== "passkey") return null;
3265
+ if (!stored.credentialId || !stored.prfSalt) return null;
3266
+ return stored;
3267
+ }
3268
+ async function passkeyLogin(stored) {
3269
+ const userId = stored.userId;
3270
+ const options = await api.getWebAuthnLoginOptions(userId, rpId);
3271
+ if (!options) {
3272
+ throw new NewDeviceError();
3273
+ }
3274
+ const { response, prfOutput } = await authenticateWithPasskey({
3275
+ optionsJSON: options,
3276
+ prfSalt: stored.prfSalt
3277
+ });
3278
+ if (!prfOutput) throw new ShareCorruptedError();
3279
+ const result = await api.postWebAuthnLogin(userId, response, window.location.origin);
3280
+ let deviceShare;
3281
+ try {
3282
+ deviceShare = await aesGcmDecrypt(
3283
+ await importAesKey(prfOutput),
3284
+ stored.wrappedDeviceShare,
3285
+ stored.iv
3286
+ );
3287
+ } catch {
3288
+ throw new ShareCorruptedError();
3289
+ }
3290
+ const rawAddresses = await api.getAddresses(result.jwt);
3291
+ const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana } : null;
3292
+ const addresses = await session.start(deviceShare, result.jwt, recordedAddresses);
3293
+ zeroize(deviceShare);
3294
+ auth = { jwt: result.jwt, userId, addresses };
3295
+ persisted = { userId, addresses };
3296
+ savePersisted(PERSIST_KEY, persisted);
3297
+ events.emit("connected");
3298
+ return { addresses, isNewUser: false };
3299
+ }
3149
3300
  async function handleAuthResult(result) {
3150
3301
  let addresses;
3151
3302
  if (!result.hasShare) {
@@ -3164,6 +3315,15 @@ async function createDecaneConnect(config) {
3164
3315
  if (auth && session.isActive()) {
3165
3316
  return { addresses: auth.addresses, isNewUser: false };
3166
3317
  }
3318
+ const candidate = await passkeyLoginCandidate();
3319
+ if (candidate) {
3320
+ try {
3321
+ return await passkeyLogin(candidate);
3322
+ } catch (e) {
3323
+ if (e instanceof UserCancelledError) throw e;
3324
+ console.warn("decane-connect-kit: passkey sign-in unavailable, falling back to standard sign-in.", e);
3325
+ }
3326
+ }
3167
3327
  const method = config.authMethods[0];
3168
3328
  if (!method) throw new CallSequenceError("No authMethods configured");
3169
3329
  if (method === "google") return client.connectWithGoogle();
@@ -3196,6 +3356,11 @@ async function createDecaneConnect(config) {
3196
3356
  return new Promise(() => {
3197
3357
  });
3198
3358
  },
3359
+ async connectWithToken(token, opts) {
3360
+ if (!token) throw new CallSequenceError("connectWithToken requires a token");
3361
+ const result = await api.authExternal(token, opts?.providerId);
3362
+ return handleAuthResult(result);
3363
+ },
3199
3364
  async connectWithEmail(email) {
3200
3365
  await startEmailAuth(api, email);
3201
3366
  return { codeRequired: true };
@@ -3204,7 +3369,22 @@ async function createDecaneConnect(config) {
3204
3369
  const result = await verifyEmailCode(api, email, code);
3205
3370
  return handleAuthResult(result);
3206
3371
  },
3372
+ async canSignInWithPasskey() {
3373
+ return await passkeyLoginCandidate() !== null;
3374
+ },
3375
+ async signInWithPasskey() {
3376
+ const candidate = await passkeyLoginCandidate();
3377
+ if (!candidate) throw new NewDeviceError();
3378
+ return passkeyLogin(candidate);
3379
+ },
3207
3380
  async unlock() {
3381
+ if (!auth) {
3382
+ const candidate = await passkeyLoginCandidate();
3383
+ if (candidate) {
3384
+ await passkeyLogin(candidate);
3385
+ return;
3386
+ }
3387
+ }
3208
3388
  if (!auth) {
3209
3389
  throw persisted ? new ReconnectRequiredError() : new CallSequenceError("Not connected \u2014 call connect() first");
3210
3390
  }
@@ -3229,11 +3409,11 @@ async function createDecaneConnect(config) {
3229
3409
  async signMessage(req) {
3230
3410
  const tee = session.requireActive();
3231
3411
  if (req.chain.startsWith("evm:")) {
3232
- return signEvmMessage(tee, req.chain, req.message);
3412
+ return signEvmMessage(tee, req.chain, req.message, auth?.jwt);
3233
3413
  }
3234
3414
  if (req.chain.startsWith("solana:")) {
3235
3415
  const msg = typeof req.message === "string" ? new TextEncoder().encode(req.message) : req.message;
3236
- const sig = await signSolanaMessage(tee, req.chain, msg);
3416
+ const sig = await signSolanaMessage(tee, req.chain, msg, auth?.jwt);
3237
3417
  return bufferToB64u(sig);
3238
3418
  }
3239
3419
  throw new CallSequenceError(`Unsupported chain: ${req.chain}`);
@@ -3281,7 +3461,7 @@ async function createDecaneConnect(config) {
3281
3461
  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 0n
3282
3462
  } : { gasPrice: req.gasPrice ?? feeData.gasPrice ?? void 0 }
3283
3463
  };
3284
- const signedTx = await signEvmTransaction(tee, req.chain, tx);
3464
+ const signedTx = await signEvmTransaction(tee, req.chain, tx, auth?.jwt);
3285
3465
  const txResponse = await provider.broadcastTransaction(signedTx);
3286
3466
  return txResponse.hash;
3287
3467
  },
@@ -3294,7 +3474,7 @@ async function createDecaneConnect(config) {
3294
3474
  tx = import_web32.Transaction.from(serializedTx);
3295
3475
  }
3296
3476
  const solChain = config.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
3297
- return signSolanaTransaction(tee, solChain, tx);
3477
+ return signSolanaTransaction(tee, solChain, tx, auth?.jwt);
3298
3478
  },
3299
3479
  async disconnect() {
3300
3480
  session.end();
@@ -3407,8 +3587,8 @@ async function createDecaneConnect(config) {
3407
3587
  function bufferToB64u(buf6) {
3408
3588
  return btoa(String.fromCharCode(...buf6)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
3409
3589
  }
3410
- function b64uToBuffer(b64u) {
3411
- const b644 = b64u.replace(/-/g, "+").replace(/_/g, "/");
3590
+ function b64uToBuffer(b64u2) {
3591
+ const b644 = b64u2.replace(/-/g, "+").replace(/_/g, "/");
3412
3592
  return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
3413
3593
  }
3414
3594
  function toHex(bytes) {
@@ -3795,11 +3975,16 @@ function WalletSelector({
3795
3975
  socialAuthMethods = ["google", "email"],
3796
3976
  onGoogleSignIn,
3797
3977
  onEmailStart,
3798
- onEmailVerify
3978
+ onEmailVerify,
3979
+ customAuthLabel,
3980
+ onCustomAuthSignIn,
3981
+ canUsePasskey = false,
3982
+ passkeyAddress = null,
3983
+ onPasskeySignIn
3799
3984
  }) {
3800
3985
  const showSocial = hasSocialMode(mode);
3801
3986
  const showWallets = mode !== "social";
3802
- const [view, setView] = (0, import_react2.useState)(showWallets ? "wallets" : "social");
3987
+ const [view, setView] = (0, import_react2.useState)(showSocial ? "social" : "wallets");
3803
3988
  const [search, setSearch] = (0, import_react2.useState)("");
3804
3989
  const [filter, setFilter] = (0, import_react2.useState)("all");
3805
3990
  const [pendingWallet, setPendingWallet] = (0, import_react2.useState)(null);
@@ -3808,6 +3993,8 @@ function WalletSelector({
3808
3993
  const [codeVal, setCodeVal] = (0, import_react2.useState)("");
3809
3994
  const [socialLoad, setSocialLoad] = (0, import_react2.useState)(false);
3810
3995
  const [socialErr, setSocialErr] = (0, import_react2.useState)("");
3996
+ const [showOtherMethods, setShowOtherMethods] = (0, import_react2.useState)(false);
3997
+ const passkeyFirst = canUsePasskey && !!onPasskeySignIn && !showOtherMethods;
3811
3998
  (0, import_react2.useEffect)(() => {
3812
3999
  if (!open) return;
3813
4000
  injectFont();
@@ -3823,17 +4010,36 @@ function WalletSelector({
3823
4010
  setSearch("");
3824
4011
  setFilter("all");
3825
4012
  setPendingWallet(null);
3826
- setView(showWallets ? "wallets" : "social");
4013
+ setView(showSocial ? "social" : "wallets");
3827
4014
  setEmailStep("input");
3828
4015
  setEmailVal("");
3829
4016
  setCodeVal("");
3830
4017
  setSocialErr("");
4018
+ setShowOtherMethods(false);
3831
4019
  return;
3832
4020
  }
3833
4021
  return () => {
3834
4022
  document.getElementById("decane-tokens")?.remove();
3835
4023
  };
3836
4024
  }, [open, showWallets]);
4025
+ async function handlePasskeySignIn() {
4026
+ if (!onPasskeySignIn) return;
4027
+ setSocialLoad(true);
4028
+ setSocialErr("");
4029
+ try {
4030
+ await onPasskeySignIn();
4031
+ } catch (e) {
4032
+ const err = e;
4033
+ if (err?.name !== "UserCancelledError") {
4034
+ setSocialErr(err?.message ?? "Passkey sign-in failed");
4035
+ setShowOtherMethods(true);
4036
+ }
4037
+ setSocialLoad(false);
4038
+ }
4039
+ }
4040
+ async function handlePasskeyClick() {
4041
+ void handlePasskeySignIn();
4042
+ }
3837
4043
  async function handleGoogleClick() {
3838
4044
  if (!onGoogleSignIn) return;
3839
4045
  setSocialLoad(true);
@@ -3845,6 +4051,17 @@ function WalletSelector({
3845
4051
  setSocialLoad(false);
3846
4052
  }
3847
4053
  }
4054
+ async function handleCustomAuthClick() {
4055
+ if (!onCustomAuthSignIn) return;
4056
+ setSocialLoad(true);
4057
+ setSocialErr("");
4058
+ try {
4059
+ await onCustomAuthSignIn();
4060
+ } catch (e) {
4061
+ setSocialErr(e.message);
4062
+ setSocialLoad(false);
4063
+ }
4064
+ }
3848
4065
  async function handleEmailStart(e) {
3849
4066
  e.preventDefault();
3850
4067
  if (!onEmailStart) return;
@@ -4119,7 +4336,7 @@ function WalletSelector({
4119
4336
  }
4120
4337
  },
4121
4338
  /* @__PURE__ */ import_react2.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M1.5 1.5L10.5 10.5M10.5 1.5L1.5 10.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }))
4122
- )), showWallets && showSocial && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 14, background: "var(--dc-surface)", borderRadius: 10, padding: 3 } }, ["wallets", "social"].map((v) => /* @__PURE__ */ import_react2.default.createElement(
4339
+ )), showWallets && showSocial && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 14, background: "var(--dc-surface)", borderRadius: 10, padding: 3 } }, ["social", "wallets"].map((v) => /* @__PURE__ */ import_react2.default.createElement(
4123
4340
  "button",
4124
4341
  {
4125
4342
  key: v,
@@ -4178,7 +4395,53 @@ function WalletSelector({
4178
4395
  e.currentTarget.style.borderColor = "var(--dc-border)";
4179
4396
  }
4180
4397
  }
4181
- ))), view === "social" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "4px 20px 24px", display: "flex", flexDirection: "column", gap: 10 } }, socialErr && /* @__PURE__ */ import_react2.default.createElement("div", { style: { background: "rgba(204,51,0,0.1)", border: "1px solid var(--dc-danger)", borderRadius: 8, padding: "9px 12px", fontSize: 12, color: "var(--dc-danger)" } }, socialErr), socialAuthMethods.includes("google") && /* @__PURE__ */ import_react2.default.createElement(
4398
+ ))), view === "social" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "4px 20px 24px", display: "flex", flexDirection: "column", gap: 10 } }, socialErr && /* @__PURE__ */ import_react2.default.createElement("div", { style: { background: "rgba(204,51,0,0.1)", border: "1px solid var(--dc-danger)", borderRadius: 8, padding: "9px 12px", fontSize: 12, color: "var(--dc-danger)" } }, socialErr), passkeyFirst && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement(
4399
+ "button",
4400
+ {
4401
+ onClick: handlePasskeyClick,
4402
+ disabled: socialLoad,
4403
+ style: {
4404
+ display: "flex",
4405
+ alignItems: "center",
4406
+ justifyContent: "center",
4407
+ gap: 10,
4408
+ width: "100%",
4409
+ padding: "13px 16px",
4410
+ background: "var(--dc-accent)",
4411
+ border: "none",
4412
+ borderRadius: 10,
4413
+ cursor: socialLoad ? "not-allowed" : "pointer",
4414
+ color: "var(--dc-accent-text)",
4415
+ fontSize: 14,
4416
+ fontWeight: 700,
4417
+ fontFamily: "inherit",
4418
+ opacity: socialLoad ? 0.6 : 1
4419
+ }
4420
+ },
4421
+ /* @__PURE__ */ import_react2.default.createElement("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "8", r: "4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M6 21v-1a4 4 0 0 1 4-4h1" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M17 15v6M14.5 18h5" })),
4422
+ socialLoad ? "Verifying\u2026" : "Continue with passkey"
4423
+ ), passkeyAddress && /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", fontSize: 12, color: "var(--dc-text-muted)" } }, "Resuming ", passkeyAddress.slice(0, 6), "\u2026", passkeyAddress.slice(-4)), /* @__PURE__ */ import_react2.default.createElement(
4424
+ "button",
4425
+ {
4426
+ onClick: () => {
4427
+ setShowOtherMethods(true);
4428
+ setSocialErr("");
4429
+ },
4430
+ disabled: socialLoad,
4431
+ style: {
4432
+ background: "none",
4433
+ border: "none",
4434
+ padding: "6px 0",
4435
+ color: "var(--dc-text-muted)",
4436
+ fontSize: 12.5,
4437
+ cursor: socialLoad ? "not-allowed" : "pointer",
4438
+ fontFamily: "inherit",
4439
+ textDecoration: "underline",
4440
+ textUnderlineOffset: 3
4441
+ }
4442
+ },
4443
+ "Use a different account"
4444
+ )), !passkeyFirst && socialAuthMethods.includes("google") && /* @__PURE__ */ import_react2.default.createElement(
4182
4445
  "button",
4183
4446
  {
4184
4447
  onClick: handleGoogleClick,
@@ -4210,7 +4473,7 @@ function WalletSelector({
4210
4473
  },
4211
4474
  /* @__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" })),
4212
4475
  socialLoad ? "Redirecting\u2026" : "Continue with Google"
4213
- ), 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)" } })), 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(
4476
+ ), !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(
4214
4477
  "input",
4215
4478
  {
4216
4479
  type: "email",
@@ -4329,7 +4592,38 @@ function WalletSelector({
4329
4592
  }
4330
4593
  },
4331
4594
  socialLoad ? "Verifying\u2026" : "Verify"
4332
- ))))), view === "wallets" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ import_react2.default.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), view === "wallets" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
4595
+ )))), !passkeyFirst && customAuthLabel && onCustomAuthSignIn && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, (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"), /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), /* @__PURE__ */ import_react2.default.createElement(
4596
+ "button",
4597
+ {
4598
+ onClick: handleCustomAuthClick,
4599
+ disabled: socialLoad,
4600
+ style: {
4601
+ display: "flex",
4602
+ alignItems: "center",
4603
+ justifyContent: "center",
4604
+ gap: 10,
4605
+ width: "100%",
4606
+ padding: "11px 16px",
4607
+ background: "var(--dc-surface)",
4608
+ border: "1px solid var(--dc-border)",
4609
+ borderRadius: 10,
4610
+ cursor: socialLoad ? "not-allowed" : "pointer",
4611
+ fontSize: 14,
4612
+ fontWeight: 600,
4613
+ color: "var(--dc-text)",
4614
+ fontFamily: "inherit",
4615
+ transition: "border-color 0.12s, background 0.12s",
4616
+ opacity: socialLoad ? 0.6 : 1
4617
+ },
4618
+ onMouseEnter: (e) => {
4619
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
4620
+ },
4621
+ onMouseLeave: (e) => {
4622
+ e.currentTarget.style.borderColor = "var(--dc-border)";
4623
+ }
4624
+ },
4625
+ socialLoad ? "Connecting\u2026" : customAuthLabel
4626
+ ))), view === "wallets" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ import_react2.default.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), view === "wallets" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
4333
4627
  )
4334
4628
  )
4335
4629
  );
@@ -4451,13 +4745,16 @@ function SignInView({
4451
4745
  onEmailStart,
4452
4746
  onEmailVerify,
4453
4747
  authMethods,
4454
- resumeHint
4748
+ resumeHint,
4749
+ customAuthLabel,
4750
+ onCustomAuthSignIn
4455
4751
  }) {
4456
4752
  const [googleLoading, setGoogleLoading] = (0, import_react3.useState)(false);
4457
4753
  const [email, setEmail] = (0, import_react3.useState)("");
4458
4754
  const [code, setCode] = (0, import_react3.useState)("");
4459
4755
  const [emailSent, setEmailSent] = (0, import_react3.useState)(false);
4460
4756
  const [emailLoading, setEmailLoading] = (0, import_react3.useState)(false);
4757
+ const [customLoading, setCustomLoading] = (0, import_react3.useState)(false);
4461
4758
  const [error, setError] = (0, import_react3.useState)("");
4462
4759
  async function handleGoogle() {
4463
4760
  setError("");
@@ -4494,6 +4791,18 @@ function SignInView({
4494
4791
  setEmailLoading(false);
4495
4792
  }
4496
4793
  }
4794
+ async function handleCustomAuth() {
4795
+ if (!onCustomAuthSignIn) return;
4796
+ setError("");
4797
+ setCustomLoading(true);
4798
+ try {
4799
+ await onCustomAuthSignIn();
4800
+ } catch (e) {
4801
+ setError(e.message);
4802
+ } finally {
4803
+ setCustomLoading(false);
4804
+ }
4805
+ }
4497
4806
  const inputStyle = {
4498
4807
  width: "100%",
4499
4808
  background: "var(--dc-surface)",
@@ -4566,7 +4875,27 @@ function SignInView({
4566
4875
  fontWeight: 700,
4567
4876
  cursor: "pointer",
4568
4877
  fontFamily: "inherit"
4569
- } }, emailLoading ? "\u2026" : "Verify"))), error && /* @__PURE__ */ import_react3.default.createElement("div", { style: { marginTop: 10, fontSize: 12, color: "var(--dc-danger, #ff5533)" } }, error));
4878
+ } }, emailLoading ? "\u2026" : "Verify"))), customAuthLabel && onCustomAuthSignIn && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, (authMethods.includes("google") || authMethods.includes("email")) && /* @__PURE__ */ import_react3.default.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), /* @__PURE__ */ import_react3.default.createElement(
4879
+ "button",
4880
+ {
4881
+ onClick: handleCustomAuth,
4882
+ disabled: customLoading,
4883
+ style: {
4884
+ width: "100%",
4885
+ padding: "10px 0",
4886
+ background: "var(--dc-surface)",
4887
+ color: "var(--dc-text)",
4888
+ border: "1px solid var(--dc-border)",
4889
+ borderRadius: 10,
4890
+ fontSize: 13,
4891
+ fontWeight: 600,
4892
+ cursor: customLoading ? "not-allowed" : "pointer",
4893
+ opacity: customLoading ? 0.7 : 1,
4894
+ fontFamily: "inherit"
4895
+ }
4896
+ },
4897
+ customLoading ? "Connecting\u2026" : customAuthLabel
4898
+ )), error && /* @__PURE__ */ import_react3.default.createElement("div", { style: { marginTop: 10, fontSize: 12, color: "var(--dc-danger, #ff5533)" } }, error));
4570
4899
  }
4571
4900
  function RecoverySection({
4572
4901
  addresses,
@@ -4683,6 +5012,8 @@ function SocialWalletModal({
4683
5012
  onEmailStart,
4684
5013
  onEmailVerify,
4685
5014
  authMethods = ["google", "email"],
5015
+ customAuthLabel,
5016
+ onCustomAuthSignIn,
4686
5017
  onRotateShares,
4687
5018
  onExportPortableBackup,
4688
5019
  rpcUrls,
@@ -4838,7 +5169,9 @@ function SocialWalletModal({
4838
5169
  onEmailStart,
4839
5170
  onEmailVerify,
4840
5171
  authMethods,
4841
- resumeHint: socialAuth.needsReconnect ? "your wallet" : void 0
5172
+ resumeHint: socialAuth.needsReconnect ? "your wallet" : void 0,
5173
+ customAuthLabel,
5174
+ onCustomAuthSignIn
4842
5175
  }
4843
5176
  ) : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("div", { style: {
4844
5177
  margin: "0 16px 14px",
@@ -4924,7 +5257,8 @@ var DEFAULT_SOCIAL = {
4924
5257
  isNewUser: false,
4925
5258
  isUnlocked: false,
4926
5259
  sessionExpiresAt: null,
4927
- needsReconnect: false
5260
+ needsReconnect: false,
5261
+ canUsePasskey: false
4928
5262
  };
4929
5263
  var DecaneContext = (0, import_react4.createContext)(null);
4930
5264
  function hasSocial(mode) {
@@ -4939,6 +5273,16 @@ function deriveNetworks(mode, explicit) {
4939
5273
  }
4940
5274
  function DecaneKit({ config, children }) {
4941
5275
  const mode = config?.mode ?? "wallets";
5276
+ if (!config?.appId) {
5277
+ throw new Error(
5278
+ "DecaneKit: config.appId is required. Pass your dashboard project ID."
5279
+ );
5280
+ }
5281
+ if (hasSocial(mode) && !config.social?.apiKey) {
5282
+ throw new Error(
5283
+ `DecaneKit: config.social.apiKey is required for social sign-in (mode "${mode}"). Create an API key for this project in the dashboard.`
5284
+ );
5285
+ }
4942
5286
  const walletRef = (0, import_react4.useRef)(null);
4943
5287
  if (!walletRef.current) walletRef.current = createDecane(config);
4944
5288
  const socialRef = (0, import_react4.useRef)(null);
@@ -4969,6 +5313,7 @@ function DecaneKit({ config, children }) {
4969
5313
  socialInitialized.current = true;
4970
5314
  const { apiKey, authMethods, promptPin, apiBaseUrl, chains, rpcUrls } = config.social;
4971
5315
  createDecaneConnect({
5316
+ appId: config.appId,
4972
5317
  apiKey,
4973
5318
  apiBaseUrl,
4974
5319
  authMethods: authMethods ?? ["google", "email"],
@@ -4986,6 +5331,8 @@ function DecaneKit({ config, children }) {
4986
5331
  if (saved) {
4987
5332
  setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false, needsReconnect: true }));
4988
5333
  }
5334
+ sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
5335
+ });
4989
5336
  const params = new URLSearchParams(window.location.search);
4990
5337
  if (params.has("decane_jwt") || params.has("decane_error")) {
4991
5338
  sdk.connectWithGoogle().then(applyResult).catch(console.error);
@@ -4999,9 +5346,20 @@ function DecaneKit({ config, children }) {
4999
5346
  isNewUser: result.isNewUser,
5000
5347
  isUnlocked: sdk?.isUnlocked() ?? false,
5001
5348
  sessionExpiresAt: sdk?.sessionExpiresAt() ?? null,
5002
- needsReconnect: false
5349
+ needsReconnect: false,
5350
+ canUsePasskey: false
5351
+ });
5352
+ sdk?.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
5003
5353
  });
5004
5354
  }
5355
+ const signInWithPasskey = (0, import_react4.useCallback)(async () => {
5356
+ const sdk = socialRef.current;
5357
+ if (!sdk) throw new Error("Social sign-in not configured");
5358
+ const result = await sdk.signInWithPasskey();
5359
+ applyResult(result);
5360
+ setSelectorOpen(false);
5361
+ return result;
5362
+ }, []);
5005
5363
  const connectWithGoogle = (0, import_react4.useCallback)(async () => {
5006
5364
  const sdk = socialRef.current;
5007
5365
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -5019,6 +5377,19 @@ function DecaneKit({ config, children }) {
5019
5377
  applyResult(result);
5020
5378
  setSelectorOpen(false);
5021
5379
  }, []);
5380
+ const connectWithToken = (0, import_react4.useCallback)(async (token, opts) => {
5381
+ const sdk = socialRef.current;
5382
+ if (!sdk) throw new Error("Social sign-in not configured");
5383
+ const result = await sdk.connectWithToken(token, opts);
5384
+ applyResult(result);
5385
+ setSelectorOpen(false);
5386
+ }, []);
5387
+ const customAuthConfig = config?.social?.customAuth;
5388
+ const connectWithCustomAuth = (0, import_react4.useCallback)(async () => {
5389
+ if (!customAuthConfig) throw new Error("Custom auth not configured");
5390
+ const token = await customAuthConfig.getToken();
5391
+ await connectWithToken(token, customAuthConfig.providerId ? { providerId: customAuthConfig.providerId } : void 0);
5392
+ }, [customAuthConfig, connectWithToken]);
5022
5393
  const socialUnlock = (0, import_react4.useCallback)(async () => {
5023
5394
  const sdk = socialRef.current;
5024
5395
  if (!sdk) return;
@@ -5095,9 +5466,13 @@ function DecaneKit({ config, children }) {
5095
5466
  setTheme,
5096
5467
  mode,
5097
5468
  socialAuth,
5469
+ signInWithPasskey,
5098
5470
  connectWithGoogle,
5099
5471
  connectWithEmail,
5100
5472
  verifyEmailCode: verifyEmailCode2,
5473
+ connectWithToken,
5474
+ customAuthLabel: customAuthConfig?.label,
5475
+ connectWithCustomAuth: customAuthConfig ? connectWithCustomAuth : void 0,
5101
5476
  socialUnlock,
5102
5477
  socialDisconnect,
5103
5478
  socialSignMessage,
@@ -5127,7 +5502,12 @@ function DecaneKit({ config, children }) {
5127
5502
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
5128
5503
  onGoogleSignIn: connectWithGoogle,
5129
5504
  onEmailStart: connectWithEmail,
5130
- onEmailVerify: verifyEmailCode2
5505
+ onEmailVerify: verifyEmailCode2,
5506
+ canUsePasskey: socialAuth.canUsePasskey,
5507
+ passkeyAddress: socialAuth.addresses?.evm ?? socialAuth.addresses?.solana ?? null,
5508
+ onPasskeySignIn: signInWithPasskey,
5509
+ customAuthLabel: customAuthConfig?.label,
5510
+ onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0
5131
5511
  }
5132
5512
  ),
5133
5513
  /* @__PURE__ */ import_react4.default.createElement(
@@ -5139,6 +5519,8 @@ function DecaneKit({ config, children }) {
5139
5519
  socialAuth,
5140
5520
  onUnlock: socialUnlock,
5141
5521
  onDisconnect: socialDisconnect,
5522
+ customAuthLabel: customAuthConfig?.label,
5523
+ onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
5142
5524
  onGoogleSignIn: connectWithGoogle,
5143
5525
  onEmailStart: connectWithEmail,
5144
5526
  onEmailVerify: verifyEmailCode2,
@@ -5169,11 +5551,16 @@ function useSocialAuth() {
5169
5551
  connectWithGoogle,
5170
5552
  connectWithEmail,
5171
5553
  verifyEmailCode: verifyEmailCode2,
5554
+ connectWithToken,
5555
+ customAuthLabel,
5556
+ connectWithCustomAuth,
5172
5557
  socialUnlock,
5173
5558
  socialDisconnect
5174
5559
  } = useDecaneContext();
5175
5560
  const [googleLoading, setGoogleLoading] = (0, import_react5.useState)(false);
5176
5561
  const [emailLoading, setEmailLoading] = (0, import_react5.useState)(false);
5562
+ const [tokenLoading, setTokenLoading] = (0, import_react5.useState)(false);
5563
+ const [customLoading, setCustomLoading] = (0, import_react5.useState)(false);
5177
5564
  const [error, setError] = (0, import_react5.useState)(null);
5178
5565
  const signInWithGoogle = (0, import_react5.useCallback)(async () => {
5179
5566
  setGoogleLoading(true);
@@ -5210,6 +5597,31 @@ function useSocialAuth() {
5210
5597
  setEmailLoading(false);
5211
5598
  }
5212
5599
  }, [verifyEmailCode2]);
5600
+ const signInWithToken = (0, import_react5.useCallback)(async (token, opts) => {
5601
+ setTokenLoading(true);
5602
+ setError(null);
5603
+ try {
5604
+ await connectWithToken(token, opts);
5605
+ } catch (e) {
5606
+ setError(e.message);
5607
+ throw e;
5608
+ } finally {
5609
+ setTokenLoading(false);
5610
+ }
5611
+ }, [connectWithToken]);
5612
+ const signInWithCustomAuth = (0, import_react5.useCallback)(async () => {
5613
+ if (!connectWithCustomAuth) throw new Error("Custom auth not configured");
5614
+ setCustomLoading(true);
5615
+ setError(null);
5616
+ try {
5617
+ await connectWithCustomAuth();
5618
+ } catch (e) {
5619
+ setError(e.message);
5620
+ throw e;
5621
+ } finally {
5622
+ setCustomLoading(false);
5623
+ }
5624
+ }, [connectWithCustomAuth]);
5213
5625
  const unlock = (0, import_react5.useCallback)(async () => {
5214
5626
  setError(null);
5215
5627
  try {
@@ -5241,11 +5653,16 @@ function useSocialAuth() {
5241
5653
  needsReconnect: socialAuth.needsReconnect,
5242
5654
  googleLoading,
5243
5655
  emailLoading,
5656
+ tokenLoading,
5657
+ customLoading,
5658
+ customAuthLabel,
5244
5659
  error,
5245
5660
  clearError: () => setError(null),
5246
5661
  signInWithGoogle,
5247
5662
  sendEmailCode,
5248
5663
  confirmEmailCode,
5664
+ signInWithToken,
5665
+ signInWithCustomAuth: connectWithCustomAuth ? signInWithCustomAuth : void 0,
5249
5666
  unlock,
5250
5667
  disconnect
5251
5668
  };