Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions packages/web/src/app/AppProviders.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -10,17 +10,18 @@ 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'
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
}
Expand Down Expand Up @@ -61,17 +62,19 @@ export const AppProviders = ({ children }: AppProvidersProps) => {
}, [basename])

return (
<WagmiProvider config={wagmiAdapter.wagmiConfig}>
<QueryClientProvider client={queryClient}>
<MediaProvider>
<ReduxProvider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RouterProvider router={router} />
</PersistGate>
</ReduxProvider>
</MediaProvider>
<ReactQueryDevtools />
</QueryClientProvider>
</WagmiProvider>
<Suspense fallback={null}>
<ReownProvider>
<QueryClientProvider client={queryClient}>
<MediaProvider>
<ReduxProvider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RouterProvider router={router} />
</PersistGate>
</ReduxProvider>
</MediaProvider>
<ReactQueryDevtools />
</QueryClientProvider>
</ReownProvider>
</Suspense>
)
}
113 changes: 71 additions & 42 deletions packages/web/src/app/ReownAppKitModal.tsx
Original file line number Diff line number Diff line change
@@ -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)
}
})
33 changes: 33 additions & 0 deletions packages/web/src/app/ReownProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<any>(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 <WagmiProvider config={wagmiConfig}>{children}</WagmiProvider>
}

88 changes: 88 additions & 0 deletions packages/web/src/app/reownConfig.ts
Original file line number Diff line number Diff line change
@@ -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<any> | 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
}

26 changes: 26 additions & 0 deletions packages/web/src/app/reownSyncInit.ts
Original file line number Diff line number Diff line change
@@ -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
}

7 changes: 6 additions & 1 deletion packages/web/src/pages/modals/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/services/audius-sdk/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AudiusWalletClient> => {
// Check if the user has already connected Hedgehog first...
Expand All @@ -33,6 +37,7 @@ export const getAudiusWalletClient = async (): Promise<AudiusWalletClient> => {
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'
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/store/sign-out/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -24,6 +25,7 @@ function* watchSignOut() {
yield takeLatest(
signOutAction.type,
function* (action: ReturnType<typeof signOutAction>) {
const wagmiConfig = getWagmiConfig()
if (wagmiConfig.state.status === 'connected') {
yield call(disconnect, wagmiConfig)
}
Expand Down