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.d.cts CHANGED
@@ -81,8 +81,24 @@ interface SocialConfig {
81
81
  apiBaseUrl?: string;
82
82
  chains?: string[];
83
83
  rpcUrls?: Record<string, string>;
84
+ /**
85
+ * Sign in with a token from your own auth system (Auth0, Clerk, Firebase,
86
+ * Cognito, or your own backend) — surfaced as a button in the built-in
87
+ * WalletSelector / SocialWalletModal UI, alongside Google and email.
88
+ * getToken() runs when the user clicks the button; wire it to however you
89
+ * already obtain a token from your auth system (e.g. after your own login
90
+ * flow completes). Configure the issuer and JWKS URL for this token
91
+ * against your project in the dashboard first. `providerId` is only
92
+ * needed when the project has more than one custom provider configured.
93
+ */
94
+ customAuth?: {
95
+ label: string;
96
+ providerId?: string;
97
+ getToken: () => Promise<string>;
98
+ };
84
99
  }
85
100
  interface DecaneConfig {
101
+ appId: string;
86
102
  chains?: ChainDescriptor[];
87
103
  supportedChainIds?: string[];
88
104
  networks?: "evm" | "solana" | "tron" | "bitcoin" | "all";
@@ -132,12 +148,13 @@ interface BitcoinProvider {
132
148
  on?(event: string, handler: (...args: unknown[]) => void): void;
133
149
  }
134
150
 
135
- declare function createDecane(config?: DecaneConfig): DecaneProvider;
151
+ declare function createDecane(config: DecaneConfig): DecaneProvider;
136
152
 
137
153
  declare class ApiClient {
138
154
  private readonly baseUrl;
139
155
  private readonly apiKey;
140
- constructor(baseUrl: string, apiKey: string);
156
+ private readonly appId;
157
+ constructor(baseUrl: string, apiKey: string, appId: string);
141
158
  private _request;
142
159
  getGoogleInitUrl(): Promise<{
143
160
  url: string;
@@ -157,6 +174,19 @@ declare class ApiClient {
157
174
  }): Promise<void>;
158
175
  getWebAuthnRegisterOptions(jwt: string, rpId: string, rpName: string): Promise<_simplewebauthn_browser.PublicKeyCredentialCreationOptionsJSON>;
159
176
  postWebAuthnRegister(jwt: string, response: _simplewebauthn_browser.RegistrationResponseJSON, origin: string): Promise<void>;
177
+ /**
178
+ * Exchange a token from the developer's own auth system for a Decane
179
+ * session. `providerId` is only needed when the project has more than one
180
+ * provider configured.
181
+ */
182
+ authExternal(token: string, providerId?: string): Promise<AuthResult>;
183
+ /**
184
+ * Returns null when this user has no server-registered passkey, which is the
185
+ * expected answer for wallets created before passkey sign-in existed or for
186
+ * PIN-only users — the caller falls back to the normal sign-in flow.
187
+ */
188
+ getWebAuthnLoginOptions(userId: string, rpId: string): Promise<_simplewebauthn_browser.PublicKeyCredentialRequestOptionsJSON | null>;
189
+ postWebAuthnLogin(userId: string, response: _simplewebauthn_browser.AuthenticationResponseJSON, origin: string): Promise<AuthResult>;
160
190
  revoke(jwt: string): Promise<void>;
161
191
  hasRecoveryBackup(jwt: string): Promise<boolean>;
162
192
  getRecoveryBackup(jwt: string): Promise<{
@@ -244,12 +274,34 @@ interface PortableBackup {
244
274
 
245
275
  type Chain = `evm:${number}` | `solana:${'mainnet' | 'devnet'}`;
246
276
  interface DecaneConnectConfig {
277
+ /** Dashboard project ID (public). Sent with every request so the backend can
278
+ * confirm the API key below actually belongs to this project. */
279
+ appId: string;
247
280
  apiKey: string;
248
281
  chains: Chain[];
249
282
  authMethods: Array<'google' | 'email'>;
250
283
  apiBaseUrl?: string;
251
284
  sessionDurationMinutes?: number;
252
285
  promptPin?: () => Promise<string>;
286
+ /**
287
+ * Require a fresh WebAuthn assertion for every signature, instead of letting
288
+ * an unlocked session sign freely until it expires. The TEE session stays
289
+ * warm — this costs one authenticator prompt per signature, not a re-auth or
290
+ * re-attestation — and the check is enforced inside the enclave, not here.
291
+ *
292
+ * Default false, which preserves the session-signing model: unlock once, sign
293
+ * for the session's lifetime with no further prompts.
294
+ *
295
+ * Two things worth knowing before turning this on:
296
+ * - Multi-step flows prompt per leg. An ERC-20 approve + swap is two
297
+ * prompts, not one.
298
+ * - The policy is declared by the client at session open, so it hardens a
299
+ * leaked session handle (useless without the authenticator) but a fully
300
+ * compromised page could open a session without it. Making it
301
+ * unbypassable requires the policy to come from the server per user or
302
+ * per project; that is not implemented yet.
303
+ */
304
+ requireAssertionPerSignature?: boolean;
253
305
  rpId?: string;
254
306
  rpName?: string;
255
307
  rpcUrls?: Record<string, string>;
@@ -313,6 +365,20 @@ interface DecaneConnect {
313
365
  connectWithEmail(email: string): Promise<{
314
366
  codeRequired: true;
315
367
  }>;
368
+ /**
369
+ * Sign in with a JWT from your own auth system (Auth0, Clerk, Firebase,
370
+ * Cognito, your own backend — anything that publishes a JWKS endpoint).
371
+ *
372
+ * Configure the issuer and JWKS URL against your project in the dashboard;
373
+ * no client-side setup and no secret in your dApp. `providerId` is only
374
+ * required if the project has more than one provider configured.
375
+ *
376
+ * The user's wallet is derived from the configured identity claim, so the
377
+ * same token subject always resolves to the same wallet.
378
+ */
379
+ connectWithToken(token: string, opts?: {
380
+ providerId?: string;
381
+ }): Promise<ConnectResult>;
316
382
  verifyEmailCode(email: string, code: string): Promise<ConnectResult>;
317
383
  unlock(): Promise<void>;
318
384
  isUnlocked(): boolean;
@@ -320,8 +386,25 @@ interface DecaneConnect {
320
386
  * True when the user's identity is known from a previous visit but there is
321
387
  * no live session, so unlock() cannot succeed and they must sign in again.
322
388
  * Check this before offering an "Unlock" action.
389
+ *
390
+ * Note this can be true while canSignInWithPasskey() is also true — in which
391
+ * case "sign in again" costs one biometric prompt, not a full auth round
392
+ * trip. Prefer offering that over a Google/email button.
323
393
  */
324
394
  needsReconnect(): boolean;
395
+ /**
396
+ * True when a returning user can be signed straight back in with the passkey
397
+ * already on this device — no Google redirect, no email code. Requires a
398
+ * remembered identity plus a passkey-wrapped device share in local storage,
399
+ * so it is false on new devices and for PIN-only wallets.
400
+ */
401
+ canSignInWithPasskey(): Promise<boolean>;
402
+ /**
403
+ * Sign in using only the device passkey. connect() already tries this first
404
+ * when it's available, so call this directly only when driving the UI
405
+ * yourself. Throws NewDeviceError when this device isn't eligible.
406
+ */
407
+ signInWithPasskey(): Promise<ConnectResult>;
325
408
  sessionExpiresAt(): number | null;
326
409
  signMessage(req: SignMessageRequest): Promise<string>;
327
410
  sendTransaction(req: SendTransactionRequest): Promise<string>;
@@ -568,8 +651,16 @@ interface WalletSelectorProps {
568
651
  codeRequired: true;
569
652
  }>;
570
653
  onEmailVerify?: (email: string, code: string) => Promise<void>;
571
- }
572
- declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onEmailStart, onEmailVerify, }: WalletSelectorProps): React.ReactPortal | null;
654
+ /** Button text for the developer's own auth system, e.g. "Continue with Acme Inc". Omitted entirely when not configured. */
655
+ customAuthLabel?: string;
656
+ onCustomAuthSignIn?: () => Promise<void>;
657
+ /** Returning user on this device — offer the passkey instead of sign-in. */
658
+ canUsePasskey?: boolean;
659
+ /** Address shown alongside the passkey button, so they know who they're resuming as. */
660
+ passkeyAddress?: string | null;
661
+ onPasskeySignIn?: () => Promise<unknown>;
662
+ }
663
+ declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onEmailStart, onEmailVerify, customAuthLabel, onCustomAuthSignIn, canUsePasskey, passkeyAddress, onPasskeySignIn, }: WalletSelectorProps): React.ReactPortal | null;
573
664
 
574
665
  type Theme = "light" | "dark" | "auto";
575
666
  interface SocialAuthState {
@@ -586,10 +677,16 @@ interface SocialAuthState {
586
677
  * requires a fresh, valid JWT to open a TEE session, and JWTs are
587
678
  * deliberately never persisted (short-lived, security-sensitive), so
588
679
  * there is no legitimate "just unlock" path in this state. A consumer
589
- * should route this into a full reconnect (connectWithGoogle() /
590
- * connectWithEmail()), not unlock() — which will always throw here.
680
+ * should route this into a full reconnect — but see canUsePasskey: when
681
+ * that is true the reconnect is a single biometric prompt rather than a
682
+ * Google redirect or an email code.
591
683
  */
592
684
  needsReconnect: boolean;
685
+ /**
686
+ * True when this device holds a passkey-wrapped share for the remembered
687
+ * user, so signing back in costs one passkey prompt and nothing else.
688
+ */
689
+ canUsePasskey: boolean;
593
690
  }
594
691
  interface DecaneKitProps {
595
692
  config?: DecaneConfig;
@@ -610,6 +707,8 @@ interface SocialWalletModalProps {
610
707
  }>;
611
708
  onEmailVerify: (email: string, code: string) => Promise<void>;
612
709
  authMethods?: Array<"google" | "email">;
710
+ customAuthLabel?: string;
711
+ onCustomAuthSignIn?: () => Promise<void>;
613
712
  onRotateShares: (opts: {
614
713
  password: string;
615
714
  passwordHint?: string;
@@ -621,7 +720,7 @@ interface SocialWalletModalProps {
621
720
  rpcUrls?: Record<string, string>;
622
721
  chains?: string[];
623
722
  }
624
- declare function SocialWalletModal({ open, onClose, theme, socialAuth, onUnlock, onDisconnect, onGoogleSignIn, onEmailStart, onEmailVerify, authMethods, onRotateShares, onExportPortableBackup, rpcUrls, chains, }: SocialWalletModalProps): React.ReactPortal | null;
723
+ declare function SocialWalletModal({ open, onClose, theme, socialAuth, onUnlock, onDisconnect, onGoogleSignIn, onEmailStart, onEmailVerify, authMethods, customAuthLabel, onCustomAuthSignIn, onRotateShares, onExportPortableBackup, rpcUrls, chains, }: SocialWalletModalProps): React.ReactPortal | null;
625
724
 
626
725
  declare function useSocialAuth(): {
627
726
  isConnected: boolean;
@@ -635,6 +734,9 @@ declare function useSocialAuth(): {
635
734
  needsReconnect: boolean;
636
735
  googleLoading: boolean;
637
736
  emailLoading: boolean;
737
+ tokenLoading: boolean;
738
+ customLoading: boolean;
739
+ customAuthLabel: string | undefined;
638
740
  error: string | null;
639
741
  clearError: () => void;
640
742
  signInWithGoogle: () => Promise<void>;
@@ -642,6 +744,10 @@ declare function useSocialAuth(): {
642
744
  codeRequired: true;
643
745
  }>;
644
746
  confirmEmailCode: (email: string, code: string) => Promise<void>;
747
+ signInWithToken: (token: string, opts?: {
748
+ providerId?: string;
749
+ }) => Promise<void>;
750
+ signInWithCustomAuth: (() => Promise<void>) | undefined;
645
751
  unlock: () => Promise<void>;
646
752
  disconnect: () => Promise<void>;
647
753
  };
package/dist/index.d.ts CHANGED
@@ -81,8 +81,24 @@ interface SocialConfig {
81
81
  apiBaseUrl?: string;
82
82
  chains?: string[];
83
83
  rpcUrls?: Record<string, string>;
84
+ /**
85
+ * Sign in with a token from your own auth system (Auth0, Clerk, Firebase,
86
+ * Cognito, or your own backend) — surfaced as a button in the built-in
87
+ * WalletSelector / SocialWalletModal UI, alongside Google and email.
88
+ * getToken() runs when the user clicks the button; wire it to however you
89
+ * already obtain a token from your auth system (e.g. after your own login
90
+ * flow completes). Configure the issuer and JWKS URL for this token
91
+ * against your project in the dashboard first. `providerId` is only
92
+ * needed when the project has more than one custom provider configured.
93
+ */
94
+ customAuth?: {
95
+ label: string;
96
+ providerId?: string;
97
+ getToken: () => Promise<string>;
98
+ };
84
99
  }
85
100
  interface DecaneConfig {
101
+ appId: string;
86
102
  chains?: ChainDescriptor[];
87
103
  supportedChainIds?: string[];
88
104
  networks?: "evm" | "solana" | "tron" | "bitcoin" | "all";
@@ -132,12 +148,13 @@ interface BitcoinProvider {
132
148
  on?(event: string, handler: (...args: unknown[]) => void): void;
133
149
  }
134
150
 
135
- declare function createDecane(config?: DecaneConfig): DecaneProvider;
151
+ declare function createDecane(config: DecaneConfig): DecaneProvider;
136
152
 
137
153
  declare class ApiClient {
138
154
  private readonly baseUrl;
139
155
  private readonly apiKey;
140
- constructor(baseUrl: string, apiKey: string);
156
+ private readonly appId;
157
+ constructor(baseUrl: string, apiKey: string, appId: string);
141
158
  private _request;
142
159
  getGoogleInitUrl(): Promise<{
143
160
  url: string;
@@ -157,6 +174,19 @@ declare class ApiClient {
157
174
  }): Promise<void>;
158
175
  getWebAuthnRegisterOptions(jwt: string, rpId: string, rpName: string): Promise<_simplewebauthn_browser.PublicKeyCredentialCreationOptionsJSON>;
159
176
  postWebAuthnRegister(jwt: string, response: _simplewebauthn_browser.RegistrationResponseJSON, origin: string): Promise<void>;
177
+ /**
178
+ * Exchange a token from the developer's own auth system for a Decane
179
+ * session. `providerId` is only needed when the project has more than one
180
+ * provider configured.
181
+ */
182
+ authExternal(token: string, providerId?: string): Promise<AuthResult>;
183
+ /**
184
+ * Returns null when this user has no server-registered passkey, which is the
185
+ * expected answer for wallets created before passkey sign-in existed or for
186
+ * PIN-only users — the caller falls back to the normal sign-in flow.
187
+ */
188
+ getWebAuthnLoginOptions(userId: string, rpId: string): Promise<_simplewebauthn_browser.PublicKeyCredentialRequestOptionsJSON | null>;
189
+ postWebAuthnLogin(userId: string, response: _simplewebauthn_browser.AuthenticationResponseJSON, origin: string): Promise<AuthResult>;
160
190
  revoke(jwt: string): Promise<void>;
161
191
  hasRecoveryBackup(jwt: string): Promise<boolean>;
162
192
  getRecoveryBackup(jwt: string): Promise<{
@@ -244,12 +274,34 @@ interface PortableBackup {
244
274
 
245
275
  type Chain = `evm:${number}` | `solana:${'mainnet' | 'devnet'}`;
246
276
  interface DecaneConnectConfig {
277
+ /** Dashboard project ID (public). Sent with every request so the backend can
278
+ * confirm the API key below actually belongs to this project. */
279
+ appId: string;
247
280
  apiKey: string;
248
281
  chains: Chain[];
249
282
  authMethods: Array<'google' | 'email'>;
250
283
  apiBaseUrl?: string;
251
284
  sessionDurationMinutes?: number;
252
285
  promptPin?: () => Promise<string>;
286
+ /**
287
+ * Require a fresh WebAuthn assertion for every signature, instead of letting
288
+ * an unlocked session sign freely until it expires. The TEE session stays
289
+ * warm — this costs one authenticator prompt per signature, not a re-auth or
290
+ * re-attestation — and the check is enforced inside the enclave, not here.
291
+ *
292
+ * Default false, which preserves the session-signing model: unlock once, sign
293
+ * for the session's lifetime with no further prompts.
294
+ *
295
+ * Two things worth knowing before turning this on:
296
+ * - Multi-step flows prompt per leg. An ERC-20 approve + swap is two
297
+ * prompts, not one.
298
+ * - The policy is declared by the client at session open, so it hardens a
299
+ * leaked session handle (useless without the authenticator) but a fully
300
+ * compromised page could open a session without it. Making it
301
+ * unbypassable requires the policy to come from the server per user or
302
+ * per project; that is not implemented yet.
303
+ */
304
+ requireAssertionPerSignature?: boolean;
253
305
  rpId?: string;
254
306
  rpName?: string;
255
307
  rpcUrls?: Record<string, string>;
@@ -313,6 +365,20 @@ interface DecaneConnect {
313
365
  connectWithEmail(email: string): Promise<{
314
366
  codeRequired: true;
315
367
  }>;
368
+ /**
369
+ * Sign in with a JWT from your own auth system (Auth0, Clerk, Firebase,
370
+ * Cognito, your own backend — anything that publishes a JWKS endpoint).
371
+ *
372
+ * Configure the issuer and JWKS URL against your project in the dashboard;
373
+ * no client-side setup and no secret in your dApp. `providerId` is only
374
+ * required if the project has more than one provider configured.
375
+ *
376
+ * The user's wallet is derived from the configured identity claim, so the
377
+ * same token subject always resolves to the same wallet.
378
+ */
379
+ connectWithToken(token: string, opts?: {
380
+ providerId?: string;
381
+ }): Promise<ConnectResult>;
316
382
  verifyEmailCode(email: string, code: string): Promise<ConnectResult>;
317
383
  unlock(): Promise<void>;
318
384
  isUnlocked(): boolean;
@@ -320,8 +386,25 @@ interface DecaneConnect {
320
386
  * True when the user's identity is known from a previous visit but there is
321
387
  * no live session, so unlock() cannot succeed and they must sign in again.
322
388
  * Check this before offering an "Unlock" action.
389
+ *
390
+ * Note this can be true while canSignInWithPasskey() is also true — in which
391
+ * case "sign in again" costs one biometric prompt, not a full auth round
392
+ * trip. Prefer offering that over a Google/email button.
323
393
  */
324
394
  needsReconnect(): boolean;
395
+ /**
396
+ * True when a returning user can be signed straight back in with the passkey
397
+ * already on this device — no Google redirect, no email code. Requires a
398
+ * remembered identity plus a passkey-wrapped device share in local storage,
399
+ * so it is false on new devices and for PIN-only wallets.
400
+ */
401
+ canSignInWithPasskey(): Promise<boolean>;
402
+ /**
403
+ * Sign in using only the device passkey. connect() already tries this first
404
+ * when it's available, so call this directly only when driving the UI
405
+ * yourself. Throws NewDeviceError when this device isn't eligible.
406
+ */
407
+ signInWithPasskey(): Promise<ConnectResult>;
325
408
  sessionExpiresAt(): number | null;
326
409
  signMessage(req: SignMessageRequest): Promise<string>;
327
410
  sendTransaction(req: SendTransactionRequest): Promise<string>;
@@ -568,8 +651,16 @@ interface WalletSelectorProps {
568
651
  codeRequired: true;
569
652
  }>;
570
653
  onEmailVerify?: (email: string, code: string) => Promise<void>;
571
- }
572
- declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onEmailStart, onEmailVerify, }: WalletSelectorProps): React.ReactPortal | null;
654
+ /** Button text for the developer's own auth system, e.g. "Continue with Acme Inc". Omitted entirely when not configured. */
655
+ customAuthLabel?: string;
656
+ onCustomAuthSignIn?: () => Promise<void>;
657
+ /** Returning user on this device — offer the passkey instead of sign-in. */
658
+ canUsePasskey?: boolean;
659
+ /** Address shown alongside the passkey button, so they know who they're resuming as. */
660
+ passkeyAddress?: string | null;
661
+ onPasskeySignIn?: () => Promise<unknown>;
662
+ }
663
+ declare function WalletSelector({ provider, open, onClose, onConnect, theme, networks, mode, socialAuthMethods, onGoogleSignIn, onEmailStart, onEmailVerify, customAuthLabel, onCustomAuthSignIn, canUsePasskey, passkeyAddress, onPasskeySignIn, }: WalletSelectorProps): React.ReactPortal | null;
573
664
 
574
665
  type Theme = "light" | "dark" | "auto";
575
666
  interface SocialAuthState {
@@ -586,10 +677,16 @@ interface SocialAuthState {
586
677
  * requires a fresh, valid JWT to open a TEE session, and JWTs are
587
678
  * deliberately never persisted (short-lived, security-sensitive), so
588
679
  * there is no legitimate "just unlock" path in this state. A consumer
589
- * should route this into a full reconnect (connectWithGoogle() /
590
- * connectWithEmail()), not unlock() — which will always throw here.
680
+ * should route this into a full reconnect — but see canUsePasskey: when
681
+ * that is true the reconnect is a single biometric prompt rather than a
682
+ * Google redirect or an email code.
591
683
  */
592
684
  needsReconnect: boolean;
685
+ /**
686
+ * True when this device holds a passkey-wrapped share for the remembered
687
+ * user, so signing back in costs one passkey prompt and nothing else.
688
+ */
689
+ canUsePasskey: boolean;
593
690
  }
594
691
  interface DecaneKitProps {
595
692
  config?: DecaneConfig;
@@ -610,6 +707,8 @@ interface SocialWalletModalProps {
610
707
  }>;
611
708
  onEmailVerify: (email: string, code: string) => Promise<void>;
612
709
  authMethods?: Array<"google" | "email">;
710
+ customAuthLabel?: string;
711
+ onCustomAuthSignIn?: () => Promise<void>;
613
712
  onRotateShares: (opts: {
614
713
  password: string;
615
714
  passwordHint?: string;
@@ -621,7 +720,7 @@ interface SocialWalletModalProps {
621
720
  rpcUrls?: Record<string, string>;
622
721
  chains?: string[];
623
722
  }
624
- declare function SocialWalletModal({ open, onClose, theme, socialAuth, onUnlock, onDisconnect, onGoogleSignIn, onEmailStart, onEmailVerify, authMethods, onRotateShares, onExportPortableBackup, rpcUrls, chains, }: SocialWalletModalProps): React.ReactPortal | null;
723
+ declare function SocialWalletModal({ open, onClose, theme, socialAuth, onUnlock, onDisconnect, onGoogleSignIn, onEmailStart, onEmailVerify, authMethods, customAuthLabel, onCustomAuthSignIn, onRotateShares, onExportPortableBackup, rpcUrls, chains, }: SocialWalletModalProps): React.ReactPortal | null;
625
724
 
626
725
  declare function useSocialAuth(): {
627
726
  isConnected: boolean;
@@ -635,6 +734,9 @@ declare function useSocialAuth(): {
635
734
  needsReconnect: boolean;
636
735
  googleLoading: boolean;
637
736
  emailLoading: boolean;
737
+ tokenLoading: boolean;
738
+ customLoading: boolean;
739
+ customAuthLabel: string | undefined;
638
740
  error: string | null;
639
741
  clearError: () => void;
640
742
  signInWithGoogle: () => Promise<void>;
@@ -642,6 +744,10 @@ declare function useSocialAuth(): {
642
744
  codeRequired: true;
643
745
  }>;
644
746
  confirmEmailCode: (email: string, code: string) => Promise<void>;
747
+ signInWithToken: (token: string, opts?: {
748
+ providerId?: string;
749
+ }) => Promise<void>;
750
+ signInWithCustomAuth: (() => Promise<void>) | undefined;
645
751
  unlock: () => Promise<void>;
646
752
  disconnect: () => Promise<void>;
647
753
  };