Skip to content

Commit 5fad554

Browse files
committed
add configurable Ethereum network support with validation
Add ETHEREUM_NETWORK environment variable Support mainnet, sepolia, arbitrum, polygon, base, optimism and celo Signed-off-by: p4u <pau@dabax.net>
1 parent 65a4aaa commit 5fad554

10 files changed

Lines changed: 325 additions & 18 deletions

.env.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ WALLETCONNECT_PROJECT_ID=your_project_id_here
22
SEQUENCER_URL=https://sequencer1.davinci.vote
33
BIGQUERY_URL=https://c3.davinci.vote
44
PUBLIC_MAILCHIMP_URL=your_public_mailchimp_url_here
5-
SHARE_TEXT="A custom text to be shared, use {title/link/app} to insert the title, process and app links"
5+
SHARE_TEXT="A custom text to be shared, use {title/link/app} to insert the title, process and app links"
6+
7+
# Ethereum Network Configuration
8+
# Supported networks: mainnet, sepolia, arbitrum, polygon, base, optimism, celo
9+
# Defaults to sepolia if not specified
10+
ETHEREUM_NETWORK=sepolia

src/Layout.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { sepolia } from '@reown/appkit/networks'
21
import { useAppKitNetwork } from '@reown/appkit/react'
32
import { useEffect, useState } from 'react'
43
import { Outlet, ScrollRestoration } from 'react-router-dom'
54
import { FloatingHeader } from '~components/floating-header'
65
import { Footer } from '~components/footer'
76
import { useMiniApp } from '~contexts/MiniAppContext'
87
import { initializeAppKit } from '~lib/appkit-miniapp'
8+
import { getConfiguredNetwork } from '~lib/network-config'
99

1010
export function Layout() {
1111
const { caipNetwork, switchNetwork } = useAppKitNetwork()
@@ -30,16 +30,18 @@ export function Layout() {
3030
initialize()
3131
}, [isInitialized])
3232

