diff --git a/packages/web/src/app/AppProviders.tsx b/packages/web/src/app/AppProviders.tsx
index 923bd42eccb..e175f063359 100644
--- a/packages/web/src/app/AppProviders.tsx
+++ b/packages/web/src/app/AppProviders.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, useState, useMemo } from 'react'
+import { ReactNode, useState, useMemo, lazy, Suspense } from 'react'
import { MediaProvider } from '@audius/harmony/src/contexts'
import { QueryClientProvider } from '@tanstack/react-query'
@@ -10,7 +10,6 @@ import {
RouterProvider
} from 'react-router'
import { PersistGate } from 'redux-persist/integration/react'
-import { WagmiProvider } from 'wagmi'
import { useIsMobile } from 'hooks/useIsMobile'
import { env } from 'services/env'
@@ -18,9 +17,11 @@ import { queryClient } from 'services/query-client'
import { configureStore } from 'store/configureStore'
import { getSystemAppearance, getTheme } from 'utils/theme/theme'
-import { wagmiAdapter } from './ReownAppKitModal'
import { createRoutes } from './routes'
+// Lazy load ReownProvider to avoid loading @reown packages until needed
+const ReownProvider = lazy(() => import('./ReownProvider').then(module => ({ default: module.ReownProvider })))
+
type AppProvidersProps = {
children?: ReactNode
}
@@ -61,17 +62,19 @@ export const AppProviders = ({ children }: AppProvidersProps) => {
}, [basename])
return (
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/packages/web/src/app/ReownAppKitModal.tsx b/packages/web/src/app/ReownAppKitModal.tsx
index 9aa5088c4b9..5d9d9fbb33f 100644
--- a/packages/web/src/app/ReownAppKitModal.tsx
+++ b/packages/web/src/app/ReownAppKitModal.tsx
@@ -1,50 +1,79 @@
-import {
- mainnet,
- solana,
- type AppKitNetwork,
- type Chain
-} from '@reown/appkit/networks'
-import { createAppKit } from '@reown/appkit/react'
-import { SolanaAdapter } from '@reown/appkit-adapter-solana/react'
-import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
+// Re-export from lazy-loaded config for backward compatibility
+// All new code should import from 'app/reownConfig' instead
+// Note: We import audiusChain directly to avoid pulling in @reown packages
+import { audiusChain } from './reownConfig'
+import { getInitializedConfig } from './reownSyncInit'
-import { env } from 'services/env'
-import zIndex from 'utils/zIndex'
+export { audiusChain }
-// Audius ACDC chain (now ports to Core)
-export const audiusChain = {
- id: env.AUDIUS_NETWORK_CHAIN_ID,
- name: 'Audius',
- nativeCurrency: { name: '-', symbol: '-', decimals: 18 },
- rpcUrls: {
- default: { http: [`${env.API_URL}/core/erpc`] }
- }
-} as const satisfies Chain
+// Dynamic import to avoid pulling in @reown packages at module load time
+const getReownConfigDynamic = async () => {
+ const module = await import('./reownConfig')
+ return module.getReownConfig()
+}
+
+// Lazy getters for backward compatibility (async version)
+export const getWagmiAdapter = async () => {
+ const config = await getReownConfigDynamic()
+ return config.wagmiAdapter
+}
+
+export const getAppkitModal = async () => {
+ const config = await getReownConfigDynamic()
+ return config.appkitModal
+}
-const projectId = env.REOWN_PROJECT_ID
-const networks: [AppKitNetwork, ...AppKitNetwork[]] = [
- mainnet,
- solana,
- audiusChain
-]
-export const wagmiAdapter = new WagmiAdapter({
- networks,
- projectId
+export const getReownConfig = getReownConfigDynamic
+
+// For backward compatibility - synchronous access after ReownProvider initializes
+// These will throw if accessed before ReownProvider mounts
+export const wagmiAdapter = new Proxy({} as any, {
+ get(_target, prop) {
+ const config = getInitializedConfig()
+ return config.wagmiAdapter[prop]
+ },
+ has(_target, prop) {
+ try {
+ const config = getInitializedConfig()
+ return prop in config.wagmiAdapter
+ } catch {
+ return false
+ }
+ },
+ ownKeys(_target) {
+ const config = getInitializedConfig()
+ return Reflect.ownKeys(config.wagmiAdapter)
+ },
+ getOwnPropertyDescriptor(_target, prop) {
+ const config = getInitializedConfig()
+ return Reflect.getOwnPropertyDescriptor(config.wagmiAdapter, prop)
+ }
})
-const solanaAdapter = new SolanaAdapter()
-export const appkitModal = createAppKit({
- adapters: [wagmiAdapter, solanaAdapter],
- networks,
- projectId,
- themeVariables: {
- '--w3m-z-index': zIndex.REOWN_APPKIT_MODAL // above ConnectWalletModal
+// Proxy for appkitModal - maintains synchronous API after initialization
+export const appkitModal = new Proxy({} as any, {
+ get(_target, prop) {
+ const config = getInitializedConfig()
+ const value = config.appkitModal[prop as keyof typeof config.appkitModal]
+ if (typeof value === 'function') {
+ return value.bind(config.appkitModal)
+ }
+ return value
+ },
+ has(_target, prop) {
+ try {
+ const config = getInitializedConfig()
+ return prop in config.appkitModal
+ } catch {
+ return false
+ }
+ },
+ ownKeys(_target) {
+ const config = getInitializedConfig()
+ return Reflect.ownKeys(config.appkitModal)
},
- features: {
- send: false,
- swaps: false,
- onramp: false,
- socials: false,
- email: false
+ getOwnPropertyDescriptor(_target, prop) {
+ const config = getInitializedConfig()
+ return Reflect.getOwnPropertyDescriptor(config.appkitModal, prop)
}
})
diff --git a/packages/web/src/app/ReownProvider.tsx b/packages/web/src/app/ReownProvider.tsx
new file mode 100644
index 00000000000..b32e9e7b880
--- /dev/null
+++ b/packages/web/src/app/ReownProvider.tsx
@@ -0,0 +1,33 @@
+import { ReactNode, useEffect, useState } from 'react'
+import { WagmiProvider } from 'wagmi'
+
+import { getReownConfig } from './reownConfig'
+import { initializeReownSync } from './reownSyncInit'
+
+type ReownProviderProps = {
+ children: ReactNode
+}
+
+/**
+ * Lazy-loaded provider that initializes Reown AppKit and wraps children with WagmiProvider.
+ * This ensures @reown packages are only loaded when this component is rendered.
+ */
+export const ReownProvider = ({ children }: ReownProviderProps) => {
+ const [wagmiConfig, setWagmiConfig] = useState(null)
+
+ useEffect(() => {
+ // Eagerly initialize Reown config when provider mounts
+ // This ensures appkitModal and wagmiAdapter are available synchronously
+ getReownConfig().then((config) => {
+ initializeReownSync(config)
+ setWagmiConfig(config.wagmiAdapter.wagmiConfig)
+ })
+ }, [])
+
+ if (!wagmiConfig) {
+ return null
+ }
+
+ return {children}
+}
+
diff --git a/packages/web/src/app/reownConfig.ts b/packages/web/src/app/reownConfig.ts
new file mode 100644
index 00000000000..cc505075d5c
--- /dev/null
+++ b/packages/web/src/app/reownConfig.ts
@@ -0,0 +1,88 @@
+import { env } from 'services/env'
+import zIndex from 'utils/zIndex'
+
+// Audius ACDC chain (now ports to Core)
+// This is defined here to avoid importing Chain type from @reown
+export const audiusChain = {
+ id: env.AUDIUS_NETWORK_CHAIN_ID,
+ name: 'Audius',
+ nativeCurrency: { name: '-', symbol: '-', decimals: 18 },
+ rpcUrls: {
+ default: { http: [`${env.API_URL}/core/erpc`] }
+ }
+} as const
+
+let wagmiAdapterInstance: any | null = null
+let appkitModalInstance: any | null = null
+let configPromise: Promise | null = null
+
+/**
+ * Lazy initialization function for Reown AppKit.
+ * Uses dynamic imports to ensure @reown packages are only loaded when this function is called.
+ */
+export const getReownConfig = async () => {
+ if (wagmiAdapterInstance && appkitModalInstance) {
+ return {
+ wagmiAdapter: wagmiAdapterInstance,
+ appkitModal: appkitModalInstance,
+ audiusChain
+ }
+ }
+
+ // If already initializing, wait for that promise
+ if (configPromise) {
+ return configPromise
+ }
+
+ // Use dynamic imports to lazy-load @reown packages
+ console.log('[Reown] Lazy loading @reown packages...')
+ configPromise = Promise.all([
+ import('@reown/appkit/networks'),
+ import('@reown/appkit/react'),
+ import('@reown/appkit-adapter-solana/react'),
+ import('@reown/appkit-adapter-wagmi')
+ ]).then(
+ ([
+ { mainnet, solana },
+ { createAppKit },
+ { SolanaAdapter },
+ { WagmiAdapter }
+ ]) => {
+ const projectId = env.REOWN_PROJECT_ID
+ const networks = [mainnet, solana, audiusChain]
+
+ wagmiAdapterInstance = new WagmiAdapter({
+ networks,
+ projectId
+ })
+
+ const solanaAdapter = new SolanaAdapter()
+
+ appkitModalInstance = createAppKit({
+ adapters: [wagmiAdapterInstance, solanaAdapter],
+ networks,
+ projectId,
+ themeVariables: {
+ '--w3m-z-index': zIndex.REOWN_APPKIT_MODAL // above ConnectWalletModal
+ },
+ features: {
+ send: false,
+ swaps: false,
+ onramp: false,
+ socials: false,
+ email: false
+ }
+ })
+
+ console.log('[Reown] @reown packages loaded successfully')
+ return {
+ wagmiAdapter: wagmiAdapterInstance,
+ appkitModal: appkitModalInstance,
+ audiusChain
+ }
+ }
+ )
+
+ return configPromise
+}
+
diff --git a/packages/web/src/app/reownSyncInit.ts b/packages/web/src/app/reownSyncInit.ts
new file mode 100644
index 00000000000..0c242019f94
--- /dev/null
+++ b/packages/web/src/app/reownSyncInit.ts
@@ -0,0 +1,26 @@
+// This module provides synchronous access to Reown config after it's been initialized
+// It's used to bridge the async initialization with the synchronous API
+
+let initializedConfig: {
+ wagmiAdapter: any
+ appkitModal: any
+ audiusChain: any
+} | null = null
+
+export const initializeReownSync = (config: {
+ wagmiAdapter: any
+ appkitModal: any
+ audiusChain: any
+}) => {
+ initializedConfig = config
+}
+
+export const getInitializedConfig = () => {
+ if (!initializedConfig) {
+ throw new Error(
+ 'Reown config not initialized. Make sure ReownProvider is mounted.'
+ )
+ }
+ return initializedConfig
+}
+
diff --git a/packages/web/src/pages/modals/Modals.tsx b/packages/web/src/pages/modals/Modals.tsx
index 434652b1276..c24e0a1a1f6 100644
--- a/packages/web/src/pages/modals/Modals.tsx
+++ b/packages/web/src/pages/modals/Modals.tsx
@@ -9,7 +9,12 @@ import { AlbumTrackRemoveConfirmationModal } from 'components/album-track-remove
import AppCTAModal from 'components/app-cta-modal/AppCTAModal'
import { ArtistPickModal } from 'components/artist-pick-modal/ArtistPickModal'
import BrowserPushConfirmationModal from 'components/browser-push-confirmation-modal/BrowserPushConfirmationModal'
-import { BuySellModal } from 'components/buy-sell-modal/BuySellModal'
+// Lazy load BuySellModal to avoid loading @reown packages until needed
+const BuySellModal = lazy(() =>
+ import('components/buy-sell-modal/BuySellModal').then((m) => ({
+ default: m.BuySellModal
+ }))
+)
import CoinflowOnrampModal from 'components/coinflow-onramp-modal'
import ConfirmerPreview from 'components/confirmer-preview/ConfirmerPreview'
import DeletePlaylistConfirmationModal from 'components/delete-playlist-confirmation-modal/DeletePlaylistConfirmationModal'
diff --git a/packages/web/src/services/audius-sdk/auth.ts b/packages/web/src/services/audius-sdk/auth.ts
index 1c3c9f965e1..9aeaa702098 100644
--- a/packages/web/src/services/audius-sdk/auth.ts
+++ b/packages/web/src/services/audius-sdk/auth.ts
@@ -9,12 +9,16 @@ import {
import { getWalletClient } from '@wagmi/core'
import { type WalletClient } from 'viem'
-import { audiusChain, wagmiAdapter } from 'app/ReownAppKitModal'
+import { audiusChain, getWagmiAdapter } from 'app/ReownAppKitModal'
import { env } from '../env'
import { localStorage } from '../local-storage'
-const wagmiConfig = wagmiAdapter.wagmiConfig
+// Lazy-load wagmiConfig when needed (async)
+const getWagmiConfig = async () => {
+ const adapter = await getWagmiAdapter()
+ return adapter.wagmiConfig
+}
export const getAudiusWalletClient = async (): Promise => {
// Check if the user has already connected Hedgehog first...
@@ -33,6 +37,7 @@ export const getAudiusWalletClient = async (): Promise => {
console.debug('[audiusSdk] Initializing SDK with external wallet...')
// Wait for the wallet to finish connecting/reconnecting
+ const wagmiConfig = await getWagmiConfig()
if (
wagmiConfig.state.status === 'reconnecting' ||
wagmiConfig.state.status === 'connecting'
diff --git a/packages/web/src/store/sign-out/sagas.ts b/packages/web/src/store/sign-out/sagas.ts
index 4cb39c0ef35..c704068b3dd 100644
--- a/packages/web/src/store/sign-out/sagas.ts
+++ b/packages/web/src/store/sign-out/sagas.ts
@@ -15,7 +15,8 @@ import { push } from 'utils/navigation'
const { resetAccount, unsubscribeBrowserPushNotifications } = accountActions
const { signOut: signOutAction } = signOutActions
-const wagmiConfig = wagmiAdapter.wagmiConfig
+// Use synchronous accessor - by the time saga runs, ReownProvider should be initialized
+const getWagmiConfig = () => wagmiAdapter.wagmiConfig
function* watchSignOut() {
const localStorage = yield* getContext('localStorage')
@@ -24,6 +25,7 @@ function* watchSignOut() {
yield takeLatest(
signOutAction.type,
function* (action: ReturnType) {
+ const wagmiConfig = getWagmiConfig()
if (wagmiConfig.state.status === 'connected') {
yield call(disconnect, wagmiConfig)
}