11import "./App.css" ;
22
3- import { useEffect , useMemo , useRef , useState } from "react" ;
3+ import { useEffect , useMemo , useState } from "react" ;
44import { createWalletClient , custom , type Address , type Chain } from "viem" ;
55import { getAddresses , requestAddresses } from "viem/actions" ;
6- import {
7- mainnet ,
8- sepolia ,
9- base ,
10- baseSepolia ,
11- optimism ,
12- optimismSepolia ,
13- arbitrum ,
14- arbitrumSepolia ,
15- polygon ,
16- } from "viem/chains" ;
17- import "viem/window" ;
6+ import * as chains from "viem/chains" ;
187import { Porto } from "porto" ;
198
20- type WalletKind = "injected" | "porto" ;
21-
22- // Known chains for naming/config if we recognize the id
23- const CHAINS : Chain [ ] = [
24- mainnet ,
25- sepolia ,
26- base ,
27- baseSepolia ,
28- optimism ,
29- optimismSepolia ,
30- arbitrum ,
31- arbitrumSepolia ,
32- polygon ,
33- ] ;
34-
35- const byId = ( id : number ) => CHAINS . find ( ( c ) => c . id === id ) ;
36-
37- export const App = ( ) => {
38- const injectedProvider =
39- ( typeof window !== "undefined" ? ( window as any ) . ethereum : undefined ) ||
40- undefined ;
41-
42- const portoRef = useRef < any > ( null ) ;
43- const portoProvider = useMemo ( ( ) => {
44- try {
45- if ( ! portoRef . current ) portoRef . current = Porto . create ( ) ;
46- return portoRef . current . provider ;
47- } catch {
48- return undefined ;
9+ import type { EIP6963ProviderInfo , EIP1193 , AnnounceEvent } from "./types.ts" ;
10+
11+ const ALL_CHAINS : Chain [ ] = Object . values ( chains ) . filter (
12+ ( c : any ) =>
13+ typeof c === "object" &&
14+ c !== null &&
15+ "id" in c &&
16+ typeof ( c as any ) . id === "number"
17+ ) ;
18+ const byId = ( id : number ) => ALL_CHAINS . find ( ( c ) => c . id === id ) ;
19+
20+ export function App ( ) {
21+ useEffect ( ( ) => {
22+ if ( ! ( window as any ) . __PORTO__ ) {
23+ ( window as any ) . __PORTO__ = Porto . create ( ) ;
4924 }
5025 } , [ ] ) ;
5126
52- const [ walletKind , setWalletKind ] = useState < WalletKind > (
53- injectedProvider ? "injected" : "porto"
54- ) ;
55- const provider = walletKind === "injected" ? injectedProvider : portoProvider ;
27+ const [ providers , setProviders ] = useState <
28+ { info : EIP6963ProviderInfo ; provider : EIP1193 } [ ]
29+ > ( [ ] ) ;
5630
57- const [ walletChainId , setWalletChainId ] = useState < number | undefined > (
58- undefined
59- ) ;
60- const [ walletChain , setWalletChain ] = useState < Chain | undefined > ( undefined ) ;
31+ useEffect ( ( ) => {
32+ const onAnnounce = ( e : Event ) => {
33+ const ev = e as AnnounceEvent ;
34+ const { info, provider } = ev . detail ;
35+ setProviders ( ( prev ) =>
36+ prev . some ( ( p ) => p . info . uuid === info . uuid )
37+ ? prev
38+ : [ ...prev , { info, provider } ]
39+ ) ;
40+ } ;
41+
42+ window . addEventListener (
43+ "eip6963:announceProvider" ,
44+ onAnnounce as EventListener
45+ ) ;
46+ window . dispatchEvent ( new Event ( "eip6963:requestProvider" ) ) ;
47+
48+ return ( ) => {
49+ window . removeEventListener (
50+ "eip6963:announceProvider" ,
51+ onAnnounce as EventListener
52+ ) ;
53+ } ;
54+ } , [ ] ) ;
55+
56+ const [ selectedUuid , setSelectedUuid ] = useState < string | null > ( null ) ;
57+ useEffect ( ( ) => {
58+ if ( providers . length === 1 && ! selectedUuid ) {
59+ setSelectedUuid ( providers [ 0 ] . info . uuid ) ;
60+ }
61+ } , [ providers , selectedUuid ] ) ;
62+
63+ const selected = providers . find ( ( p ) => p . info . uuid === selectedUuid ) ?? null ;
64+
65+ const [ account , setAccount ] = useState < Address > ( ) ;
66+ const [ chainId , setChainId ] = useState < number > ( ) ;
67+ const [ chain , setChain ] = useState < Chain > ( ) ;
6168
6269 const walletClient = useMemo (
6370 ( ) =>
64- provider
71+ selected
6572 ? createWalletClient ( {
66- chain : walletChain ,
67- transport : custom ( provider ) ,
73+ chain,
74+ transport : custom ( selected . provider ) ,
6875 } )
6976 : undefined ,
70- [ provider , walletChain ]
77+ [ selected , chain ]
7178 ) ;
7279
73- const [ account , setAccount ] = useState < Address > ( ) ;
74-
7580 useEffect ( ( ) => {
76- if ( ! provider ) {
77- setAccount ( undefined ) ;
78- setWalletChainId ( undefined ) ;
79- setWalletChain ( undefined ) ;
80- return ;
81- }
81+ if ( ! selected ) return ;
8282
83- const onAccountsChanged = ( accts : string [ ] ) => {
83+ const onAccountsChanged = ( accts : string [ ] ) =>
8484 setAccount ( ( accts ?. [ 0 ] as Address ) || undefined ) ;
85- } ;
8685
8786 const onChainChanged = ( hex : string ) => {
8887 const id = parseInt ( hex , 16 ) ;
89- setWalletChainId ( id ) ;
90- setWalletChain ( byId ( id ) ) ;
88+ setChainId ( id ) ;
89+ setChain ( byId ( id ) ) ;
9190 } ;
9291
93- provider . on ?.( "accountsChanged" , onAccountsChanged ) ;
94- provider . on ?.( "chainChanged" , onChainChanged ) ;
95-
92+ selected . provider . on ?.( "accountsChanged" , onAccountsChanged ) ;
93+ selected . provider . on ?.( "chainChanged" , onChainChanged ) ;
9694 return ( ) => {
97- provider . removeListener ?.( "accountsChanged" , onAccountsChanged ) ;
98- provider . removeListener ?.( "chainChanged" , onChainChanged ) ;
95+ selected . provider . removeListener ?.( "accountsChanged" , onAccountsChanged ) ;
96+ selected . provider . removeListener ?.( "chainChanged" , onChainChanged ) ;
9997 } ;
100- } , [ provider ] ) ;
98+ } , [ selected ] ) ;
10199
102100 useEffect ( ( ) => {
103101 ( async ( ) => {
104- if ( ! provider ) return ;
102+ if ( ! selected ) return ;
105103
106104 try {
107- const hex = await provider . request ( { method : "eth_chainId" } ) ;
105+ const hex = await selected . provider . request ( { method : "eth_chainId" } ) ;
108106 const id = parseInt ( hex as string , 16 ) ;
109- setWalletChainId ( id ) ;
110- setWalletChain ( byId ( id ) ) ;
107+ setChainId ( id ) ;
108+ setChain ( byId ( id ) ) ;
111109 } catch {
112- setWalletChainId ( undefined ) ;
113- setWalletChain ( undefined ) ;
110+ setChainId ( undefined ) ;
111+ setChain ( undefined ) ;
114112 }
115113
116114 if ( walletClient ) {
@@ -122,25 +120,23 @@ export const App = () => {
122120 }
123121 }
124122 } ) ( ) ;
125- } , [ provider , walletClient ] ) ;
123+ } , [ selected , walletClient ] ) ;
126124
127125 const connect = async ( ) => {
128- if ( ! walletClient ) return ;
129-
126+ if ( ! walletClient || ! selected ) return ;
130127 const addrs = await requestAddresses ( walletClient ) ;
131128 setAccount ( addrs [ 0 ] as Address ) ;
132129
133130 try {
134- const hex = await provider ! . request ( { method : "eth_chainId" } ) ;
131+ const hex = await selected . provider . request ( { method : "eth_chainId" } ) ;
135132 const id = parseInt ( hex as string , 16 ) ;
136- setWalletChainId ( id ) ;
137- setWalletChain ( byId ( id ) ) ;
133+ setChainId ( id ) ;
134+ setChain ( byId ( id ) ) ;
138135 } catch { }
139136 } ;
140137
141138 const disconnect = async ( ) => {
142139 setAccount ( undefined ) ;
143-
144140 try {
145141 await walletClient ?. transport . request ( {
146142 method : "wallet_revokePermissions" ,
@@ -149,52 +145,68 @@ export const App = () => {
149145 } catch { }
150146 } ;
151147
152- const injectedAvailable = ! ! injectedProvider ;
153- const portoAvailable = ! ! portoProvider ;
154- const providerAvailable = ! ! provider ;
155-
156148 return (
157149 < div className = "container" >
158150 < h1 > Foundry</ h1 >
159151
160- < label style = { { display : "block" , marginBottom : 8 } } >
161- Wallet:
162- < select
163- value = { walletKind }
164- onChange = { ( e ) => setWalletKind ( e . target . value as WalletKind ) }
165- >
166- < option value = "injected" disabled = { ! injectedAvailable } >
167- Injected { injectedAvailable ? "" : "(not detected)" }
168- </ option >
169- < option value = "porto" disabled = { ! portoAvailable } >
170- Porto { portoAvailable ? "" : "(unavailable)" }
171- </ option >
172- </ select >
173- </ label >
174-
175- { account && (
176- < div style = { { marginBottom : 12 } } >
177- Wallet chain:{ " " }
178- < b >
179- { walletChain
180- ? `${ walletChain . name } (${ walletChainId ?? "unknown" } )`
181- : "" }
182- </ b >
152+ { providers . length > 1 && (
153+ < div style = { { marginBottom : 8 } } >
154+ < label >
155+ Wallet:
156+ < select
157+ value = { selectedUuid ?? "" }
158+ onChange = { ( e ) => setSelectedUuid ( e . target . value || null ) }
159+ >
160+ < option value = "" disabled >
161+ Select wallet…
162+ </ option >
163+ { providers . map ( ( { info } ) => (
164+ < option key = { info . uuid } value = { info . uuid } >
165+ { info . name } ({ info . rdns } )
166+ </ option >
167+ ) ) }
168+ </ select >
169+ </ label >
183170 </ div >
184171 ) }
185172
186- { account ? (
173+ { providers . length === 0 && < p > No EIP-6963 wallets found.</ p > }
174+
175+ { selected && account && (
176+ < pre
177+ style = { {
178+ border : "1px solid #e1e4e8" ,
179+ borderRadius : 6 ,
180+ padding : "8px 12px" ,
181+ fontFamily : "ui-monospace, SFMono-Regular, Menlo, monospace" ,
182+ fontSize : 13 ,
183+ lineHeight : 1.5 ,
184+ marginBottom : 16 ,
185+ whiteSpace : "pre-wrap" ,
186+ } }
187+ >
188+ { `\
189+ chain: ${ chain ? `${ chain . name } (${ chainId } )` : chainId ?? "unknown" }
190+ rpc: ${
191+ chain ?. rpcUrls ?. default ?. http ?. [ 0 ] ??
192+ chain ?. rpcUrls ?. public ?. http ?. [ 0 ] ??
193+ "unknown"
194+ } `}
195+ </ pre >
196+ ) }
197+
198+ { selected && (
187199 < >
188- < div style = { { marginBottom : 8 } } > Connected: { account } </ div >
189- < div style = { { display : "flex" , gap : 8 } } >
190- < button onClick = { disconnect } > Disconnect</ button >
191- </ div >
200+ { account ? (
201+ < >
202+ < div style = { { marginBottom : 8 } } > Connected: { account } </ div >
203+ < button onClick = { disconnect } > Disconnect</ button >
204+ </ >
205+ ) : (
206+ < button onClick = { connect } > Connect Wallet</ button >
207+ ) }
192208 </ >
193- ) : (
194- < button onClick = { connect } disabled = { ! providerAvailable } >
195- { providerAvailable ? "Connect Wallet" : "No Provider Available" }
196- </ button >
197209 ) }
198210 </ div >
199211 ) ;
200- } ;
212+ }
0 commit comments