33-
// Switch to configured chain if not connected to Sepolia
33+
// Switch to configured network if not already connected to it
3434
useEffect(() => {
3535
if (!caipNetwork || !appKitInitialized) return
3636

37-
// Check if we're on Sepolia (chain ID 11155111)
38-
if (caipNetwork.id !== sepolia.id) {
37+
const configuredNetwork = getConfiguredNetwork()
38+
39+
// Check if we're on the configured network
40+
if (caipNetwork.id !== configuredNetwork.id) {
3941
try {
40-
switchNetwork(sepolia)
42+
switchNetwork(configuredNetwork)
4143
} catch (error) {
42-
console.error('Failed to switch chain:', error)
44+
console.error('Failed to switch to configured network:', error)
4345
}
4446
}
4547
}, [caipNetwork, switchNetwork, appKitInitialized])
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { AlertTriangle, Network } from 'lucide-react'
2+
import { Alert, AlertDescription, AlertTitle } from '~components/ui/alert'
3+
import { Button } from '~components/ui/button'
4+
import { Spinner } from '~components/ui/spinner'
5+
import { useNetworkValidation } from '~hooks/use-network-validation'
6+
import { useUnifiedWallet } from '~hooks/use-unified-wallet'
7+
8+
/**
9+
* Banner component that displays network validation status and allows switching networks
10+
* Shows when wallet is connected to wrong network
11+
*/
12+
export function NetworkValidationBanner() {
13+
const { isConnected } = useUnifiedWallet()
14+
const {
15+
isCorrectNetwork,
16+
isValidating,
17+
isSwitching,
18+
error,
19+
requiredNetworkName,
20+
currentChainId,
21+
switchToCorrectNetwork,
22+
} = useNetworkValidation()
23+
24+
// Don't show if not connected
25+
if (!isConnected) {
26+
return null
27+
}
28+
29+
// Don't show if validating
30+
if (isValidating) {
31+
return null
32+
}
33+
34+
// Don't show if on correct network
35+
if (isCorrectNetwork) {
36+
return null
37+
}
38+
39+
return (
40+
<Alert variant='destructive' className='border-orange-200 bg-orange-50'>
41+
<AlertTriangle className='h-4 w-4 text-orange-600' />
42+
<AlertTitle className='text-orange-800'>Wrong Network</AlertTitle>
43+
<AlertDescription className='text-orange-700'>
44+
<div className='space-y-3'>
45+
<p>
46+
Your wallet is connected to the wrong network. This vote requires the <strong>{requiredNetworkName}</strong>{' '}
47+
network.
48+
{currentChainId && (
49+
<>
50+
{' '}
51+
You are currently on chain ID <strong>{currentChainId}</strong>.
52+
</>
53+
)}
54+
</p>
55+
56+
{error && (
57+
<div className='bg-red-100 p-3 rounded border border-red-200'>
58+
<p className='text-sm text-red-800'>{error.message}</p>
59+
</div>
60+
)}
61+
62+
<Button
63+
onClick={switchToCorrectNetwork}
64+
disabled={isSwitching}
65+
className='bg-orange-600 hover:bg-orange-700 text-white'
66+
size='sm'
67+
>
68+
{isSwitching ? (
69+
<>
70+
<Spinner className='mr-2' />
71+
Switching Network...
72+
</>
73+
) : (
74+
<>
75+
<Network className='w-4 h-4 mr-2' />
76+
Switch to {requiredNetworkName}
77+
</>
78+
)}
79+
</Button>
80+
81+
<p className='text-xs text-orange-600'>
82+
<strong>Note:</strong> Some wallets may not support automatic network switching. If the switch fails, please
83+
add and select {requiredNetworkName} manually in your wallet.
84+
</p>
85+
</div>
86+
</AlertDescription>
87+
</Alert>
88+
)
89+
}

src/components/vote-display.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { useProcessQuery } from '~hooks/use-process-query'
4141
import { useUnifiedProvider } from '~hooks/use-unified-provider'
4242
import { useUnifiedWallet } from '~hooks/use-unified-wallet'
4343
import { truncateAddress } from '~lib/web3-utils'
44+
import { NetworkValidationBanner } from './network-validation-banner'
4445
import { useProcess } from './process-context'
4546
import RelativeTimeRemaining from './relative-time-remaining'
4647
import ConnectWalletButtonMiniApp from './ui/connect-wallet-button-miniapp'
@@ -614,6 +615,9 @@ export function VoteDisplay() {
614615
<CardContent className='pt-6 bg-davinci-text-base space-y-6'>
615616
<WalletEligibilityStatus />
616617

618+
{/* Network Validation Banner */}
619+
<NetworkValidationBanner />
620+
617621
{/* Vote Progress Tracker */}
618622
{voteId && <VoteProgressTracker onVoteAgain={handleVoteAgain} processId={process.id} voteId={voteId} />}
619623

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { useAppKitNetwork } from '@reown/appkit/react'
2+
import { useEffect, useState } from 'react'
3+
import {
4+
getConfiguredChainId,
5+
getConfiguredNetwork,
6+
getConfiguredNetworkName,
7+
isCorrectNetwork,
8+
} from '~lib/network-config'
9+
import { useUnifiedWallet } from './use-unified-wallet'
10+
11+
export interface NetworkValidationState {
12+
isCorrectNetwork: boolean
13+
isValidating: boolean
14+
isSwitching: boolean
15+
error: Error | null
16+
requiredChainId: number
17+
requiredNetworkName: string
18+
currentChainId: number | undefined
19+
switchToCorrectNetwork: () => Promise<void>
20+
}
21+
22+
/**
23+
* Hook to validate and manage network switching for the connected wallet
24+
* Ensures the wallet is connected to the correct network as configured
25+
*/
26+
export function useNetworkValidation(): NetworkValidationState {
27+
const { isConnected } = useUnifiedWallet()
28+
const { chainId, switchNetwork } = useAppKitNetwork()
29+
const [isValidating, setIsValidating] = useState(false)
30+
const [isSwitching, setIsSwitching] = useState(false)
31+
const [error, setError] = useState<Error | null>(null)
32+
33+
const requiredChainId = getConfiguredChainId()
34+
const requiredNetworkName = getConfiguredNetworkName()
35+
const currentChainId = chainId ? Number(chainId) : undefined
36+
const isOnCorrectNetwork = currentChainId ? isCorrectNetwork(currentChainId) : false
37+
38+
// Validate network when connection state changes
39+
useEffect(() => {
40+
if (isConnected && currentChainId) {
41+
setIsValidating(true)
42+
const isValid = isCorrectNetwork(currentChainId)
43+
setIsValidating(false)
44+
45+
if (!isValid) {
46+
console.warn(
47+
`Wallet connected to wrong network. Expected: ${requiredNetworkName} (${requiredChainId}), Got: ${currentChainId}`
48+
)
49+
}
50+
}
51+
}, [isConnected, currentChainId, requiredChainId, requiredNetworkName])
52+
53+
/**
54+
* Switch the wallet to the correct network
55+
*/
56+
const switchToCorrectNetwork = async () => {
57+
if (!isConnected) {
58+
throw new Error('Wallet not connected')
59+
}
60+
61+
if (isOnCorrectNetwork) {
62+
return // Already on correct network
63+
}
64+
65+
setIsSwitching(true)
66+
setError(null)
67+
68+
try {
69+
// Get the configured network object
70+
const network = getConfiguredNetwork()
71+
72+
// Use AppKit's switchNetwork function
73+
await switchNetwork(network)
74+
75+
console.info(`Successfully switched to ${requiredNetworkName}`)
76+
} catch (err) {
77+
const error = err as Error
78+
console.error('Failed to switch network:', error)
79+
80+
// Provide user-friendly error messages
81+
if (error.message?.includes('rejected') || error.message?.includes('denied')) {
82+
setError(new Error('Network switch was rejected. Please try again.'))
83+
} else if (error.message?.includes('not supported')) {
84+
setError(
85+
new Error(
86+
`Your wallet doesn't support ${requiredNetworkName}. Please add it manually or use a different wallet.`
87+
)
88+
)
89+
} else {
90+
setError(new Error(`Failed to switch network: ${error.message}`))
91+
}
92+
93+
throw error
94+
} finally {
95+
setIsSwitching(false)
96+
}
97+
}
98+
99+
return {
100+
isCorrectNetwork: isOnCorrectNetwork,
101+
isValidating,
102+
isSwitching,
103+
error,
104+
requiredChainId,
105+
requiredNetworkName,
106+
currentChainId,
107+
switchToCorrectNetwork,
108+
}
109+
}

src/lib/appkit-miniapp.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EthersAdapter } from '@reown/appkit-adapter-ethers'
2-
import { sepolia } from '@reown/appkit/networks'
32
import { AppKit, createAppKit } from '@reown/appkit/react'
3+
import { getConfiguredNetwork } from './network-config'
44

55
// 1. Get projectId from environment
66
const projectId = import.meta.env.WALLETCONNECT_PROJECT_ID
@@ -13,7 +13,10 @@ const metadata = {
1313
icons: ['/images/davinci-icon-small.png'],
1414
}
1515

16-
// 3. Create Ethers Adapter with conditional provider
16+
// 3. Get configured network
17+
const network = getConfiguredNetwork()
18+
19+
// 4. Create Ethers Adapter with conditional provider
1720
const ethersAdapter = new EthersAdapter()
1821

1922
let appKit: AppKit | null = null
@@ -31,7 +34,7 @@ export async function initializeAppKit() {
3134
// Create standard AppKit (works for both mini app and regular web)
3235
appKit = createAppKit({
3336
adapters: [ethersAdapter],
34-
networks: [sepolia],
37+
networks: [network],
3538
projectId,
3639
metadata,
3740
features: {

src/lib/appkit.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EthersAdapter } from '@reown/appkit-adapter-ethers'
2-
import { sepolia } from '@reown/appkit/networks'
32
import { createAppKit } from '@reown/appkit/react'
3+
import { getConfiguredNetwork } from './network-config'
44

55
// 1. Get projectId from environment
66
const projectId = import.meta.env.WALLETCONNECT_PROJECT_ID
@@ -13,13 +13,16 @@ const metadata = {
1313
icons: ['/images/davinci-icon-small.png'],
1414
}
1515

16-
// 3. Create Ethers Adapter
16+
// 3. Get configured network
17+
const network = getConfiguredNetwork()
18+
19+
// 4. Create Ethers Adapter
1720
const ethersAdapter = new EthersAdapter()
1821

19-
// 4. Create modal
22+
// 5. Create modal
2023
createAppKit({
2124
adapters: [ethersAdapter],
22-
networks: [sepolia],
25+
networks: [network],
2326
projectId,
2427
metadata,
2528
features: {

0 commit comments

Comments
 (0)