Skip to content

Commit da5bb21

Browse files
committed
refactor, use EIP6963 discovery exclusively
1 parent c6ffa1e commit da5bb21

File tree

3 files changed

+152
-123
lines changed

3 files changed

+152
-123
lines changed

src/App.tsx

Lines changed: 134 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,114 @@
11
import "./App.css";
22

3-
import { useEffect, useMemo, useRef, useState } from "react";
3+
import { useEffect, useMemo, useState } from "react";
44
import { createWalletClient, custom, type Address, type Chain } from "viem";
55
import { 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";
187
import { 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:&nbsp;
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:&nbsp;
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+
}

src/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export type EIP6963ProviderInfo = {
2+
uuid: string;
3+
name: string;
4+
icon: string;
5+
rdns: string;
6+
};
7+
8+
export type EIP1193 = {
9+
request: (args: { method: string; params?: any[] | object }) => Promise<any>;
10+
on?: (event: string, listener: (...args: any[]) => void) => void;
11+
removeListener?: (event: string, listener: (...args: any[]) => void) => void;
12+
};
13+
14+
export type AnnounceEvent = CustomEvent<{
15+
info: EIP6963ProviderInfo;
16+
provider: EIP1193;
17+
}>;

vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default defineConfig({
1111
generateBundle(_, bundle) {
1212
for (const file of Object.values(bundle)) {
1313
if (file.type === "chunk" && file.fileName === "main.js") {
14-
file.code = "/** SESSION_TOKEN */\n" + file.code;
14+
file.code = "/** SESSION_TOKEN */\n\n" + file.code;
1515
}
1616
}
1717
},

0 commit comments

Comments
 (0)