From af7c9f922787d26777ec704ff7c0817e5c48bedf Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 03:17:35 +0300 Subject: [PATCH 01/10] v3 auth state check and various improvements --- examples/react/src/components/Connected.tsx | 6 +- .../src/components/Connect/Connect.tsx | 149 +++++++++--- .../SequenceConnectInlineProvider.tsx | 11 +- .../SequenceConnectPreviewProvider.tsx | 11 +- .../SequenceConnectProvider.tsx | 11 +- .../connect/src/config/defaultConnectors.ts | 54 ++--- .../src/connectors/ecosystem/ecosystemV3.ts | 40 +++- .../wagmiConnectors/sequenceV3Connector.ts | 5 + .../connect/src/contexts/ConnectConfig.ts | 18 +- packages/connect/src/hooks/useAuthStatus.ts | 117 ++++++++++ .../src/hooks/useResolvedConnectConfig.ts | 54 ++++- packages/connect/src/hooks/useWallets.ts | 217 ++++++++++++++++-- packages/connect/src/index.ts | 8 +- packages/connect/src/styles.ts | 71 ------ packages/connect/src/utils/checkAuthStatus.ts | 112 +++++++++ .../connect/src/utils/walletConfiguration.ts | 31 ++- 16 files changed, 730 insertions(+), 185 deletions(-) create mode 100644 packages/connect/src/hooks/useAuthStatus.ts create mode 100644 packages/connect/src/utils/checkAuthStatus.ts diff --git a/examples/react/src/components/Connected.tsx b/examples/react/src/components/Connected.tsx index f88ab880e..662e8b988 100644 --- a/examples/react/src/components/Connected.tsx +++ b/examples/react/src/components/Connected.tsx @@ -37,6 +37,10 @@ export const Connected = () => { const { wallets, setActiveWallet, disconnectWallet } = useWallets() + useEffect(() => { + console.log('wallets changed', wallets, Date.now()) + }, [wallets]) + const isV3WalletConnectionActive = wallets.some(w => w.id === 'sequence-v3-wallet' && w.isActive) const sessionState = useSequenceSessionState() @@ -44,7 +48,7 @@ export const Connected = () => { const [hasPermission, setHasPermission] = React.useState(false) const [isCheckingPermission, setIsCheckingPermission] = React.useState(false) - console.log('sessionState', sessionState) + // console.log('sessionState', sessionState) const { data: implicitTestTxnData, diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index 8f16025cb..5bf98117a 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -86,6 +86,9 @@ interface ConnectProps extends SequenceConnectProviderProps { onClose: () => void isInline?: boolean enabledProviders?: WalletConfigurationProvider[] + isV3WalletSignedIn?: boolean | null + isAuthStatusLoading?: boolean + resolvedConfig?: ConnectConfig } export const Connect = (props: ConnectProps) => { @@ -96,7 +99,10 @@ export const Connect = (props: ConnectProps) => { const { analytics } = useAnalyticsContext() const { hideExternalConnectOptions, hideConnectedWallets, hideSocialConnectOptions } = useWalletSettings() - const { onClose, emailConflictInfo, config = {} as ConnectConfig, isInline = false } = props + const { onClose, emailConflictInfo, config: incomingConfig = {} as ConnectConfig, isInline = false } = props + const config = props.resolvedConfig ?? incomingConfig + const isV3WalletSignedIn = props.isV3WalletSignedIn ?? null + const isAuthStatusLoading = props.isAuthStatusLoading ?? false const { signIn = {} } = config const storage = useStorage() @@ -108,7 +114,7 @@ export const Connect = (props: ConnectProps) => { const [email, setEmail] = useState('') const [showEmailWaasPinInput, setShowEmailWaasPinInput] = useState(false) - const [showExtendedList, setShowExtendedList] = useState(null) + const [showExtendedList, setShowExtendedList] = useState(null) const { status, connectors, connect } = useConnect() const { signMessageAsync } = useSignMessage() @@ -271,6 +277,7 @@ export const Connect = (props: ConnectProps) => { const hasSequenceWalletConnection = hasV3Wallet || hasWaasWallet const hasSocialConnection = connections.some(c => (c.connector as ExtendedConnector)?._wallet?.type === 'social') const hasPrimarySequenceConnection = hasSequenceWalletConnection || hasSocialConnection + const hasAnyConnection = connections.length > 0 const extendedConnectors = filteredConnectors as ExtendedConnector[] @@ -444,12 +451,55 @@ export const Connect = (props: ConnectProps) => { } }) - const ecosystemConnectors = extendedConnectors.filter(c => c._wallet?.isEcosystemWallet) + const ecosystemConnector = extendedConnectors.find(c => c._wallet?.isEcosystemWallet) + + // Filter v3 connectors based on login status from status.js endpoint + // If logged in: only show ecosystem connector + // If not logged in: show regular v3 connectors (apple, passkey, google, etc.) but NOT ecosystem + // This logic only applies to v3 connectors (type === SEQUENCE_V3_CONNECTOR_TYPE) + const v3SocialConnectors = useMemo( + () => sequenceConnectors.filter(c => c._wallet?.type === 'social' && !c._wallet?.id.includes('email')), + [sequenceConnectors] + ) + const regularV3Connectors = useMemo(() => v3SocialConnectors.filter(c => !c._wallet?.isEcosystemWallet), [v3SocialConnectors]) + + // For v3 connectors: show ecosystem if logged in (from status.js check), otherwise show regular v3 connectors + // Only apply this filtering if we have v3 connectors and auth status has been checked + const visibleV3ConnectorIds = useMemo(() => { + // Wait for auth status to avoid swapping connector lists after the view is shown + if (isAuthStatusLoading || isV3WalletSignedIn === null) { + return new Set() + } + + // Only apply auth-based filtering if we have v3 connectors + if (sequenceConnectors.length === 0) { + // No v3 connectors, return empty set (will be filtered out anyway) + return new Set() + } + + if (isV3WalletSignedIn === true) { + // Logged in (status.js returned authState === 'signed-in'): only show ecosystem connector + return ecosystemConnector ? new Set([ecosystemConnector.uid]) : new Set() + } else if (isV3WalletSignedIn === false) { + // Not logged in (status.js returned authState !== 'signed-in'): show regular v3 connectors (not ecosystem) + return new Set(regularV3Connectors.map(c => c.uid)) + } + // Fallback: default to no v3 connectors if state is unknown + return new Set() + }, [isAuthStatusLoading, isV3WalletSignedIn, ecosystemConnector, regularV3Connectors, sequenceConnectors.length]) const socialAuthConnectors = extendedConnectors .filter(c => c._wallet?.type === 'social') .filter(c => !c._wallet?.id.includes('email')) - .filter(c => !c._wallet?.isEcosystemWallet) + .filter(c => { + // For v3 connectors, use the filtered list based on login status + const isV3Connector = c.type === SEQUENCE_V3_CONNECTOR_TYPE + if (isV3Connector) { + return visibleV3ConnectorIds.has(c.uid) + } + // For non-v3 connectors, exclude ecosystem wallets + return !c._wallet?.isEcosystemWallet + }) .sort((a, b) => { const isPasskey = (wallet?: ExtendedConnector['_wallet']) => wallet?.id === 'passkey-v3' if (isPasskey(a._wallet) && !isPasskey(b._wallet)) { @@ -466,7 +516,9 @@ export const Connect = (props: ConnectProps) => { return hasPrimarySequenceConnection ? true : !connector._wallet?.isEcosystemWallet }) - const shouldHideStandardSocial = ecosystemConnectors.length > 0 + // For v3: hide standard social only if logged in and ecosystem connector exists + // For non-v3: hide standard social if ecosystem connector exists + const shouldHideStandardSocial = isV3WalletSignedIn === true ? !!ecosystemConnector : false const emailConnector = !hideSocialConnectOptions && !shouldHideStandardSocial @@ -484,6 +536,47 @@ export const Connect = (props: ConnectProps) => { disableTooltip: options?.disableTooltip } + // Special handling for ecosystem connector - use config data for display + if (connector._wallet?.isEcosystemWallet) { + const signInConfig = config?.signIn + const projectName = signInConfig?.projectName || connector._wallet.name + const logoUrl = signInConfig?.logoUrl + + const renderEcosystemLogo = (logoProps: LogoProps) => ( + {projectName} + ) + + // Create a modified connector with config-based display properties + const displayConnector: ExtendedConnector = { + ...connector, + _wallet: { + ...connector._wallet, + name: projectName, + ctaText: signInConfig?.projectName ? `Connect with ${signInConfig.projectName}` : connector._wallet.ctaText, + // Override logos if logoUrl is available + ...(logoUrl && { + logoDark: renderEcosystemLogo, + logoLight: renderEcosystemLogo + }) + } + } + + return + } + switch (connector._wallet?.id) { case 'guest-waas': return @@ -651,15 +744,14 @@ export const Connect = (props: ConnectProps) => { } }, [emailConflictInfo]) - const showEcosystemConnectorSection = !hideSocialConnectOptions && ecosystemConnectors.length > 0 + // For v3: only show ecosystem connector section if logged in + // For non-v3: show ecosystem connector section if it exists + const showEcosystemConnectorSection = !hideSocialConnectOptions && !!ecosystemConnector && isV3WalletSignedIn === true const showSocialConnectorSection = !hideSocialConnectOptions && !shouldHideStandardSocial && socialAuthConnectors.length > 0 const showEmailInputSection = !hideSocialConnectOptions && !shouldHideStandardSocial && !!emailConnector - const showMoreEcosystemOptions = ecosystemConnectors.length > MAX_ITEM_PER_ROW const showMoreSocialOptions = socialAuthConnectors.length > MAX_ITEM_PER_ROW const showMoreWalletOptions = walletConnectors.length > MAX_ITEM_PER_ROW - const ecosystemConnectorsPerRow = - showMoreEcosystemOptions && !descriptiveSocials ? MAX_ITEM_PER_ROW - 1 : ecosystemConnectors.length const socialConnectorsPerRow = showMoreSocialOptions && !descriptiveSocials ? MAX_ITEM_PER_ROW - 1 : socialAuthConnectors.length const walletConnectorsPerRow = showMoreWalletOptions ? MAX_ITEM_PER_ROW - 1 : walletConnectors.length @@ -736,19 +828,9 @@ export const Connect = (props: ConnectProps) => { if (showExtendedList) { const SEARCHABLE_TRESHOLD = 8 - const connectorsForModal = - showExtendedList === 'social' - ? socialAuthConnectors - : showExtendedList === 'ecosystem' - ? ecosystemConnectors - : walletConnectors + const connectorsForModal = showExtendedList === 'social' ? socialAuthConnectors : walletConnectors const searchable = connectorsForModal.length > SEARCHABLE_TRESHOLD - const title = - showExtendedList === 'social' - ? 'Continue with a social account' - : showExtendedList === 'ecosystem' - ? 'Connect with an ecosystem wallet' - : 'Choose a wallet' + const title = showExtendedList === 'social' ? 'Continue with a social account' : 'Choose a wallet' return ( { /> ) : ( <> - {!hasPrimarySequenceConnection && } + {!hasAnyConnection && } {showWalletAuthOptionsFirst && !hideExternalConnectOptions && walletConnectors.length > 0 && ( @@ -876,25 +958,16 @@ export const Connect = (props: ConnectProps) => { {!hasPrimarySequenceConnection && (
<> - {showEcosystemConnectorSection && ( + {showEcosystemConnectorSection && ecosystemConnector && (
- {ecosystemConnectors.slice(0, ecosystemConnectorsPerRow).map(connector => { - return ( -
- {renderConnectorButton(connector, { - isDescriptive: descriptiveSocials, - disableTooltip: config?.signIn?.disableTooltipForDescriptiveSocials - })} -
- ) - })} - {showMoreEcosystemOptions && ( -
- setShowExtendedList('ecosystem')} /> -
- )} +
+ {renderConnectorButton(ecosystemConnector, { + isDescriptive: descriptiveSocials, + disableTooltip: config?.signIn?.disableTooltipForDescriptiveSocials + })} +
)} {!hideSocialConnectOptions && showSocialConnectorSection && ( diff --git a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx index 8208ed5b7..0eb8757aa 100644 --- a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx +++ b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx @@ -69,7 +69,13 @@ const resolveInlineBackground = (theme: Theme | undefined) => { */ export const SequenceConnectInlineProvider = (props: SequenceConnectInlineProviderProps) => { const { config: incomingConfig, children } = props - const { resolvedConfig: config, isLoading: isWalletConfigLoading, enabledProviders } = useResolvedConnectConfig(incomingConfig) + const { + resolvedConfig: config, + isLoading: isWalletConfigLoading, + enabledProviders, + isV3WalletSignedIn, + isAuthStatusLoading + } = useResolvedConnectConfig(incomingConfig) const { defaultTheme = 'dark', @@ -252,6 +258,9 @@ export const SequenceConnectInlineProvider = (props: SequenceConnectInlineProvid isInline {...props} config={config} + resolvedConfig={config} + isV3WalletSignedIn={isV3WalletSignedIn} + isAuthStatusLoading={isAuthStatusLoading} enabledProviders={enabledProviders} /> )} diff --git a/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx b/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx index 5fad94472..74812ec58 100644 --- a/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx +++ b/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx @@ -42,7 +42,13 @@ const resolveInlineBackground = (theme: Theme | undefined) => { */ export const SequenceConnectPreviewProvider = (props: SequenceConnectProviderProps) => { const { config: incomingConfig, children } = props - const { resolvedConfig: config, isLoading: isWalletConfigLoading, enabledProviders } = useResolvedConnectConfig(incomingConfig) + const { + resolvedConfig: config, + isLoading: isWalletConfigLoading, + enabledProviders, + isV3WalletSignedIn, + isAuthStatusLoading + } = useResolvedConnectConfig(incomingConfig) const { defaultTheme = 'dark', @@ -104,6 +110,9 @@ export const SequenceConnectPreviewProvider = (props: SequenceConnectProviderPro isInline {...props} config={config} + resolvedConfig={config} + isV3WalletSignedIn={isV3WalletSignedIn} + isAuthStatusLoading={isAuthStatusLoading} enabledProviders={enabledProviders} /> )} diff --git a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx index 9a4614c47..253a2a4b3 100644 --- a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx +++ b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx @@ -47,7 +47,13 @@ export type SequenceConnectProviderProps = { export const SequenceConnectProvider = (props: SequenceConnectProviderProps) => { const { config: incomingConfig, children } = props - const { resolvedConfig: config, isLoading: isWalletConfigLoading, enabledProviders } = useResolvedConnectConfig(incomingConfig) + const { + resolvedConfig: config, + isLoading: isWalletConfigLoading, + enabledProviders, + isV3WalletSignedIn, + isAuthStatusLoading + } = useResolvedConnectConfig(incomingConfig) const { defaultTheme = 'dark', signIn = {}, @@ -247,6 +253,9 @@ export const SequenceConnectProvider = (props: SequenceConnectProviderProps) => emailConflictInfo={emailConflictInfo} {...props} config={config} + resolvedConfig={config} + isV3WalletSignedIn={isV3WalletSignedIn} + isAuthStatusLoading={isAuthStatusLoading} enabledProviders={enabledProviders} /> diff --git a/packages/connect/src/config/defaultConnectors.ts b/packages/connect/src/config/defaultConnectors.ts index 2a6c8660e..ab4de8f46 100644 --- a/packages/connect/src/config/defaultConnectors.ts +++ b/packages/connect/src/config/defaultConnectors.ts @@ -3,7 +3,7 @@ import type { CreateConnectorFn } from 'wagmi' import { appleV3 } from '../connectors/apple/applev3.js' import { appleWaas } from '../connectors/apple/appleWaas.js' import { coinbaseWallet } from '../connectors/coinbaseWallet/coinbaseWallet.js' -import { ecosystemV3, type EcosystemWalletDefinition } from '../connectors/ecosystem/ecosystemV3.js' +import { ecosystemV3 } from '../connectors/ecosystem/ecosystemV3.js' import { emailV3 } from '../connectors/email/emailv3.js' import { emailWaas } from '../connectors/email/emailWaas.js' import { epicWaas } from '../connectors/epic/epicWaas.js' @@ -50,7 +50,6 @@ export interface DefaultV3ConnectorOptions extends CommonConnectorOptions { | { projectId: string } - ecosystemWallets?: EcosystemWalletDefinition[] additionalWallets?: Wallet[] /** * @deprecated, use connectors.walletConnect.projectId instead @@ -263,12 +262,10 @@ export const getDefaultWaasConnectors = (options: DefaultWaasConnectorOptions): export const getDefaultV3Connectors = (options: DefaultV3ConnectorOptions): CreateConnectorFn[] => { const { projectAccessKey, walletUrl, dappOrigin, defaultChainId = 1 } = options const appName = resolveAppName(options) - const hasEcosystemWallets = Array.isArray(options.ecosystemWallets) && options.ecosystemWallets.length > 0 - const shouldIncludeStandardSocialWallets = !hasEcosystemWallets const wallets: Wallet[] = [] - if (shouldIncludeStandardSocialWallets && options.email !== false) { + if (options.email !== false) { if (!walletUrl || !dappOrigin) { throw new Error('Email wallet requires walletUrl and dappOrigin to be set') } @@ -287,7 +284,7 @@ export const getDefaultV3Connectors = (options: DefaultV3ConnectorOptions): Crea ) } - if (shouldIncludeStandardSocialWallets && options.google !== false) { + if (options.google !== false) { if (!walletUrl || !dappOrigin) { throw new Error('Google wallet requires walletUrl and dappOrigin to be set') } @@ -306,7 +303,7 @@ export const getDefaultV3Connectors = (options: DefaultV3ConnectorOptions): Crea ) } - if (shouldIncludeStandardSocialWallets && options.apple !== false) { + if (options.apple !== false) { if (!walletUrl || !dappOrigin) { throw new Error('Apple wallet requires walletUrl and dappOrigin to be set') } @@ -325,7 +322,7 @@ export const getDefaultV3Connectors = (options: DefaultV3ConnectorOptions): Crea ) } - if (shouldIncludeStandardSocialWallets && options.passkey !== false) { + if (options.passkey !== false) { if (!walletUrl || !dappOrigin) { throw new Error('Passkey wallet requires walletUrl and dappOrigin to be set') } @@ -344,33 +341,20 @@ export const getDefaultV3Connectors = (options: DefaultV3ConnectorOptions): Crea ) } - if (options.ecosystemWallets && options.ecosystemWallets.length > 0) { - if (!walletUrl || !dappOrigin) { - throw new Error('Ecosystem wallets require walletUrl and dappOrigin to be set') - } - options.ecosystemWallets.forEach(ecosystemWallet => { - wallets.push( - ecosystemV3({ - projectAccessKey: projectAccessKey, - walletUrl: walletUrl, - defaultNetwork: defaultChainId, - dappOrigin: dappOrigin, - explicitSessionParams: options.explicitSessionParams, - enableImplicitSession: options.enableImplicitSession, - includeFeeOptionPermissions: options.includeFeeOptionPermissions, - nodesUrl: options.nodesUrl, - relayerUrl: options.relayerUrl, - id: ecosystemWallet.id, - name: ecosystemWallet.name, - logoDark: ecosystemWallet.logoDark, - logoLight: ecosystemWallet.logoLight, - monochromeLogoDark: ecosystemWallet.monochromeLogoDark, - monochromeLogoLight: ecosystemWallet.monochromeLogoLight, - ctaText: ecosystemWallet.ctaText, - loginType: ecosystemWallet.loginType - }) - ) - }) + if (walletUrl && dappOrigin) { + wallets.push( + ecosystemV3({ + projectAccessKey: projectAccessKey, + walletUrl: walletUrl, + defaultNetwork: defaultChainId, + dappOrigin: dappOrigin, + explicitSessionParams: options.explicitSessionParams, + enableImplicitSession: options.enableImplicitSession, + includeFeeOptionPermissions: options.includeFeeOptionPermissions, + nodesUrl: options.nodesUrl, + relayerUrl: options.relayerUrl + }) + ) } if (options.metaMask !== false) { diff --git a/packages/connect/src/connectors/ecosystem/ecosystemV3.ts b/packages/connect/src/connectors/ecosystem/ecosystemV3.ts index 25018c32d..64a83b337 100644 --- a/packages/connect/src/connectors/ecosystem/ecosystemV3.ts +++ b/packages/connect/src/connectors/ecosystem/ecosystemV3.ts @@ -1,12 +1,27 @@ -import type { Wallet, WalletProperties } from '../../types.js' +import type { FunctionComponent } from 'react' +import { createElement } from 'react' + +import type { LogoProps, Wallet, WalletProperties } from '../../types.js' import { sequenceV3Wallet, type BaseSequenceV3ConnectorOptions } from '../wagmiConnectors/sequenceV3Connector.js' -export type EcosystemWalletDefinition = Pick< - WalletProperties, - 'logoDark' | 'logoLight' | 'monochromeLogoDark' | 'monochromeLogoLight' +const DefaultLogo: FunctionComponent = ({ className, style }) => { + return createElement('div', { + className, + style: { + width: '100%', + height: '100%', + backgroundColor: 'currentColor', + borderRadius: '50%', + ...style + } + }) +} + +export type EcosystemWalletDefinition = Partial< + Pick > & { id?: string - name: string + name?: string ctaText?: string loginType?: BaseSequenceV3ConnectorOptions['loginType'] } @@ -17,19 +32,20 @@ export const ecosystemV3 = (options: EcosystemV3Options): Wallet => { const { id, name, ctaText, logoDark, logoLight, monochromeLogoDark, monochromeLogoLight, loginType, ...connectorOptions } = options - const walletId = id || createEcosystemWalletId(name) + const walletId = id || createEcosystemWalletId(name || '') + const walletName = name || 'Wallet' return { id: walletId, - logoDark, - logoLight, - monochromeLogoDark, - monochromeLogoLight, - name, + logoDark: logoDark || DefaultLogo, + logoLight: logoLight || DefaultLogo, + monochromeLogoDark: monochromeLogoDark || logoDark || DefaultLogo, + monochromeLogoLight: monochromeLogoLight || logoLight || DefaultLogo, + name: walletName, type: 'social', isSequenceBased: true, isEcosystemWallet: true, - ctaText: ctaText || `Connect with ${name}`, + ctaText: ctaText || `Connect with ${walletName}`, createConnector: () => { const connector = sequenceV3Wallet({ ...connectorOptions, diff --git a/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts b/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts index 29556a07f..6d0b0ce3f 100644 --- a/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts +++ b/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts @@ -39,6 +39,8 @@ export interface SequenceV3Connector extends Connector { setEmail: (email: string) => void auxData?: Record readonly client: DappClient + /** The login method reported by the v3 client after authentication (e.g., ecosystem-v3, google, passkey, etc.) */ + readonly loginMethod?: string readonly loginOptions?: { loginType?: SequenceV3LoginType loginStorageKey?: string @@ -121,6 +123,9 @@ export function sequenceV3Wallet(params: BaseSequenceV3ConnectorOptions) { get client() { return client }, + get loginMethod() { + return client.loginMethod ?? undefined + }, auxData: undefined as Record | undefined, loginOptions: { diff --git a/packages/connect/src/contexts/ConnectConfig.ts b/packages/connect/src/contexts/ConnectConfig.ts index 069ec0242..9666b5e5b 100644 --- a/packages/connect/src/contexts/ConnectConfig.ts +++ b/packages/connect/src/contexts/ConnectConfig.ts @@ -1,9 +1,19 @@ -'use client' +import { createContext, useContext } from 'react' import type { ConnectConfig } from '../types.js' -import { createGenericContext } from './genericContext.js' +const ConnectConfigContext = createContext(undefined) -const [useConnectConfigContext, ConnectConfigContextProvider] = createGenericContext() +export const ConnectConfigContextProvider = ConnectConfigContext.Provider -export { ConnectConfigContextProvider, useConnectConfigContext } +export const useConnectConfigContext = (): ConnectConfig => { + const ctx = useContext(ConnectConfigContext) + if (!ctx) { + throw new Error('useConnectConfigContext must be used within a Provider') + } + return ctx +} + +export const useOptionalConnectConfigContext = (): ConnectConfig | undefined => { + return useContext(ConnectConfigContext) +} diff --git a/packages/connect/src/hooks/useAuthStatus.ts b/packages/connect/src/hooks/useAuthStatus.ts new file mode 100644 index 000000000..33cf1b89b --- /dev/null +++ b/packages/connect/src/hooks/useAuthStatus.ts @@ -0,0 +1,117 @@ +'use client' + +import { useEffect, useState } from 'react' + +import { checkAuthStatus } from '../utils/checkAuthStatus.js' + +export interface UseAuthStatusOptions { + /** + * Whether to automatically check auth status on mount and when walletUrl changes + * @default true + */ + enabled?: boolean + /** + * Interval in milliseconds to refetch auth status + * If not provided, will only check once on mount and when walletUrl changes + */ + refetchInterval?: number +} + +export interface UseAuthStatusResult { + /** + * Whether the user is logged in according to the auth status endpoint + */ + isV3WalletSignedIn: boolean | null + /** + * Whether the auth status check is currently in progress + */ + isLoading: boolean + /** + * Error that occurred during the auth status check, if any + */ + error: Error | null + /** + * Manually refetch the auth status + */ + refetch: () => Promise +} + +/** + * Hook to check if the user is logged in by calling the auth status endpoint + * + * This hook calls `${walletUrl}/api/auth/status.js` to determine if the user + * is currently logged in. Useful for v3 connectors to check authentication state. + * + * @param walletUrl - The wallet URL to check auth status against + * @param options - Optional configuration for the hook + * @returns Object containing login status, loading state, error, and refetch function + * + * @example + * ```tsx + * const { isV3WalletSignedIn, isLoading } = useAuthStatus('https://wallet.sequence.app') + * + * if (isLoading) { + * return
Checking auth status...
+ * } + * + * if (isV3WalletSignedIn) { + * return
User is logged in
+ * } + * ``` + */ +export const useAuthStatus = (walletUrl: string | undefined | null, options: UseAuthStatusOptions = {}): UseAuthStatusResult => { + const { enabled = true, refetchInterval } = options + + const [isV3WalletSignedIn, setIsV3WalletSignedIn] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const checkStatus = async () => { + if (!walletUrl || !enabled) { + setIsV3WalletSignedIn(null) + setIsLoading(false) + setError(null) + return + } + + setIsLoading(true) + setError(null) + + try { + const signedIn = await checkAuthStatus(walletUrl) + setIsV3WalletSignedIn(signedIn) + } catch (err) { + const error = err instanceof Error ? err : new Error('Failed to check auth status') + setError(error) + setIsV3WalletSignedIn(false) + } finally { + setIsLoading(false) + } + } + + useEffect(() => { + if (!enabled || !walletUrl) { + return + } + + checkStatus() + + let intervalId: ReturnType | undefined + if (refetchInterval && refetchInterval > 0) { + intervalId = setInterval(checkStatus, refetchInterval) + } + + return () => { + if (intervalId) { + clearInterval(intervalId) + } + } + }, [walletUrl, enabled, refetchInterval]) + + return { + isV3WalletSignedIn, + isLoading, + error, + refetch: checkStatus + } +} diff --git a/packages/connect/src/hooks/useResolvedConnectConfig.ts b/packages/connect/src/hooks/useResolvedConnectConfig.ts index a1f6f7c2d..86f688f11 100644 --- a/packages/connect/src/hooks/useResolvedConnectConfig.ts +++ b/packages/connect/src/hooks/useResolvedConnectConfig.ts @@ -1,8 +1,11 @@ import { useEffect, useMemo, useState } from 'react' import type { ConnectConfig } from '../types.js' +import { checkAuthStatus } from '../utils/checkAuthStatus.js' import { + cacheProjectName, fetchWalletConfiguration, + getCachedProjectName, mapWalletConfigurationToOverrides, mergeConnectConfigWithWalletConfiguration, normalizeWalletUrl, @@ -13,6 +16,8 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { const [resolvedConfig, setResolvedConfig] = useState(config) const [isLoading, setIsLoading] = useState(false) const [enabledProviders, setEnabledProviders] = useState(undefined) + const [isV3WalletSignedIn, setIsV3WalletSignedIn] = useState(null) + const [isAuthStatusLoading, setIsAuthStatusLoading] = useState(false) const normalizedWalletUrl = useMemo(() => { return config.walletUrl ? normalizeWalletUrl(config.walletUrl) : '' @@ -21,21 +26,57 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { useEffect(() => { setResolvedConfig(config) setEnabledProviders(undefined) + setIsV3WalletSignedIn(null) + setIsAuthStatusLoading(false) }, [config]) useEffect(() => { let cancelled = false + const cachedProjectName = normalizedWalletUrl ? getCachedProjectName(normalizedWalletUrl) : undefined + const configWithCachedProjectName = + cachedProjectName && !config.signIn?.projectName + ? { + ...config, + signIn: { + ...config.signIn, + projectName: cachedProjectName + } + } + : config + if (!normalizedWalletUrl) { - setResolvedConfig(config) + setResolvedConfig(configWithCachedProjectName) setEnabledProviders(undefined) + setIsV3WalletSignedIn(null) setIsLoading(false) + setIsAuthStatusLoading(false) return () => { cancelled = true } } + setResolvedConfig(configWithCachedProjectName) setIsLoading(true) + setIsAuthStatusLoading(true) + + checkAuthStatus(normalizedWalletUrl) + .then(signedIn => { + if (!cancelled) { + setIsV3WalletSignedIn(signedIn) + } + }) + .catch(error => { + if (!cancelled) { + console.warn('Failed to check auth status', error) + setIsV3WalletSignedIn(false) + } + }) + .finally(() => { + if (!cancelled) { + setIsAuthStatusLoading(false) + } + }) fetchWalletConfiguration(normalizedWalletUrl) .then(remoteConfig => { @@ -45,7 +86,10 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { const overrides = mapWalletConfigurationToOverrides(remoteConfig) setEnabledProviders(overrides.enabledProviders) - setResolvedConfig(mergeConnectConfigWithWalletConfiguration(config, overrides)) + setResolvedConfig(mergeConnectConfigWithWalletConfiguration(configWithCachedProjectName, overrides)) + if (overrides.signIn?.projectName) { + cacheProjectName(normalizedWalletUrl, overrides.signIn.projectName) + } }) .catch(error => { if (!cancelled) { @@ -69,8 +113,10 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { () => ({ resolvedConfig, isLoading, - enabledProviders + enabledProviders, + isV3WalletSignedIn, + isAuthStatusLoading }), - [resolvedConfig, isLoading, enabledProviders] + [resolvedConfig, isLoading, enabledProviders, isV3WalletSignedIn, isAuthStatusLoading] ) } diff --git a/packages/connect/src/hooks/useWallets.ts b/packages/connect/src/hooks/useWallets.ts index dcd30861e..716601922 100644 --- a/packages/connect/src/hooks/useWallets.ts +++ b/packages/connect/src/hooks/useWallets.ts @@ -2,10 +2,12 @@ import { SequenceAPIClient, type GetLinkedWalletsRequest, type LinkedWallet } from '@0xsequence/api' import { useAPIClient } from '@0xsequence/hooks' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useAccount, useConnect, useConnections, useDisconnect, type Connector, type UseConnectionsReturnType } from 'wagmi' +import { useOptionalConnectConfigContext } from '../contexts/ConnectConfig.js' import type { ExtendedConnector } from '../types.js' +import { getCachedProjectName, normalizeWalletUrl } from '../utils/walletConfiguration.js' import { useWaasGetLinkedWalletsSignature } from './useWaasGetLinkedWalletsSignature.js' @@ -228,10 +230,14 @@ export interface UseWalletsReturnType { */ export const useWallets = (): UseWalletsReturnType => { - const { address } = useAccount() + const { address, status: accountStatus } = useAccount() const connections = useConnections() const { connectAsync } = useConnect() const { disconnectAsync } = useDisconnect() + const connectConfig = useOptionalConnectConfigContext() + const normalizedWalletUrl = connectConfig?.walletUrl ? normalizeWalletUrl(connectConfig.walletUrl) : '' + const sequenceProjectName = + connectConfig?.signIn?.projectName || (normalizedWalletUrl ? getCachedProjectName(normalizedWalletUrl) : undefined) const waasConnection = connections.find(c => (c.connector as ExtendedConnector)?.type === 'sequence-waas') @@ -261,31 +267,122 @@ export const useWallets = (): UseWalletsReturnType => { } ) - const wallets: ConnectedWallet[] = connections.map((connection: UseConnectionsReturnType[number]) => ({ - id: connection.connector.id, - name: getConnectorName(connection.connector), - address: connection.accounts[0], - isActive: connection.accounts[0] === address, - isEmbedded: connection.connector.id.includes('waas'), - signInMethod: (connection.connector._wallet as any)?.id - })) + const ecosystemProjectName = connectConfig?.signIn?.projectName + const [loginMethodVersion, setLoginMethodVersion] = useState(0) + + // Keep track of the last non-empty connections list so we can present stable data while wagmi reconnects. + const lastKnownConnectionsRef = useRef([]) + useEffect(() => { + if (connections.length > 0) { + lastKnownConnectionsRef.current = connections + } + }, [connections]) + + const baseConnections: UseConnectionsReturnType = useMemo(() => { + const isReconnecting = accountStatus === 'connecting' || accountStatus === 'reconnecting' + if (connections.length === 0 && isReconnecting && lastKnownConnectionsRef.current.length > 0) { + return lastKnownConnectionsRef.current + } + return connections + }, [connections, accountStatus]) + + useEffect(() => { + const unsubscribers: Array<() => void> = [] + baseConnections.forEach(connection => { + if (connection.connector.type === 'sequence-v3-wallet') { + const client = (connection.connector as any)?.client + if (client?.on) { + const handler = () => setLoginMethodVersion(v => v + 1) + const unsubscribe = client.on('sessionsUpdated', handler) + if (unsubscribe) { + unsubscribers.push(unsubscribe) + } + } + } + }) + return () => { + unsubscribers.forEach(unsub => { + try { + unsub() + } catch { + // ignore + } + }) + } + }, [baseConnections]) + + const walletsFromConnections = useMemo(() => { + let hasPendingV3LoginMethod = false + + const list: ConnectedWallet[] = baseConnections.map((connection: UseConnectionsReturnType[number]) => { + const signInMethod = getSignInMethod(connection) + if (connection.connector.type === 'sequence-v3-wallet' && signInMethod === 'unknown') { + hasPendingV3LoginMethod = true + } + + return { + id: connection.connector.id, + name: getConnectorName(connection.connector, sequenceProjectName, ecosystemProjectName), + address: connection.accounts[0], + isActive: connection.accounts[0] === address, + isEmbedded: connection.connector.id.includes('waas'), + signInMethod + } + }) + + const sorted = list.sort((a, b) => { + if (a.id !== b.id) { + return a.id.localeCompare(b.id) + } + return a.address.toLowerCase().localeCompare(b.address.toLowerCase()) + }) + + return { list: sorted, hasPendingV3LoginMethod } + }, [baseConnections, sequenceProjectName, ecosystemProjectName, address, loginMethodVersion]) + + // Preserve the last non-empty wallet list while wagmi is reconnecting to avoid UI flicker on refresh. + const [stableWallets, setStableWallets] = useState( + walletsFromConnections.hasPendingV3LoginMethod || walletsFromConnections.list.length === 0 ? [] : walletsFromConnections.list + ) + useEffect(() => { + if (walletsFromConnections.hasPendingV3LoginMethod) { + return + } + const nextList = walletsFromConnections.list + if (nextList.length === 0) { + return + } + + const timer = setTimeout(() => { + setStableWallets(prev => (areWalletListsEqual(prev, nextList) ? prev : nextList)) + }, 120) + + return () => { + clearTimeout(timer) + } + }, [walletsFromConnections]) const setActiveWallet = async (walletAddress: string) => { const connection = connections.find( (c: UseConnectionsReturnType[number]) => c.accounts[0].toLowerCase() === walletAddress.toLowerCase() ) - if (!connection) { + const connectionFromCache = lastKnownConnectionsRef.current.find( + (c: UseConnectionsReturnType[number]) => c.accounts[0].toLowerCase() === walletAddress.toLowerCase() + ) + const connectionToUse = connection || connectionFromCache + + if (!connectionToUse) { console.error('No connection found for wallet address:', walletAddress) return } // Do not try to change if it's already active - if (wallets.find(w => w.address.toLowerCase() === walletAddress.toLowerCase())?.isActive) { + if (stableWallets.find((w: ConnectedWallet) => w.address.toLowerCase() === walletAddress.toLowerCase())?.isActive) { return } try { - await connectAsync({ connector: connection.connector }) + await connectAsync({ connector: connectionToUse.connector }) } catch (error) { console.error('Failed to set active wallet:', error) } @@ -312,7 +409,7 @@ export const useWallets = (): UseWalletsReturnType => { } return { - wallets, + wallets: stableWallets, linkedWallets, setActiveWallet, disconnectWallet, @@ -320,9 +417,99 @@ export const useWallets = (): UseWalletsReturnType => { } } -const getConnectorName = (connector: Connector) => { +const getConnectorName = (connector: Connector, sequenceProjectName?: string, ecosystemProjectName?: string) => { const connectorName = connector.name const connectorWalletName = (connector._wallet as any)?.name + if (sequenceProjectName && connector.type === 'sequence-v3-wallet') { + return sequenceProjectName + } + + if ((connector as any)._wallet?.isEcosystemWallet && ecosystemProjectName) { + return ecosystemProjectName + } + return connectorWalletName ?? connectorName } + +const getSignInMethod = (connection: UseConnectionsReturnType[number]) => { + const walletId = (connection.connector._wallet as any)?.id as string | undefined + const connectorId = connection.connector.id + const lowerId = connectorId.toLowerCase() + const address = connection.accounts[0] + + if (connection.connector.type === 'sequence-v3-wallet') { + const fromV3Client = + ((connection.connector as any)?.loginMethod as string | undefined) || + ((connection.connector as any)?.client?.loginMethod as string | undefined) + + if (fromV3Client) { + setCachedLoginMethod(connectorId, address, fromV3Client) + return fromV3Client + } + + const cached = getCachedLoginMethod(connectorId, address) + return cached ?? 'unknown' + } + + return ( + walletId || + (lowerId.includes('metamask') ? 'metamask-wallet' : lowerId.includes('coinbase') ? 'coinbase-wallet' : connectorId) + ) +} + +const areWalletListsEqual = (a: ConnectedWallet[], b: ConnectedWallet[]) => { + if (a === b) { + return true + } + if (a.length !== b.length) { + return false + } + for (let i = 0; i < a.length; i++) { + if (a[i].address.toLowerCase() !== b[i].address.toLowerCase()) { + return false + } + if (a[i].id !== b[i].id) { + return false + } + if (a[i].isActive !== b[i].isActive) { + return false + } + if (a[i].name !== b[i].name) { + return false + } + if (a[i].signInMethod !== b[i].signInMethod) { + return false + } + if (a[i].isEmbedded !== b[i].isEmbedded) { + return false + } + } + return true +} + +const LOGIN_METHOD_CACHE_PREFIX = '@0xsequence.loginMethod' + +const getCachedLoginMethod = (connectorId: string, address: string) => { + try { + if (typeof window === 'undefined') { + return undefined + } + const key = `${LOGIN_METHOD_CACHE_PREFIX}:${connectorId}:${address.toLowerCase()}` + return window.localStorage.getItem(key) || undefined + } catch { + return undefined + } +} + +const setCachedLoginMethod = (connectorId: string, address: string, value: string) => { + try { + if (typeof window === 'undefined') { + return + } + const key = `${LOGIN_METHOD_CACHE_PREFIX}:${connectorId}:${address.toLowerCase()}` + window.localStorage.setItem(key, value) + } catch { + // ignore + } +} diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 0fdcbd8e2..496b986a6 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -69,7 +69,11 @@ export { isTxRejected, sendTransactions, waitForTransactionReceipt } from './uti export { createContractPermission, createContractPermissions, createExplicitSessionConfig } from './utils/session/index.js' // Contexts -export { ConnectConfigContextProvider, useConnectConfigContext } from './contexts/ConnectConfig.js' +export { + ConnectConfigContextProvider, + useConnectConfigContext, + useOptionalConnectConfigContext +} from './contexts/ConnectConfig.js' export { AnalyticsContextProvider, useAnalyticsContext } from './contexts/Analytics.js' export { ConnectModalContextProvider, useConnectModalContext } from './contexts/ConnectModal.js' export { ThemeContextProvider, useThemeContext } from './contexts/Theme.js' @@ -118,6 +122,8 @@ export { useWaasSignInEmail } from './hooks/useWaasSignInEmail.js' export { useFeeOptions } from './hooks/useFeeOptions.js' export { useExplicitSessions } from './hooks/useExplicitSessions.js' export { useSequenceSessionState } from './hooks/useSequenceSessionState.js' +export { useAuthStatus } from './hooks/useAuthStatus.js' +export type { UseAuthStatusOptions, UseAuthStatusResult } from './hooks/useAuthStatus.js' // Components export { NetworkBadge } from './components/NetworkBadge/index.js' diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index e5fd63bd3..73a764e4c 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -318,9 +318,6 @@ export const styles = String.raw` .my-4 { margin-block: calc(var(--spacing) * 4); } - .mt-0 { - margin-top: calc(var(--spacing) * 0); - } .mt-1 { margin-top: calc(var(--spacing) * 1); } @@ -345,18 +342,9 @@ export const styles = String.raw` .-mr-\[1px\] { margin-right: calc(1px * -1); } - .mr-3 { - margin-right: calc(var(--spacing) * 3); - } .mr-4 { margin-right: calc(var(--spacing) * 4); } - .mr-10 { - margin-right: calc(var(--spacing) * 10); - } - .mr-12 { - margin-right: calc(var(--spacing) * 12); - } .-mb-\[1px\] { margin-bottom: calc(1px * -1); } @@ -366,9 +354,6 @@ export const styles = String.raw` .mb-2 { margin-bottom: calc(var(--spacing) * 2); } - .mb-3 { - margin-bottom: calc(var(--spacing) * 3); - } .mb-4 { margin-bottom: calc(var(--spacing) * 4); } @@ -456,15 +441,6 @@ export const styles = String.raw` .h-16 { height: calc(var(--spacing) * 16); } - .h-24 { - height: calc(var(--spacing) * 24); - } - .h-200 { - height: calc(var(--spacing) * 200); - } - .h-400 { - height: calc(var(--spacing) * 400); - } .h-\[1px\] { height: 1px; } @@ -612,9 +588,6 @@ export const styles = String.raw` .w-\[148px\] { width: 148px; } - .w-auto { - width: auto; - } .w-fit { width: fit-content; } @@ -633,9 +606,6 @@ export const styles = String.raw` .max-w-full { max-width: 100%; } - .max-w-md { - max-width: var(--container-md); - } .min-w-0 { min-width: calc(var(--spacing) * 0); } @@ -817,9 +787,6 @@ export const styles = String.raw` text-overflow: ellipsis; white-space: nowrap; } - .overflow-auto { - overflow: auto; - } .overflow-hidden { overflow: hidden; } @@ -875,22 +842,10 @@ export const styles = String.raw` border-top-left-radius: var(--radius-2xl); border-top-right-radius: var(--radius-2xl); } - .rounded-t-none { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - .rounded-t-xl { - border-top-left-radius: var(--radius-xl); - border-top-right-radius: var(--radius-xl); - } .rounded-b-none { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } - .rounded-b-xl { - border-bottom-right-radius: var(--radius-xl); - border-bottom-left-radius: var(--radius-xl); - } .border { border-style: var(--tw-border-style); border-width: 1px; @@ -935,12 +890,6 @@ export const styles = String.raw` .border-border-normal { border-color: var(--seq-color-border-normal); } - .border-primary { - border-color: var(--seq-color-primary); - } - .border-red-500 { - border-color: var(--color-red-500); - } .border-transparent { border-color: transparent; } @@ -1007,10 +956,6 @@ export const styles = String.raw` .bg-white { background-color: var(--color-white); } - .bg-gradient-to-r { - --tw-gradient-position: to right in oklab; - background-image: linear-gradient(var(--tw-gradient-stops)); - } .bg-gradient-primary { background-image: var(--seq-color-gradient-primary); } @@ -1083,9 +1028,6 @@ export const styles = String.raw` .px-6 { padding-inline: calc(var(--spacing) * 6); } - .px-8 { - padding-inline: calc(var(--spacing) * 8); - } .py-1 { padding-block: calc(var(--spacing) * 1); } @@ -1098,9 +1040,6 @@ export const styles = String.raw` .py-4 { padding-block: calc(var(--spacing) * 4); } - .py-5 { - padding-block: calc(var(--spacing) * 5); - } .py-6 { padding-block: calc(var(--spacing) * 6); } @@ -1140,9 +1079,6 @@ export const styles = String.raw` .pr-4 { padding-right: calc(var(--spacing) * 4); } - .pr-16 { - padding-right: calc(var(--spacing) * 16); - } .pb-2 { padding-bottom: calc(var(--spacing) * 2); } @@ -1236,10 +1172,6 @@ export const styles = String.raw` --tw-leading: calc(var(--spacing) * 0); line-height: calc(var(--spacing) * 0); } - .leading-1 { - --tw-leading: calc(var(--spacing) * 1); - line-height: calc(var(--spacing) * 1); - } .leading-4 { --tw-leading: calc(var(--spacing) * 4); line-height: calc(var(--spacing) * 4); @@ -1319,9 +1251,6 @@ export const styles = String.raw` .text-black { color: var(--color-black); } - .text-gray-500 { - color: var(--color-gray-500); - } .text-info { color: var(--seq-color-info); } diff --git a/packages/connect/src/utils/checkAuthStatus.ts b/packages/connect/src/utils/checkAuthStatus.ts new file mode 100644 index 000000000..f0defe3b6 --- /dev/null +++ b/packages/connect/src/utils/checkAuthStatus.ts @@ -0,0 +1,112 @@ +import { normalizeWalletUrl } from './walletConfiguration.js' + +type AuthStatusData = { + authState?: 'signed-in' | 'signed-out' + [key: string]: unknown +} + +/** + * Checks if the user is logged in by calling the auth status endpoint + * The endpoint uses JSONP pattern - it returns JavaScript that calls a callback function + * @param walletUrl - The wallet URL to check auth status against + * @returns Promise - Returns true if user is logged in, false otherwise + */ +export const checkAuthStatus = async (walletUrl: string): Promise => { + const normalizedUrl = normalizeWalletUrl(walletUrl) + + if (!normalizedUrl) { + return false + } + + return new Promise(resolve => { + let resolved = false + const callbackName = `sequenceAuthStatusCallback_${Date.now()}_${Math.random().toString(36).slice(2)}` + const originalCallback = (window as any)[callbackName] + + // Create script tag to load the JSONP endpoint + const script = document.createElement('script') + script.src = `${normalizedUrl}/api/auth/status.js?callback=${callbackName}&_=${Date.now()}` + script.async = true + script.defer = true + + // Create a callback that will receive the auth status data + const authCallback = (data: AuthStatusData) => { + if (resolved) { + return + } + resolved = true + + // Clean up script and callback + script.remove() + if (originalCallback) { + ;(window as any)[callbackName] = originalCallback + } else { + delete (window as any)[callbackName] + } + if (timeoutId) { + clearTimeout(timeoutId) + } + + // Check if user is signed in + const isV3WalletSignedIn = data.authState === 'signed-in' + resolve(isV3WalletSignedIn) + } + + // Set up the global callback function + ;(window as any)[callbackName] = authCallback + + // Handle script load - if callback wasn't called, resolve as false after a short delay + script.addEventListener('load', () => { + setTimeout(() => { + if (!resolved) { + resolved = true + script.remove() + if (originalCallback) { + ;(window as any)[callbackName] = originalCallback + } else { + delete (window as any)[callbackName] + } + if (timeoutId) { + clearTimeout(timeoutId) + } + resolve(false) + } + }, 0) + }) + + // Handle script error + script.addEventListener('error', () => { + if (!resolved) { + resolved = true + script.remove() + if (originalCallback) { + ;(window as any)[callbackName] = originalCallback + } else { + delete (window as any)[callbackName] + } + if (timeoutId) { + clearTimeout(timeoutId) + } + console.warn('Failed to load auth status script') + resolve(false) + } + }) + + // Timeout fallback in case callback is never called + const timeoutId = setTimeout(() => { + if (!resolved) { + resolved = true + script.remove() + if (originalCallback) { + ;(window as any)[callbackName] = originalCallback + } else { + delete (window as any)[callbackName] + } + resolve(false) + } + }, 5000) // 5 second timeout + + // Append script to document head to trigger load + document.head.appendChild(script) + }) +} diff --git a/packages/connect/src/utils/walletConfiguration.ts b/packages/connect/src/utils/walletConfiguration.ts index 845f37bab..d08bfcc9e 100644 --- a/packages/connect/src/utils/walletConfiguration.ts +++ b/packages/connect/src/utils/walletConfiguration.ts @@ -56,6 +56,7 @@ const CACHE_TTL_MS = 1000 * 60 * 60 * 4 const allowedProviders: WalletConfigurationProvider[] = ['EMAIL', 'GOOGLE', 'APPLE', 'PASSKEY'] const walletConfigurationPromises = new Map>() const walletConfigurationCache = new Map() +const PROJECT_NAME_CACHE_KEY_PREFIX = '@0xsequence.wallet-config.projectName:' export const normalizeWalletUrl = (walletUrl: string): string => { const trimmed = walletUrl.trim() @@ -126,7 +127,8 @@ const pickLogoUrl = (config: WalletConfigurationResponse): string | undefined => ) as string[] const getLogoFromTheme = (theme?: WalletConfigurationTheme) => { - return theme?.fileAuthLogo?.src || theme?.fileHeaderLogo?.src + // Prefer header logo for more compact aspect ratios; fall back to auth logo + return theme?.fileHeaderLogo?.src || theme?.fileAuthLogo?.src } for (const themeKey of themeOrder) { @@ -151,6 +153,33 @@ const normalizeEnabledProviders = (providers?: string[]): WalletConfigurationPro return normalized } +const buildProjectNameCacheKey = (normalizedUrl: string) => `${PROJECT_NAME_CACHE_KEY_PREFIX}${normalizedUrl}` + +export const getCachedProjectName = (walletUrl: string): string | undefined => { + const normalizedUrl = normalizeWalletUrl(walletUrl) + if (!normalizedUrl) { + return undefined + } + try { + const cached = localStorage.getItem(buildProjectNameCacheKey(normalizedUrl)) + return cached || undefined + } catch { + return undefined + } +} + +export const cacheProjectName = (walletUrl: string, projectName: string) => { + const normalizedUrl = normalizeWalletUrl(walletUrl) + if (!normalizedUrl || !projectName) { + return + } + try { + localStorage.setItem(buildProjectNameCacheKey(normalizedUrl), projectName) + } catch { + // ignore storage failures + } +} + export const mapWalletConfigurationToOverrides = (config: WalletConfigurationResponse): WalletConfigurationOverrides => { const projectName = typeof config.name === 'string' && config.name.trim() ? config.name : undefined const logoUrl = pickLogoUrl(config) From 7b88cb18fa4ca41039267f2c648208e6d5d6c8e2 Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 14:10:26 +0300 Subject: [PATCH 02/10] Fix useWallet state issue --- packages/connect/src/hooks/useWallets.ts | 6 +++++- packages/connect/src/styles.ts | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/connect/src/hooks/useWallets.ts b/packages/connect/src/hooks/useWallets.ts index 716601922..1fdae6650 100644 --- a/packages/connect/src/hooks/useWallets.ts +++ b/packages/connect/src/hooks/useWallets.ts @@ -350,6 +350,10 @@ export const useWallets = (): UseWalletsReturnType => { } const nextList = walletsFromConnections.list if (nextList.length === 0) { + // When there are no connections and wagmi isn't reconnecting, clear the stable list. + if (accountStatus !== 'connecting' && accountStatus !== 'reconnecting') { + setStableWallets([]) + } return } @@ -360,7 +364,7 @@ export const useWallets = (): UseWalletsReturnType => { return () => { clearTimeout(timer) } - }, [walletsFromConnections]) + }, [walletsFromConnections, accountStatus]) const setActiveWallet = async (walletAddress: string) => { const connection = connections.find( diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index 73a764e4c..95cc61b20 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -2483,6 +2483,4 @@ export const styles = String.raw` --tw-gradient-to-position: 100%; } } -} - -` +}` From 1c3a8e90923ca440e6a503d813e3ef2068fedb84 Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 15:59:11 +0300 Subject: [PATCH 03/10] Update --- examples/react/src/components/Homepage.tsx | 3 +++ examples/react/src/config.ts | 3 +++ examples/react/src/constants/index.ts | 1 + examples/react/src/constants/logo.ts | 2 ++ .../src/components/Connect/Connect.tsx | 22 ++++++++------- .../SequenceConnectInlineProvider.tsx | 6 +++-- .../SequenceConnectPreviewProvider.tsx | 6 +++-- .../SequenceConnectProvider.tsx | 6 +++-- .../src/hooks/useResolvedConnectConfig.ts | 27 +++++++++---------- packages/connect/src/hooks/useWallets.ts | 18 ++++++++----- packages/connect/src/styles.ts | 4 ++- .../connect/src/utils/walletConfiguration.ts | 21 +-------------- 12 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 examples/react/src/constants/logo.ts diff --git a/examples/react/src/components/Homepage.tsx b/examples/react/src/components/Homepage.tsx index 13b617f25..043d53032 100644 --- a/examples/react/src/components/Homepage.tsx +++ b/examples/react/src/components/Homepage.tsx @@ -29,6 +29,9 @@ export const Homepage = ({ walletUrl, onWalletUrlChange }: HomepageProps) => { const normalizedInput = sanitizeWalletUrl(walletUrlInput) const isDirty = normalizedInput !== walletUrl + console.log('wallets.lenght', wallets.length) + console.log('wallets', wallets) + return (
{wallets.length === 0 ? ( diff --git a/examples/react/src/config.ts b/examples/react/src/config.ts index bf9a8332b..86cd74fb9 100644 --- a/examples/react/src/config.ts +++ b/examples/react/src/config.ts @@ -4,6 +4,7 @@ import { Environment } from '@imtbl/config' import { passport } from '@imtbl/sdk' import { parseEther } from 'viem' +import { webSdkDemoLogoDataUrl } from './constants/index.js' import { getEmitterContractAddress } from './constants/permissions' // const searchParams = new URLSearchParams(location.search) @@ -37,6 +38,8 @@ export const connectConfig: ConnectConfig = { projectAccessKey, walletUrl: DEFAULT_WALLET_URL, signIn: { + projectName: 'Web SDK Demo', + logoUrl: webSdkDemoLogoDataUrl, descriptiveSocials: true, disableTooltipForDescriptiveSocials: true }, diff --git a/examples/react/src/constants/index.ts b/examples/react/src/constants/index.ts index e649c697d..3163a27bd 100644 --- a/examples/react/src/constants/index.ts +++ b/examples/react/src/constants/index.ts @@ -1 +1,2 @@ export const messageToSign = 'Two roads diverged in a yellow wood' +export { webSdkDemoLogoDataUrl } from './logo.js' diff --git a/examples/react/src/constants/logo.ts b/examples/react/src/constants/logo.ts new file mode 100644 index 000000000..f1d447c3a --- /dev/null +++ b/examples/react/src/constants/logo.ts @@ -0,0 +1,2 @@ +export const webSdkDemoLogoDataUrl = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTgwIDQwQzgwIDYyLjA5MTQgNjIuMDkxNCA4MCA0MCA4MEMxNy45MDg2IDgwIDAgNjIuMDkxNCAwIDQwQzAgMTcuOTA4NiAxNy45MDg2IDAgNDAgMEM2Mi4wOTE0IDAgODAgMTcuOTA4NiA4MCA0MFoiIGZpbGw9IiMxRDI2MzMiPjwvcGF0aD4KPHBhdGggZD0iTTY5LjcxMzYgNTAuMjEzM0M3MS4wNjQ0IDUwLjIxMzMgNzIuMDI1MiA1MS41MjYxIDcxLjUxNzIgNTIuNzc3N0M2Ni40NjY2IDY1LjIyMzMgNTQuMjU4MiA3NCAzOS45OTk5IDc0QzI1Ljc0MTcgNzQgMTMuNTMzMyA2NS4yMjMzIDguNDgyNjMgNTIuNzc3N0M3Ljk3NDY2IDUxLjUyNjEgOC45MzU0OCA1MC4yMTMzIDEwLjI4NjMgNTAuMjEzM0g2OS43MTM2WiIgZmlsbD0iI0VERUJERSI+PC9wYXRoPgo8cGF0aCBkPSJNNzEuNTE3MiAyNy4yMjIyQzcyLjAyNTIgMjguNDczOSA3MS4wNjQ0IDI5Ljc4NjcgNjkuNzEzNiAyOS43ODY3SDEwLjI4NjNDOC45MzU0OSAyOS43ODY3IDcuOTc0NjggMjguNDczOSA4LjQ4MjY0IDI3LjIyMjJDMTMuNTMzMyAxNC43NzY3IDI1Ljc0MTcgNiAzOS45OTk5IDZDNTQuMjU4MiA2IDY2LjQ2NjUgMTQuNzc2NyA3MS41MTcyIDI3LjIyMjJaIiBmaWxsPSIjRURFQkRFIj48L3BhdGg+CjxwYXRoIGQ9Ik02OS44MzcgMzcuMTQ5OUM3Mi4wODQyIDM3LjE0OTkgNzMuMTY4NiAzOC43MTc3IDczLjE2ODYgNDAuNTMzN1Y0My45NDM3SDcwLjgxNjlWNDAuOTM4OEM3MC44MTY5IDQwLjExNTcgNzAuNTE2NCAzOS40MzYzIDY5LjY1NDEgMzkuNDM2M0M2OC43OTE4IDM5LjQzNjMgNjguNTA0NCA0MC4xMTU3IDY4LjUwNDQgNDAuOTM4OFY0My45NDM3SDY2LjE1MjdWNDAuOTM4OEM2Ni4xNTI3IDQwLjExNTcgNjUuODY1MyAzOS40MzYzIDY1LjAwMyAzOS40MzYzQzY0LjE0MDcgMzkuNDM2MyA2My44NDAyIDQwLjExNTcgNjMuODQwMiA0MC45Mzg4VjQzLjk0MzdINjEuNDg4NVY0MC41MzM3QzYxLjQ4ODUgMzguNzE3NyA2Mi41NzI5IDM3LjE0OTkgNjQuODIwMSAzNy4xNDk5QzY2LjA0ODIgMzcuMTQ5OSA2Ni45MzY2IDM3LjYzMzMgNjcuMzQxNiAzOC40MzAzQzY3Ljc3MjggMzcuNjMzMyA2OC43MTM1IDM3LjE0OTkgNjkuODM3IDM3LjE0OTlaIiBmaWxsPSIjRURFQkRFIj48L3BhdGg+CjxwYXRoIGQ9Ik01Ny4yNjI3IDQxLjg1MzNDNTguMDIwNSA0MS44NTMzIDU4LjM0NzEgNDEuMTYwOSA1OC4zNDcxIDQwLjMzNzhWMzcuNDExMkg2MC42OTg4VjQwLjYyNTJDNjAuNjk4OCA0Mi41NDU3IDU5LjU3NTIgNDQuMTM5NyA1Ny4yNjI3IDQ0LjEzOTdDNTQuOTUwMiA0NC4xMzk3IDUzLjgyNjcgNDIuNTQ1NyA1My44MjY3IDQwLjYyNTJWMzcuNDExMkg1Ni4xNzgzVjQwLjMzNzhDNTYuMTc4MyA0MS4xNjA5IDU2LjUwNSA0MS44NTMzIDU3LjI2MjcgNDEuODUzM1oiIGZpbGw9IiNFREVCREUiPjwvcGF0aD4KPHBhdGggZD0iTTUwLjM2ODcgNDQuMTUyN0M0OC44OTI0IDQ0LjE1MjcgNDcuOTM4NiA0My42NDMyIDQ3LjM3NjkgNDIuNjUwM0w0OS4wNzUzIDQxLjY5NjVDNDkuMzEwNSA0Mi4xMTQ2IDQ5LjY2MzIgNDIuMzIzNiA1MC4yNTExIDQyLjMyMzZDNTAuNzM0NSA0Mi4zMjM2IDUwLjk1NjYgNDIuMTUzOCA1MC45NTY2IDQxLjk0NDhDNTAuOTU2NiA0MS4xNDc4IDQ3LjUwNzUgNDEuOTU3OCA0Ny41MDc1IDM5LjQyMzJDNDcuNTA3NSAzOC4xOTUxIDQ4LjU1MjcgMzcuMjAyMiA1MC4zMjk1IDM3LjIwMjJDNTEuODg0MyAzNy4yMDIyIDUyLjczMzUgMzcuOTU5OSA1My4xMTI0IDM4LjY5MTZMNTEuNDEzOSAzOS42NTg0QzUxLjI3MDIgMzkuMjY2NCA1MC44MjYgMzkuMDMxMyA1MC4zODE4IDM5LjAzMTNDNTAuMDQyMSAzOS4wMzEzIDQ5Ljg1OTIgMzkuMTc1IDQ5Ljg1OTIgMzkuMzcxQzQ5Ljg1OTIgNDAuMTgxIDUzLjMwODMgMzkuNDM2MyA1My4zMDgzIDQxLjg5MjVDNTMuMzA4MyA0My4yNjQzIDUxLjk2MjcgNDQuMTUyNyA1MC4zNjg3IDQ0LjE1MjdaIiBmaWxsPSIjRURFQkRFIj48L3BhdGg+CjxwYXRoIGQ9Ik00Mi4xNTI0IDQ2Ljk0ODZIMzkuODAwN1Y0MC42NjQ0QzM5LjgwMDcgMzguNjY1NSA0MS4zMDMyIDM3LjIxNTIgNDMuMzkzNiAzNy4yMTUyQzQ1LjQzMTcgMzcuMjE1MiA0Ni45ODY0IDM4Ljc3IDQ2Ljk4NjQgNDAuNjY0NEM0Ni45ODY0IDQyLjc1NDggNDUuNjAxNiA0NC4xMzk3IDQzLjUyNDIgNDQuMTM5N0M0My4wNDA4IDQ0LjEzOTcgNDIuNTU3NCA0My45OTYgNDIuMTUyNCA0My43NzM4VjQ2Ljk0ODZaTTQzLjM5MzYgNDEuOTMxN0M0NC4xMzgzIDQxLjkzMTcgNDQuNjM0NyA0MS4zNTY4IDQ0LjYzNDcgNDAuNjc3NUM0NC42MzQ3IDM5Ljk4NSA0NC4xMzgzIDM5LjQyMzIgNDMuMzkzNiAzOS40MjMyQzQyLjY0ODkgMzkuNDIzMiA0Mi4xNTI0IDM5Ljk4NSA0Mi4xNTI0IDQwLjY3NzVDNDIuMTUyNCA0MS4zNTY4IDQyLjY0ODkgNDEuOTMxNyA0My4zOTM2IDQxLjkzMTdaIiBmaWxsPSIjRURFQkRFIj48L3BhdGg+CjxwYXRoIGQ9Ik0zNy40NDQ5IDM2LjcxODhDMzYuNjc0MSAzNi43MTg4IDM2LjAzMzkgMzYuMDc4NiAzNi4wMzM5IDM1LjMwNzhDMzYuMDMzOSAzNC41MzY5IDM2LjY3NDEgMzMuODk2NyAzNy40NDQ5IDMzLjg5NjdDMzguMjE1OCAzMy44OTY3IDM4Ljg1NiAzNC41MzY5IDM4Ljg1NiAzNS4zMDc4QzM4Ljg1NiAzNi4wNzg2IDM4LjIxNTggMzYuNzE4OCAzNy40NDQ5IDM2LjcxODhaTTM2LjI2OTEgMzcuNDExMkgzOC42MjA4VjQzLjk0MzdIMzYuMjY5MVYzNy40MTEyWiIgZmlsbD0iI0VERUJERSI+PC9wYXRoPgo8cGF0aCBkPSJNMzEuNzU3MSA0NC4xMzk3QzI5LjcxOSA0NC4xMzk3IDI4LjE2NDIgNDIuNTcxOSAyOC4xNjQyIDQwLjY2NDRDMjguMTY0MiAzOC43NyAyOS43MTkgMzcuMjAyMiAzMS43NTcxIDM3LjIwMjJDMzMuNzk1MiAzNy4yMDIyIDM1LjM1IDM4Ljc3IDM1LjM1IDQwLjY2NDRDMzUuMzUgNDIuNTcxOSAzMy43OTUyIDQ0LjEzOTcgMzEuNzU3MSA0NC4xMzk3Wk0zMS43NTcxIDQxLjkzMTdDMzIuNTAxOCA0MS45MzE3IDMyLjk5ODMgNDEuMzU2OCAzMi45OTgzIDQwLjY3NzVDMzIuOTk4MyAzOS45ODUgMzIuNTAxOCAzOS40MTAyIDMxLjc1NzEgMzkuNDEwMkMzMS4wMTI0IDM5LjQxMDIgMzAuNTE1OSAzOS45ODUgMzAuNTE1OSA0MC42Nzc1QzMwLjUxNTkgNDEuMzU2OCAzMS4wMTI0IDQxLjkzMTcgMzEuNzU3MSA0MS45MzE3WiIgZmlsbD0iI0VERUJERSI+PC9wYXRoPgo8cGF0aCBkPSJNMjMuNzIzNyA0Ny4xNDQ2QzIyLjEwMzYgNDcuMTQ0NiAyMC45Mjc4IDQ2LjQyNiAyMC4zMjY4IDQ1LjE0NTdMMjIuMjczNSA0NC4xMDA1QzIyLjQ4MjUgNDQuNTQ0NyAyMi44NzQ1IDQ1LjAwMiAyMy42ODQ1IDQ1LjAwMkMyNC41MzM3IDQ1LjAwMiAyNS4wOTU1IDQ0LjQ2NjMgMjUuMTYwOCA0My41MjU2QzI0Ljg0NzMgNDMuOCAyNC4zNTA4IDQ0LjAwOSAyMy42MDYxIDQ0LjAwOUMyMS43OTAxIDQ0LjAwOSAyMC4zMjY4IDQyLjYxMTEgMjAuMzI2OCA0MC42NTEzQzIwLjMyNjggMzguNzU2OSAyMS44ODE1IDM3LjIxNTIgMjMuOTE5NyAzNy4yMTUyQzI2LjAxIDM3LjIxNTIgMjcuNTEyNSAzOC42NjU1IDI3LjUxMjUgNDAuNjY0NFY0My4yNTEzQzI3LjUxMjUgNDUuNjAyOSAyNS44Nzk0IDQ3LjE0NDYgMjMuNzIzNyA0Ny4xNDQ2Wk0yMy44ODA1IDQxLjgwMUMyNC41ODYgNDEuODAxIDI1LjEyMTYgNDEuMzE3NiAyNS4xMjE2IDQwLjU5OTFDMjUuMTIxNiAzOS44OTM2IDI0LjU4NiAzOS40MjMyIDIzLjg4MDUgMzkuNDIzMkMyMy4xODggMzkuNDIzMiAyMi42MzkzIDM5Ljg5MzYgMjIuNjM5MyA0MC41OTkxQzIyLjYzOTMgNDEuMzE3NiAyMy4xODggNDEuODAxIDIzLjg4MDUgNDEuODAxWiIgZmlsbD0iI0VERUJERSI+PC9wYXRoPgo8cGF0aCBkPSJNMTYuMzM3NCA0NC4xMzk3QzE0LjI5OTMgNDQuMTM5NyAxMi43NDQ1IDQyLjU3MTkgMTIuNzQ0NSA0MC42NjQ0QzEyLjc0NDUgMzguNzcgMTQuMjk5MyAzNy4yMDIyIDE2LjMzNzQgMzcuMjAyMkMxOC4zNzU1IDM3LjIwMjIgMTkuOTMwMyAzOC43NyAxOS45MzAzIDQwLjY2NDRDMTkuOTMwMyA0Mi41NzE5IDE4LjM3NTUgNDQuMTM5NyAxNi4zMzc0IDQ0LjEzOTdaTTE2LjMzNzQgNDEuOTMxN0MxNy4wODIxIDQxLjkzMTcgMTcuNTc4NiA0MS4zNTY4IDE3LjU3ODYgNDAuNjc3NUMxNy41Nzg2IDM5Ljk4NSAxNy4wODIxIDM5LjQxMDIgMTYuMzM3NCAzOS40MTAyQzE1LjU5MjcgMzkuNDEwMiAxNS4wOTYyIDM5Ljk4NSAxNS4wOTYyIDQwLjY3NzVDMTUuMDk2MiA0MS4zNTY4IDE1LjU5MjcgNDEuOTMxNyAxNi4zMzc0IDQxLjkzMTdaIiBmaWxsPSIjRURFQkRFIj48L3BhdGg+CjxwYXRoIGQ9Ik04LjQ2MjE4IDM1LjEyNDlWNDEuNzIyN0gxMi4zNTU1VjQzLjk0MzdIOC4xMzU1NkM2LjY0NjE1IDQzLjk0MzcgNi4wNDUxNyA0My4xNTk4IDYuMDQ1MTcgNDEuOTE4NlYzNS4xMjQ5SDguNDYyMThaIiBmaWxsPSIjRURFQkRFIj48L3BhdGg+CjxwYXRoIGQ9Ik0zMy45OTk5IDYuNTI3NzRDMzUuOTQ3NiA2LjE4MDk1IDM3Ljk1MjYgNiA0MCA2QzQyLjA0NzMgNiA0NC4wNTIzIDYuMTgwOTUgNDUuOTk5OSA2LjUyNzczVjI5Ljc4NjdIMzMuOTk5OVY2LjUyNzc0WiIgZmlsbD0iI0FDMjgyOCI+PC9wYXRoPgo8cGF0aCBkPSJNNDYuMDAwNCA1MC4yMTMzSDMzLjk5OTNDMzMuOTc0MSA1NS40MzEyIDMzLjAyMzQgNjAuNjAzOCAzMS4xOTA5IDY1LjQ5MDZMMjguNzE4NSA3Mi4wODM2QzMyLjI0ODYgNzMuMzI0OSAzNi4wNDU0IDc0IDM5Ljk5OTkgNzRDNDMuOTU0MyA3NCA0Ny43NTExIDczLjMyNDkgNTEuMjgxMiA3Mi4wODM2TDQ4LjgwODggNjUuNDkwNkM0Ni45NzYzIDYwLjYwMzggNDYuMDI1NiA1NS40MzEyIDQ2LjAwMDQgNTAuMjEzM1oiIGZpbGw9IiNBQzI4MjgiPjwvcGF0aD4KPHBhdGggZD0iTTQ2IDYuNTI3NzFDNTAuMzQwOCA3LjMwMDYgNTQuMzk2NyA4Ljg5NzIyIDU4IDExLjE1MDJWMjkuNzg2N0g0NlY2LjUyNzcxWiIgZmlsbD0iIzMwNkE5RiI+PC9wYXRoPgo8cGF0aCBkPSJNNTEuMjgxMyA3Mi4wODM2QzU2LjczODcgNzAuMTY0NyA2MS41NTg5IDY2Ljg5MjggNjUuMzM5MSA2Mi42NzA0TDY0Ljc0OTIgNjIuMjk1QzYwLjYwNTYgNTkuNjU4MSA1OC4wNzMgNTUuMTE0NyA1OC4wMDE1IDUwLjIxMzNINDYuMDAwNUM0Ni4wMjU3IDU1LjQzMTIgNDYuOTc2NCA2MC42MDM4IDQ4LjgwOSA2NS40OTA2TDUxLjI4MTMgNzIuMDgzNloiIGZpbGw9IiMzMDZBOUYiPjwvcGF0aD4KPHBhdGggZD0iTTIxLjk5OTkgMTEuMTUwMkMyNS42MDMyIDguODk3MjMgMjkuNjU5IDcuMzAwNiAzMy45OTk5IDYuNTI3NzFWMjkuNzg2N0gyMS45OTk5VjExLjE1MDJaIiBmaWxsPSIjREVCMDM4Ij48L3BhdGg+CjxwYXRoIGQ9Ik0yMS45OTgzIDUwLjIxMzNIMzMuOTk5M0MzMy45NzQyIDU1LjQwODkgMzMuMDMxNSA2MC41NTk2IDMxLjIxNDMgNjUuNDI4TDI4LjcxODUgNzIuMDgzNkMyMy4yNjEgNzAuMTY0NyAxOC40NDA5IDY2Ljg5MjggMTQuNjYwNiA2Mi42NzA0TDE1LjI1MDUgNjIuMjk1QzE5LjM5NDEgNTkuNjU4MSAyMS45MjY4IDU1LjExNDcgMjEuOTk4MyA1MC4yMTMzWiIgZmlsbD0iI0RFQjAzOCI+PC9wYXRoPgo8L3N2Zz4=' diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index 5bf98117a..3dc31f632 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -34,7 +34,7 @@ import { useWallets } from '../../hooks/useWallets.js' import { useWalletSettings } from '../../hooks/useWalletSettings.js' import type { ConnectConfig, ExtendedConnector, LogoProps } from '../../types.js' import { formatAddress, isEmailValid } from '../../utils/helpers.js' -import type { WalletConfigurationProvider } from '../../utils/walletConfiguration.js' +import type { WalletConfigurationOverrides, WalletConfigurationProvider } from '../../utils/walletConfiguration.js' import { AppleWaasConnectButton, ConnectButton, @@ -89,6 +89,7 @@ interface ConnectProps extends SequenceConnectProviderProps { isV3WalletSignedIn?: boolean | null isAuthStatusLoading?: boolean resolvedConfig?: ConnectConfig + walletConfigurationSignIn?: WalletConfigurationOverrides['signIn'] } export const Connect = (props: ConnectProps) => { @@ -99,17 +100,21 @@ export const Connect = (props: ConnectProps) => { const { analytics } = useAnalyticsContext() const { hideExternalConnectOptions, hideConnectedWallets, hideSocialConnectOptions } = useWalletSettings() - const { onClose, emailConflictInfo, config: incomingConfig = {} as ConnectConfig, isInline = false } = props - const config = props.resolvedConfig ?? incomingConfig + const { onClose, emailConflictInfo, config: baseConfig = {} as ConnectConfig, isInline = false } = props + const config = props.resolvedConfig ?? baseConfig const isV3WalletSignedIn = props.isV3WalletSignedIn ?? null const isAuthStatusLoading = props.isAuthStatusLoading ?? false + const walletConfigurationSignIn = props.walletConfigurationSignIn const { signIn = {} } = config + const baseSignIn = baseConfig.signIn ?? {} const storage = useStorage() const descriptiveSocials = !!config?.signIn?.descriptiveSocials const showWalletAuthOptionsFirst = config?.signIn?.showWalletAuthOptionsFirst ?? false const [isLoading, setIsLoading] = useState(false) - const projectName = config?.signIn?.projectName + const projectName = baseSignIn?.projectName + const ecosystemProjectName = walletConfigurationSignIn?.projectName ?? baseSignIn?.projectName + const ecosystemLogoUrl = walletConfigurationSignIn?.logoUrl ?? baseSignIn?.logoUrl const [email, setEmail] = useState('') const [showEmailWaasPinInput, setShowEmailWaasPinInput] = useState(false) @@ -538,9 +543,8 @@ export const Connect = (props: ConnectProps) => { // Special handling for ecosystem connector - use config data for display if (connector._wallet?.isEcosystemWallet) { - const signInConfig = config?.signIn - const projectName = signInConfig?.projectName || connector._wallet.name - const logoUrl = signInConfig?.logoUrl + const projectName = ecosystemProjectName || connector._wallet.name + const logoUrl = ecosystemLogoUrl const renderEcosystemLogo = (logoProps: LogoProps) => ( { _wallet: { ...connector._wallet, name: projectName, - ctaText: signInConfig?.projectName ? `Connect with ${signInConfig.projectName}` : connector._wallet.ctaText, + ctaText: ecosystemProjectName ? `Connect with ${ecosystemProjectName}` : connector._wallet.ctaText, // Override logos if logoUrl is available ...(logoUrl && { logoDark: renderEcosystemLogo, @@ -949,7 +953,7 @@ export const Connect = (props: ConnectProps) => { /> ) : ( <> - {!hasAnyConnection && } + {!hasAnyConnection && } {showWalletAuthOptionsFirst && !hideExternalConnectOptions && walletConnectors.length > 0 && ( diff --git a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx index 0eb8757aa..925d18904 100644 --- a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx +++ b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx @@ -74,7 +74,8 @@ export const SequenceConnectInlineProvider = (props: SequenceConnectInlineProvid isLoading: isWalletConfigLoading, enabledProviders, isV3WalletSignedIn, - isAuthStatusLoading + isAuthStatusLoading, + walletConfigurationSignIn } = useResolvedConnectConfig(incomingConfig) const { @@ -257,11 +258,12 @@ export const SequenceConnectInlineProvider = (props: SequenceConnectInlineProvid emailConflictInfo={emailConflictInfo} isInline {...props} - config={config} + config={incomingConfig} resolvedConfig={config} isV3WalletSignedIn={isV3WalletSignedIn} isAuthStatusLoading={isAuthStatusLoading} enabledProviders={enabledProviders} + walletConfigurationSignIn={walletConfigurationSignIn} /> )} diff --git a/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx b/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx index 74812ec58..1495efe18 100644 --- a/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx +++ b/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx @@ -47,7 +47,8 @@ export const SequenceConnectPreviewProvider = (props: SequenceConnectProviderPro isLoading: isWalletConfigLoading, enabledProviders, isV3WalletSignedIn, - isAuthStatusLoading + isAuthStatusLoading, + walletConfigurationSignIn } = useResolvedConnectConfig(incomingConfig) const { @@ -109,11 +110,12 @@ export const SequenceConnectPreviewProvider = (props: SequenceConnectProviderPro emailConflictInfo={emailConflictInfo} isInline {...props} - config={config} + config={incomingConfig} resolvedConfig={config} isV3WalletSignedIn={isV3WalletSignedIn} isAuthStatusLoading={isAuthStatusLoading} enabledProviders={enabledProviders} + walletConfigurationSignIn={walletConfigurationSignIn} /> )} diff --git a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx index 253a2a4b3..d85fd832c 100644 --- a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx +++ b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx @@ -52,7 +52,8 @@ export const SequenceConnectProvider = (props: SequenceConnectProviderProps) => isLoading: isWalletConfigLoading, enabledProviders, isV3WalletSignedIn, - isAuthStatusLoading + isAuthStatusLoading, + walletConfigurationSignIn } = useResolvedConnectConfig(incomingConfig) const { defaultTheme = 'dark', @@ -252,11 +253,12 @@ export const SequenceConnectProvider = (props: SequenceConnectProviderProps) => onClose={() => setOpenConnectModal(false)} emailConflictInfo={emailConflictInfo} {...props} - config={config} + config={incomingConfig} resolvedConfig={config} isV3WalletSignedIn={isV3WalletSignedIn} isAuthStatusLoading={isAuthStatusLoading} enabledProviders={enabledProviders} + walletConfigurationSignIn={walletConfigurationSignIn} /> )} diff --git a/packages/connect/src/hooks/useResolvedConnectConfig.ts b/packages/connect/src/hooks/useResolvedConnectConfig.ts index 86f688f11..f27889094 100644 --- a/packages/connect/src/hooks/useResolvedConnectConfig.ts +++ b/packages/connect/src/hooks/useResolvedConnectConfig.ts @@ -9,6 +9,7 @@ import { mapWalletConfigurationToOverrides, mergeConnectConfigWithWalletConfiguration, normalizeWalletUrl, + type WalletConfigurationOverrides, type WalletConfigurationProvider } from '../utils/walletConfiguration.js' @@ -16,6 +17,7 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { const [resolvedConfig, setResolvedConfig] = useState(config) const [isLoading, setIsLoading] = useState(false) const [enabledProviders, setEnabledProviders] = useState(undefined) + const [walletConfigurationSignIn, setWalletConfigurationSignIn] = useState() const [isV3WalletSignedIn, setIsV3WalletSignedIn] = useState(null) const [isAuthStatusLoading, setIsAuthStatusLoading] = useState(false) @@ -26,6 +28,7 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { useEffect(() => { setResolvedConfig(config) setEnabledProviders(undefined) + setWalletConfigurationSignIn(undefined) setIsV3WalletSignedIn(null) setIsAuthStatusLoading(false) }, [config]) @@ -34,20 +37,12 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { let cancelled = false const cachedProjectName = normalizedWalletUrl ? getCachedProjectName(normalizedWalletUrl) : undefined - const configWithCachedProjectName = - cachedProjectName && !config.signIn?.projectName - ? { - ...config, - signIn: { - ...config.signIn, - projectName: cachedProjectName - } - } - : config + const cachedSignIn = cachedProjectName ? { projectName: cachedProjectName } : undefined if (!normalizedWalletUrl) { - setResolvedConfig(configWithCachedProjectName) + setResolvedConfig(config) setEnabledProviders(undefined) + setWalletConfigurationSignIn(undefined) setIsV3WalletSignedIn(null) setIsLoading(false) setIsAuthStatusLoading(false) @@ -56,7 +51,8 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { } } - setResolvedConfig(configWithCachedProjectName) + setResolvedConfig(config) + setWalletConfigurationSignIn(cachedSignIn) setIsLoading(true) setIsAuthStatusLoading(true) @@ -86,7 +82,8 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { const overrides = mapWalletConfigurationToOverrides(remoteConfig) setEnabledProviders(overrides.enabledProviders) - setResolvedConfig(mergeConnectConfigWithWalletConfiguration(configWithCachedProjectName, overrides)) + setWalletConfigurationSignIn(overrides.signIn) + setResolvedConfig(mergeConnectConfigWithWalletConfiguration(config, overrides)) if (overrides.signIn?.projectName) { cacheProjectName(normalizedWalletUrl, overrides.signIn.projectName) } @@ -96,6 +93,7 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { console.warn('Failed to fetch wallet configuration', error) setResolvedConfig(config) setEnabledProviders(undefined) + setWalletConfigurationSignIn(cachedSignIn) } }) .finally(() => { @@ -114,9 +112,10 @@ export const useResolvedConnectConfig = (config: ConnectConfig) => { resolvedConfig, isLoading, enabledProviders, + walletConfigurationSignIn, isV3WalletSignedIn, isAuthStatusLoading }), - [resolvedConfig, isLoading, enabledProviders, isV3WalletSignedIn, isAuthStatusLoading] + [resolvedConfig, isLoading, enabledProviders, walletConfigurationSignIn, isV3WalletSignedIn, isAuthStatusLoading] ) } diff --git a/packages/connect/src/hooks/useWallets.ts b/packages/connect/src/hooks/useWallets.ts index 1fdae6650..bfb15f2a2 100644 --- a/packages/connect/src/hooks/useWallets.ts +++ b/packages/connect/src/hooks/useWallets.ts @@ -236,8 +236,7 @@ export const useWallets = (): UseWalletsReturnType => { const { disconnectAsync } = useDisconnect() const connectConfig = useOptionalConnectConfigContext() const normalizedWalletUrl = connectConfig?.walletUrl ? normalizeWalletUrl(connectConfig.walletUrl) : '' - const sequenceProjectName = - connectConfig?.signIn?.projectName || (normalizedWalletUrl ? getCachedProjectName(normalizedWalletUrl) : undefined) + const sequenceProjectName = normalizedWalletUrl ? getCachedProjectName(normalizedWalletUrl) : undefined const waasConnection = connections.find(c => (c.connector as ExtendedConnector)?.type === 'sequence-waas') @@ -279,7 +278,7 @@ export const useWallets = (): UseWalletsReturnType => { }, [connections]) const baseConnections: UseConnectionsReturnType = useMemo(() => { - const isReconnecting = accountStatus === 'connecting' || accountStatus === 'reconnecting' + const isReconnecting = accountStatus === 'reconnecting' if (connections.length === 0 && isReconnecting && lastKnownConnectionsRef.current.length > 0) { return lastKnownConnectionsRef.current } @@ -352,11 +351,17 @@ export const useWallets = (): UseWalletsReturnType => { if (nextList.length === 0) { // When there are no connections and wagmi isn't reconnecting, clear the stable list. if (accountStatus !== 'connecting' && accountStatus !== 'reconnecting') { - setStableWallets([]) + setStableWallets(prev => (prev.length === 0 ? prev : [])) } return } + // If we have wallets and the stable list is empty, hydrate immediately to avoid an empty emission. + if (stableWallets.length === 0) { + setStableWallets(nextList) + return + } + const timer = setTimeout(() => { setStableWallets(prev => (areWalletListsEqual(prev, nextList) ? prev : nextList)) }, 120) @@ -364,7 +369,7 @@ export const useWallets = (): UseWalletsReturnType => { return () => { clearTimeout(timer) } - }, [walletsFromConnections, accountStatus]) + }, [walletsFromConnections, accountStatus, stableWallets.length]) const setActiveWallet = async (walletAddress: string) => { const connection = connections.find( @@ -423,7 +428,6 @@ export const useWallets = (): UseWalletsReturnType => { const getConnectorName = (connector: Connector, sequenceProjectName?: string, ecosystemProjectName?: string) => { const connectorName = connector.name - const connectorWalletName = (connector._wallet as any)?.name if (sequenceProjectName && connector.type === 'sequence-v3-wallet') { return sequenceProjectName @@ -433,7 +437,7 @@ const getConnectorName = (connector: Connector, sequenceProjectName?: string, ec return ecosystemProjectName } - return connectorWalletName ?? connectorName + return connectorName } const getSignInMethod = (connection: UseConnectionsReturnType[number]) => { diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index 95cc61b20..73a764e4c 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -2483,4 +2483,6 @@ export const styles = String.raw` --tw-gradient-to-position: 100%; } } -}` +} + +` diff --git a/packages/connect/src/utils/walletConfiguration.ts b/packages/connect/src/utils/walletConfiguration.ts index d08bfcc9e..447d7a8a0 100644 --- a/packages/connect/src/utils/walletConfiguration.ts +++ b/packages/connect/src/utils/walletConfiguration.ts @@ -1,5 +1,3 @@ -import type { Theme } from '@0xsequence/design-system' - import type { ConnectConfig } from '../types.js' export type WalletConfigurationProvider = 'EMAIL' | 'GOOGLE' | 'APPLE' | 'PASSKEY' @@ -37,12 +35,11 @@ type WalletConfigurationResponse = { supportedChains?: number[] } -type WalletConfigurationOverrides = { +export type WalletConfigurationOverrides = { signIn?: { projectName?: string logoUrl?: string } - defaultTheme?: Theme chainIds?: number[] enabledProviders?: WalletConfigurationProvider[] } @@ -184,10 +181,6 @@ export const mapWalletConfigurationToOverrides = (config: WalletConfigurationRes const projectName = typeof config.name === 'string' && config.name.trim() ? config.name : undefined const logoUrl = pickLogoUrl(config) - const normalizedTheme = config.defaultTheme?.toLowerCase() - const defaultTheme: Theme | undefined = - normalizedTheme === 'dark' || normalizedTheme === 'light' ? (normalizedTheme as Theme) : undefined - const chainIds = Array.isArray(config.supportedChains) && config.supportedChains.length > 0 ? config.supportedChains : undefined const enabledProviders = normalizeEnabledProviders(config.enabledProviders) @@ -200,7 +193,6 @@ export const mapWalletConfigurationToOverrides = (config: WalletConfigurationRes logoUrl } : undefined, - defaultTheme, chainIds, enabledProviders } @@ -218,17 +210,6 @@ export const mergeConnectConfigWithWalletConfiguration = ( ...config } - if (overrides.signIn) { - mergedConfig.signIn = { - ...config.signIn, - ...overrides.signIn - } - } - - if (overrides.defaultTheme !== undefined) { - mergedConfig.defaultTheme = overrides.defaultTheme - } - if (overrides.chainIds !== undefined) { mergedConfig.chainIds = overrides.chainIds } From 1dd8358eb777b6b0a02d0d56d3aa2dd30392cd4f Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 16:10:45 +0300 Subject: [PATCH 04/10] Update --- packages/connect/src/hooks/useWallets.ts | 30 ++++++++++++++++++++---- packages/connect/src/styles.ts | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/connect/src/hooks/useWallets.ts b/packages/connect/src/hooks/useWallets.ts index bfb15f2a2..638a9caa4 100644 --- a/packages/connect/src/hooks/useWallets.ts +++ b/packages/connect/src/hooks/useWallets.ts @@ -272,16 +272,36 @@ export const useWallets = (): UseWalletsReturnType => { // Keep track of the last non-empty connections list so we can present stable data while wagmi reconnects. const lastKnownConnectionsRef = useRef([]) useEffect(() => { - if (connections.length > 0) { - lastKnownConnectionsRef.current = connections + const isRecovering = accountStatus === 'connecting' || accountStatus === 'reconnecting' + + if (accountStatus === 'disconnected') { + lastKnownConnectionsRef.current = [] + return + } + + if (connections.length === 0) { + return + } + + // Do not downgrade the cache while wagmi is recovering; otherwise we risk flickering counts. + if (connections.length < lastKnownConnectionsRef.current.length && isRecovering) { + return } - }, [connections]) + + lastKnownConnectionsRef.current = connections + }, [connections, accountStatus]) const baseConnections: UseConnectionsReturnType = useMemo(() => { - const isReconnecting = accountStatus === 'reconnecting' - if (connections.length === 0 && isReconnecting && lastKnownConnectionsRef.current.length > 0) { + const isRecovering = accountStatus === 'connecting' || accountStatus === 'reconnecting' + const shouldUseCache = + isRecovering && + lastKnownConnectionsRef.current.length > 0 && + (connections.length === 0 || connections.length < lastKnownConnectionsRef.current.length) + + if (shouldUseCache) { return lastKnownConnectionsRef.current } + return connections }, [connections, accountStatus]) diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index 73a764e4c..5391e6b94 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -2485,4 +2485,4 @@ export const styles = String.raw` } } -` +` \ No newline at end of file From a8c2e7b7b93d250ce410796d841ab88eaef2a10c Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 16:20:32 +0300 Subject: [PATCH 05/10] Update --- packages/connect/src/components/Connect/Connect.tsx | 4 +++- packages/connect/src/styles.ts | 2 +- packages/connect/src/utils/walletConfiguration.ts | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index 3dc31f632..e2379d61e 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -555,8 +555,10 @@ export const Connect = (props: ConnectProps) => { style={{ objectFit: 'contain', width: 'auto', + minWidth: '30px', height: '100%', - maxWidth: '100%', + minHeight: '30px', + maxWidth: '60px', maxHeight: '100%', ...logoProps.style }} diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index 5391e6b94..73a764e4c 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -2485,4 +2485,4 @@ export const styles = String.raw` } } -` \ No newline at end of file +` diff --git a/packages/connect/src/utils/walletConfiguration.ts b/packages/connect/src/utils/walletConfiguration.ts index 447d7a8a0..103d2de29 100644 --- a/packages/connect/src/utils/walletConfiguration.ts +++ b/packages/connect/src/utils/walletConfiguration.ts @@ -124,8 +124,7 @@ const pickLogoUrl = (config: WalletConfigurationResponse): string | undefined => ) as string[] const getLogoFromTheme = (theme?: WalletConfigurationTheme) => { - // Prefer header logo for more compact aspect ratios; fall back to auth logo - return theme?.fileHeaderLogo?.src || theme?.fileAuthLogo?.src + return theme?.fileAuthLogo?.src } for (const themeKey of themeOrder) { From c7348409ae9dbccb52ceceae5a5a7c68d271f71e Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 18:41:53 +0300 Subject: [PATCH 06/10] Safari pop up fix --- .../src/components/Connect/Connect.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index e2379d61e..26b0a209a 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -286,6 +286,31 @@ export const Connect = (props: ConnectProps) => { const extendedConnectors = filteredConnectors as ExtendedConnector[] + const isSequenceV3Connector = (connector: ExtendedConnector): connector is ExtendedConnector & SequenceV3Connector => { + return connector.type === SEQUENCE_V3_CONNECTOR_TYPE + } + + // Safari aggressively blocks popups if window.open is not triggered directly from the click handler. + // Pre-open the Sequence popup in the same gesture before we kick off the async wagmi connect flow. + const preopenSequenceV3Popup = useCallback((connector: ExtendedConnector & SequenceV3Connector) => { + if (typeof window === 'undefined') return + + try { + const client: any = connector.client + const transport = typeof client?.ensureTransport === 'function' ? client.ensureTransport() : client?.transport + const isPopupMode = (transport?.mode ?? client?.transportMode) === 'popup' + + if (isPopupMode && typeof transport?.openWallet === 'function') { + // Use the same path used by connect requests so the opened window is reused. + transport.openWallet('/request/connect').catch(() => { + /* Ignore preopen failures; the main connect flow will try again */ + }) + } + } catch (error) { + console.warn('Failed to pre-open Sequence popup', error) + } + }, []) + const sequenceConnectors = useMemo( () => extendedConnectors.filter( @@ -673,6 +698,10 @@ export const Connect = (props: ConnectProps) => { return } + if (isSequenceV3Connector(connector)) { + preopenSequenceV3Popup(connector) + } + if (connector._wallet.id === 'email') { const email = prompt('Auto-email login, please specify the email address:') From 17494c962e6950561090b5029399c208156d1d4d Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 20:27:15 +0300 Subject: [PATCH 07/10] setChains util and updating chains dynamically from remote config --- examples/react/src/config.ts | 4 +- .../src/components/Connect/Connect.tsx | 4 +- .../SequenceConnectInlineProvider.tsx | 2 + .../SequenceConnectPreviewProvider.tsx | 2 + .../SequenceConnectProvider.tsx | 2 + .../connect/src/hooks/useSyncWagmiChains.ts | 61 +++++++++++++++++++ packages/connect/src/index.ts | 1 + packages/connect/src/utils/setChains.ts | 29 +++++++++ 8 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 packages/connect/src/hooks/useSyncWagmiChains.ts create mode 100644 packages/connect/src/utils/setChains.ts diff --git a/examples/react/src/config.ts b/examples/react/src/config.ts index 86cd74fb9..65d7d781e 100644 --- a/examples/react/src/config.ts +++ b/examples/react/src/config.ts @@ -99,7 +99,7 @@ export const createExampleConfig = (walletUrl: string) => walletUrl: sanitizeWalletUrl(walletUrl), dappOrigin: window.location.origin, appName: 'Sequence Web SDK Demo', - defaultChainId: ChainId.OPTIMISM, + defaultChainId: ChainId.ARBITRUM_SEPOLIA, walletConnect: { projectId: walletConnectProjectId }, @@ -108,7 +108,7 @@ export const createExampleConfig = (walletUrl: string) => enableImplicitSession: true, includeFeeOptionPermissions: true, explicitSessionParams: { - chainId: ChainId.OPTIMISM, + chainId: ChainId.ARBITRUM_SEPOLIA, nativeTokenSpending: { valueLimit: parseEther('0.1') }, diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index 26b0a209a..14d1ae3d9 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -293,7 +293,9 @@ export const Connect = (props: ConnectProps) => { // Safari aggressively blocks popups if window.open is not triggered directly from the click handler. // Pre-open the Sequence popup in the same gesture before we kick off the async wagmi connect flow. const preopenSequenceV3Popup = useCallback((connector: ExtendedConnector & SequenceV3Connector) => { - if (typeof window === 'undefined') return + if (typeof window === 'undefined') { + return + } try { const client: any = connector.client diff --git a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx index 925d18904..db13ca1a1 100644 --- a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx +++ b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx @@ -19,6 +19,7 @@ import { ThemeContextProvider } from '../../contexts/Theme.js' import { WalletConfigContextProvider } from '../../contexts/WalletConfig.js' import { useResolvedConnectConfig } from '../../hooks/useResolvedConnectConfig.js' import { useStorage } from '../../hooks/useStorage.js' +import { useSyncWagmiChains } from '../../hooks/useSyncWagmiChains.js' import { useWaasConfirmationHandler } from '../../hooks/useWaasConfirmationHandler.js' import { useEmailConflict } from '../../hooks/useWaasEmailConflict.js' import { styleProperties } from '../../styleProperties.js' @@ -103,6 +104,7 @@ export const SequenceConnectInlineProvider = (props: SequenceConnectInlineProvid const [analytics, setAnalytics] = useState() const { address, isConnected } = useAccount() const wagmiConfig = useConfig() + useSyncWagmiChains(config, wagmiConfig) const storage = useStorage() const connections = useConnections() const waasConnector: Connector | undefined = connections.find(c => c.connector.id.includes('waas'))?.connector diff --git a/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx b/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx index 1495efe18..3e1782766 100644 --- a/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx +++ b/packages/connect/src/components/SequenceConnectPreview/SequenceConnectPreviewProvider.tsx @@ -10,6 +10,7 @@ import { ConnectConfigContextProvider } from '../../contexts/ConnectConfig.js' import { ThemeContextProvider } from '../../contexts/Theme.js' import { WalletConfigContextProvider } from '../../contexts/WalletConfig.js' import { useResolvedConnectConfig } from '../../hooks/useResolvedConnectConfig.js' +import { useSyncWagmiChains } from '../../hooks/useSyncWagmiChains.js' import { useEmailConflict } from '../../hooks/useWaasEmailConflict.js' import { type ConnectConfig, type DisplayedAsset, type ExtendedConnector, type ModalPosition } from '../../types.js' import { Connect } from '../Connect/Connect.js' @@ -66,6 +67,7 @@ export const SequenceConnectPreviewProvider = (props: SequenceConnectProviderPro const [displayedAssets, setDisplayedAssets] = useState(displayedAssetsSetting) const wagmiConfig = useConfig() + useSyncWagmiChains(config, wagmiConfig) const inlineBackground = resolveInlineBackground(theme) diff --git a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx index d85fd832c..cc5ab0149 100644 --- a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx +++ b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx @@ -19,6 +19,7 @@ import { ThemeContextProvider } from '../../contexts/Theme.js' import { WalletConfigContextProvider } from '../../contexts/WalletConfig.js' import { useResolvedConnectConfig } from '../../hooks/useResolvedConnectConfig.js' import { useStorage } from '../../hooks/useStorage.js' +import { useSyncWagmiChains } from '../../hooks/useSyncWagmiChains.js' import { useWaasConfirmationHandler } from '../../hooks/useWaasConfirmationHandler.js' import { useEmailConflict } from '../../hooks/useWaasEmailConflict.js' import { @@ -81,6 +82,7 @@ export const SequenceConnectProvider = (props: SequenceConnectProviderProps) => const [analytics, setAnalytics] = useState() const { address, isConnected } = useAccount() const wagmiConfig = useConfig() + useSyncWagmiChains(config, wagmiConfig) const connections = useConnections() const waasConnector: Connector | undefined = connections.find(c => c.connector.id.includes('waas'))?.connector const [isWalletWidgetOpen, setIsWalletWidgetOpen] = useState(false) diff --git a/packages/connect/src/hooks/useSyncWagmiChains.ts b/packages/connect/src/hooks/useSyncWagmiChains.ts new file mode 100644 index 000000000..ce85687b9 --- /dev/null +++ b/packages/connect/src/hooks/useSyncWagmiChains.ts @@ -0,0 +1,61 @@ +import { useEffect, useRef } from 'react' +import { type Chain, type Transport } from 'viem' +import { type Config } from 'wagmi' + +import { getDefaultChains } from '../config/defaultChains.js' +import { getDefaultTransports } from '../config/defaultTransports.js' +import type { ConnectConfig } from '../types.js' +import { setChains } from '../utils/setChains.js' + +const haveSameChainIds = (current: readonly Chain[], next: readonly Chain[]) => { + if (current.length !== next.length) { + return false + } + + return current.every((chain, index) => chain.id === next[index]?.id) +} + +export const useSyncWagmiChains = (config: ConnectConfig, wagmiConfig: Config) => { + const initialChainsRef = useRef(undefined) + const initialTransportsRef = useRef | undefined>(undefined) + + useEffect(() => { + const chainState = ((wagmiConfig as any)._internal?.chains?.getState?.() ?? wagmiConfig.chains) as readonly [ + Chain, + ...Chain[] + ] + initialChainsRef.current = chainState + + const transports = ((wagmiConfig as any)._internal?.transports ?? {}) as Record + initialTransportsRef.current = transports + }, [wagmiConfig]) + + useEffect(() => { + const initialChains = initialChainsRef.current + if (!initialChains) { + return + } + + const chainIds = Array.isArray(config.chainIds) && config.chainIds.length > 0 ? config.chainIds : undefined + const nextChains = chainIds ? getDefaultChains(chainIds) : initialChains + + const chainStore = (wagmiConfig as any)._internal?.chains + const currentChains = (chainStore?.getState?.() ?? wagmiConfig.chains) as readonly Chain[] + + if (haveSameChainIds(currentChains, nextChains)) { + return + } + + const currentTransports = + ((wagmiConfig as any)._internal?.transports as Record | undefined) || initialTransportsRef.current || {} + const transports = { + ...getDefaultTransports(nextChains, config.projectAccessKey), + ...currentTransports + } + + setChains(wagmiConfig, { + chains: nextChains, + transports + }) + }, [config.chainIds, config.projectAccessKey, wagmiConfig]) +} diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 496b986a6..c354de01d 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -52,6 +52,7 @@ export * from './utils/session/constants.js' // Utils export { getConnectWallets } from './utils/getConnectWallets.js' +export { setChains } from './utils/setChains.js' export { capitalize, compareAddress, diff --git a/packages/connect/src/utils/setChains.ts b/packages/connect/src/utils/setChains.ts new file mode 100644 index 000000000..bbf83fed4 --- /dev/null +++ b/packages/connect/src/utils/setChains.ts @@ -0,0 +1,29 @@ +import { type Chain, type Transport } from 'viem' +import { type Config } from 'wagmi' + +export function setChains( + config: Config, + { + chains, + transports + }: { + chains: readonly [Chain, ...Chain[]] + transports?: Record + } +) { + // 1. Update transports mapping + // We cast to 'any' to access the private '_internal' property + if (transports) { + const internalConfig = config as any + for (const chain of chains) { + const transport = transports[chain.id] + if (transport) { + internalConfig._internal.transports[chain.id] = transport + } + } + } + + // 2. Update chains state + // This will trigger subscribers (like WagmiProvider and hooks) to re-render + ;(config as any)._internal.chains.setState(chains) +} From 4f05707ccb48bbe7b389261d8e3c29756ae6896f Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Tue, 16 Dec 2025 20:49:16 +0300 Subject: [PATCH 08/10] Minor style changes --- packages/connect/src/components/Connect/Banner.tsx | 2 +- packages/connect/src/components/Connect/Connect.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/connect/src/components/Connect/Banner.tsx b/packages/connect/src/components/Connect/Banner.tsx index f40635392..b8a582eee 100644 --- a/packages/connect/src/components/Connect/Banner.tsx +++ b/packages/connect/src/components/Connect/Banner.tsx @@ -13,7 +13,7 @@ export const Banner = ({ config = {} as ConnectConfig }: BannerProps) => { return ( <> {logoUrl && ( -
+
)} diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index 14d1ae3d9..10881ab92 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -993,7 +993,7 @@ export const Connect = (props: ConnectProps) => { )} {!hasPrimarySequenceConnection && ( -
+
<> {showEcosystemConnectorSection && ecosystemConnector && (
Date: Wed, 17 Dec 2025 15:31:13 +0300 Subject: [PATCH 09/10] Use remote config for demo, fix connect UI element jump issue --- examples/react/src/App.tsx | 21 +++++++++++++++++-- .../src/components/Connect/Connect.tsx | 8 +++---- .../SequenceConnectInlineProvider.tsx | 2 +- .../SequenceConnectProvider.tsx | 2 +- packages/connect/src/index.ts | 1 + packages/connect/src/styles.ts | 9 ++++++++ 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx index 4fc649efc..1e21f9128 100644 --- a/examples/react/src/App.tsx +++ b/examples/react/src/App.tsx @@ -1,4 +1,4 @@ -import { SequenceConnect } from '@0xsequence/connect' +import { SequenceConnect, useResolvedConnectConfig } from '@0xsequence/connect' import { SequenceWalletProvider } from '@0xsequence/wallet-widget' import { useCallback, useMemo, useState } from 'react' import { BrowserRouter, Route, Routes } from 'react-router-dom' @@ -18,7 +18,24 @@ export const App = () => { setWalletUrl(sanitizedUrl) }, []) - const config = useMemo(() => createExampleConfig(walletUrl), [walletUrl]) + const baseConfig = useMemo(() => createExampleConfig(walletUrl), [walletUrl]) + const { resolvedConfig: resolvedConnectConfig, walletConfigurationSignIn } = useResolvedConnectConfig(baseConfig.connectConfig) + + const config = useMemo(() => { + const baseSignIn = baseConfig.connectConfig.signIn ?? {} + const resolvedSignIn = walletConfigurationSignIn ?? {} + return { + ...baseConfig, + connectConfig: { + ...resolvedConnectConfig, + signIn: { + ...baseSignIn, + projectName: resolvedSignIn.projectName ?? baseSignIn.projectName, + logoUrl: resolvedSignIn.logoUrl ?? baseSignIn.logoUrl + } + } + } + }, [baseConfig, resolvedConnectConfig, walletConfigurationSignIn]) return ( diff --git a/packages/connect/src/components/Connect/Connect.tsx b/packages/connect/src/components/Connect/Connect.tsx index 10881ab92..8a15da745 100644 --- a/packages/connect/src/components/Connect/Connect.tsx +++ b/packages/connect/src/components/Connect/Connect.tsx @@ -498,9 +498,9 @@ export const Connect = (props: ConnectProps) => { // For v3 connectors: show ecosystem if logged in (from status.js check), otherwise show regular v3 connectors // Only apply this filtering if we have v3 connectors and auth status has been checked const visibleV3ConnectorIds = useMemo(() => { - // Wait for auth status to avoid swapping connector lists after the view is shown + // While loading, show regular v3 socials to keep layout stable if (isAuthStatusLoading || isV3WalletSignedIn === null) { - return new Set() + return new Set(regularV3Connectors.map(c => c.uid)) } // Only apply auth-based filtering if we have v3 connectors @@ -516,8 +516,8 @@ export const Connect = (props: ConnectProps) => { // Not logged in (status.js returned authState !== 'signed-in'): show regular v3 connectors (not ecosystem) return new Set(regularV3Connectors.map(c => c.uid)) } - // Fallback: default to no v3 connectors if state is unknown - return new Set() + // Fallback: default to showing standard v3 socials to avoid empty space + return new Set(regularV3Connectors.map(c => c.uid)) }, [isAuthStatusLoading, isV3WalletSignedIn, ecosystemConnector, regularV3Connectors, sequenceConnectors.length]) const socialAuthConnectors = extendedConnectors diff --git a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx index db13ca1a1..aff31ff2f 100644 --- a/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx +++ b/packages/connect/src/components/SequenceConnectInlineProvider/SequenceConnectInlineProvider.tsx @@ -250,7 +250,7 @@ export const SequenceConnectInlineProvider = (props: SequenceConnectInlineProvid
- {isWalletConfigLoading ? ( + {isWalletConfigLoading || isAuthStatusLoading ? (
diff --git a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx index cc5ab0149..f7acdfeae 100644 --- a/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx +++ b/packages/connect/src/components/SequenceConnectProvider/SequenceConnectProvider.tsx @@ -245,7 +245,7 @@ export const SequenceConnectProvider = (props: SequenceConnectProviderProps) => }} onClose={() => setOpenConnectModal(false)} > - {isWalletConfigLoading ? ( + {isWalletConfigLoading || isAuthStatusLoading ? (
diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index c354de01d..411dd0d37 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -103,6 +103,7 @@ export { export { useOpenConnectModal } from './hooks/useOpenConnectModal.js' export { useTheme } from './hooks/useTheme.js' export { useWalletSettings } from './hooks/useWalletSettings.js' +export { useResolvedConnectConfig } from './hooks/useResolvedConnectConfig.js' export { useSignInEmail } from './hooks/useSignInEmail.js' export { useProjectAccessKey } from './hooks/useProjectAccessKey.js' diff --git a/packages/connect/src/styles.ts b/packages/connect/src/styles.ts index 73a764e4c..68333827b 100644 --- a/packages/connect/src/styles.ts +++ b/packages/connect/src/styles.ts @@ -44,6 +44,7 @@ export const styles = String.raw` --radius-2xl: 1rem; --ease-out: cubic-bezier(0, 0, 0.2, 1); --animate-spin: spin 1s linear infinite; + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --blur-xs: 4px; --blur-md: 12px; --default-transition-duration: 150ms; @@ -671,6 +672,9 @@ export const styles = String.raw` .transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); } + .animate-pulse { + animation: var(--animate-pulse); + } .animate-skeleton { animation: skeleton 1s ease infinite; } @@ -2398,6 +2402,11 @@ export const styles = String.raw` transform: rotate(360deg); } } +@keyframes pulse { + 50% { + opacity: 0.5; + } +} @keyframes skeleton { 0% { background-position: 0% 50%; From 8663eecdb5d69f40fd0121e00bfddcaabb0955dd Mon Sep 17 00:00:00 2001 From: Tolgahan Arikan Date: Wed, 17 Dec 2025 15:54:25 +0300 Subject: [PATCH 10/10] Improve code quality: add constants, wagmi guards, and documentation --- packages/connect/src/constants.ts | 33 +++++++ packages/connect/src/hooks/useAuthStatus.ts | 8 +- packages/connect/src/hooks/useWallets.ts | 3 +- packages/connect/src/utils/checkAuthStatus.ts | 87 +++++++++++-------- packages/connect/src/utils/setChains.ts | 58 ++++++++++--- 5 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 packages/connect/src/constants.ts diff --git a/packages/connect/src/constants.ts b/packages/connect/src/constants.ts new file mode 100644 index 000000000..a1b8af738 --- /dev/null +++ b/packages/connect/src/constants.ts @@ -0,0 +1,33 @@ +/** + * Connect SDK constants + * + * This file contains magic numbers and configuration values used throughout the SDK. + * Centralizing these values makes them easier to find, understand, and tune. + */ + +/** + * Debounce delay in milliseconds for the wallet list updates. + * + * This delay helps prevent UI flicker when wagmi is reconnecting or when + * wallet connections are rapidly changing. A value of 120ms balances + * responsiveness with stability - it's short enough to feel instant but + * long enough to avoid flickering during transient connection states. + */ +export const WALLET_LIST_DEBOUNCE_MS = 120 + +/** + * Timeout in milliseconds for the auth status JSONP request. + * + * If the JSONP callback is not invoked within this time, we assume + * the user is not authenticated. 5 seconds is generous enough to handle + * slow networks while not leaving users waiting indefinitely. + */ +export const AUTH_STATUS_TIMEOUT_MS = 5000 + +/** + * Minimum supported wagmi version for internal API access. + * + * The setChains utility accesses wagmi's internal API (`config._internal`). + * This constant documents which version the implementation was tested against. + */ +export const WAGMI_MIN_TESTED_VERSION = '2.0.0' diff --git a/packages/connect/src/hooks/useAuthStatus.ts b/packages/connect/src/hooks/useAuthStatus.ts index 33cf1b89b..f7f8c8b70 100644 --- a/packages/connect/src/hooks/useAuthStatus.ts +++ b/packages/connect/src/hooks/useAuthStatus.ts @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { checkAuthStatus } from '../utils/checkAuthStatus.js' @@ -66,7 +66,7 @@ export const useAuthStatus = (walletUrl: string | undefined | null, options: Use const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) - const checkStatus = async () => { + const checkStatus = useCallback(async () => { if (!walletUrl || !enabled) { setIsV3WalletSignedIn(null) setIsLoading(false) @@ -87,7 +87,7 @@ export const useAuthStatus = (walletUrl: string | undefined | null, options: Use } finally { setIsLoading(false) } - } + }, [walletUrl, enabled]) useEffect(() => { if (!enabled || !walletUrl) { @@ -106,7 +106,7 @@ export const useAuthStatus = (walletUrl: string | undefined | null, options: Use clearInterval(intervalId) } } - }, [walletUrl, enabled, refetchInterval]) + }, [checkStatus, refetchInterval]) return { isV3WalletSignedIn, diff --git a/packages/connect/src/hooks/useWallets.ts b/packages/connect/src/hooks/useWallets.ts index 638a9caa4..8b71e3022 100644 --- a/packages/connect/src/hooks/useWallets.ts +++ b/packages/connect/src/hooks/useWallets.ts @@ -5,6 +5,7 @@ import { useAPIClient } from '@0xsequence/hooks' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useAccount, useConnect, useConnections, useDisconnect, type Connector, type UseConnectionsReturnType } from 'wagmi' +import { WALLET_LIST_DEBOUNCE_MS } from '../constants.js' import { useOptionalConnectConfigContext } from '../contexts/ConnectConfig.js' import type { ExtendedConnector } from '../types.js' import { getCachedProjectName, normalizeWalletUrl } from '../utils/walletConfiguration.js' @@ -384,7 +385,7 @@ export const useWallets = (): UseWalletsReturnType => { const timer = setTimeout(() => { setStableWallets(prev => (areWalletListsEqual(prev, nextList) ? prev : nextList)) - }, 120) + }, WALLET_LIST_DEBOUNCE_MS) return () => { clearTimeout(timer) diff --git a/packages/connect/src/utils/checkAuthStatus.ts b/packages/connect/src/utils/checkAuthStatus.ts index f0defe3b6..03519f6f1 100644 --- a/packages/connect/src/utils/checkAuthStatus.ts +++ b/packages/connect/src/utils/checkAuthStatus.ts @@ -1,3 +1,5 @@ +import { AUTH_STATUS_TIMEOUT_MS } from '../constants.js' + import { normalizeWalletUrl } from './walletConfiguration.js' type AuthStatusData = { @@ -6,9 +8,40 @@ type AuthStatusData = { } /** - * Checks if the user is logged in by calling the auth status endpoint - * The endpoint uses JSONP pattern - it returns JavaScript that calls a callback function - * @param walletUrl - The wallet URL to check auth status against + * Checks if the user is logged in by calling the auth status endpoint. + * + * ## Why JSONP instead of fetch? + * + * This function uses JSONP (JSON with Padding) instead of a standard `fetch` request for + * important cross-origin cookie access reasons: + * + * 1. **Third-party cookie restrictions**: Modern browsers (especially Safari) heavily restrict + * third-party cookie access. When using `fetch` with `credentials: 'include'`, the browser + * may block cookies from being sent to a different origin (e.g., wallet.sequence.app from + * your dapp's domain). + * + * 2. **JSONP bypasses cookie restrictions**: JSONP works by dynamically inserting a `