Skip to content

Commit 8f92dad

Browse files
committed
basic setup
1 parent 0b1dda8 commit 8f92dad

File tree

9 files changed

+416
-3537
lines changed

9 files changed

+416
-3537
lines changed

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v24.10.0

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13-
"@rainbow-me/rainbowkit": "^2.2.9",
1413
"@tanstack/react-query": "^5.90.5",
14+
"porto": "^0.2.35",
1515
"react": "^19.2.0",
1616
"react-dom": "^19.2.0",
1717
"viem": "^2.38.3",
18-
"wagmi": "^2.18.2"
18+
"vite-plugin-mkcert": "^1.17.9"
1919
},
2020
"devDependencies": {
2121
"@eslint/js": "^9.38.0",

pnpm-lock.yaml

Lines changed: 166 additions & 3488 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/banner.png

-546 KB
Binary file not shown.

src/App.css

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,36 @@
66
min-height: 100vh;
77
}
88

9-
.banner {
10-
width: 100%;
11-
margin: 0 auto 50px;
12-
max-width: 800px;
9+
h1 {
10+
margin-bottom: 24px;
11+
font-size: 36px;
12+
color: #f8f8f8;
13+
}
14+
15+
select {
16+
margin-left: 8px;
17+
padding: 4px;
18+
border-radius: 4px;
19+
border: 1px solid #444;
20+
background-color: #1e2026;
21+
color: #f8f8f8;
22+
}
23+
24+
option {
25+
background-color: #1e2026;
26+
color: #f8f8f8;
27+
}
28+
29+
button {
30+
padding: 8px 12px;
31+
margin-top: 24px;
32+
border: none;
33+
border-radius: 4px;
34+
background-color: #3a3f51;
35+
color: #f8f8f8;
36+
cursor: pointer;
37+
}
38+
39+
button:hover {
40+
background-color: #50566e;
1341
}

src/App.tsx

Lines changed: 194 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,200 @@
1-
import { ConnectButton } from "@rainbow-me/rainbowkit";
2-
31
import "./App.css";
42

3+
import { useEffect, useMemo, useRef, useState } from "react";
4+
import { createWalletClient, custom, type Address, type Chain } from "viem";
5+
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";
18+
import { Porto } from "porto";
19+
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+
537
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;
49+
}
50+
}, []);
51+
52+
const [walletKind, setWalletKind] = useState<WalletKind>(
53+
injectedProvider ? "injected" : "porto"
54+
);
55+
const provider = walletKind === "injected" ? injectedProvider : portoProvider;
56+
57+
const [walletChainId, setWalletChainId] = useState<number | undefined>(
58+
undefined
59+
);
60+
const [walletChain, setWalletChain] = useState<Chain | undefined>(undefined);
61+
62+
const walletClient = useMemo(
63+
() =>
64+
provider
65+
? createWalletClient({
66+
chain: walletChain,
67+
transport: custom(provider),
68+
})
69+
: undefined,
70+
[provider, walletChain]
71+
);
72+
73+
const [account, setAccount] = useState<Address>();
74+
75+
useEffect(() => {
76+
if (!provider) {
77+
setAccount(undefined);
78+
setWalletChainId(undefined);
79+
setWalletChain(undefined);
80+
return;
81+
}
82+
83+
const onAccountsChanged = (accts: string[]) => {
84+
setAccount((accts?.[0] as Address) || undefined);
85+
};
86+
87+
const onChainChanged = (hex: string) => {
88+
const id = parseInt(hex, 16);
89+
setWalletChainId(id);
90+
setWalletChain(byId(id));
91+
};
92+
93+
provider.on?.("accountsChanged", onAccountsChanged);
94+
provider.on?.("chainChanged", onChainChanged);
95+
96+
return () => {
97+
provider.removeListener?.("accountsChanged", onAccountsChanged);
98+
provider.removeListener?.("chainChanged", onChainChanged);
99+
};
100+
}, [provider]);
101+
102+
useEffect(() => {
103+
(async () => {
104+
if (!provider) return;
105+
106+
try {
107+
const hex = await provider.request({ method: "eth_chainId" });
108+
const id = parseInt(hex as string, 16);
109+
setWalletChainId(id);
110+
setWalletChain(byId(id));
111+
} catch {
112+
setWalletChainId(undefined);
113+
setWalletChain(undefined);
114+
}
115+
116+
if (walletClient) {
117+
try {
118+
const addrs = await getAddresses(walletClient);
119+
setAccount((addrs?.[0] as Address) || undefined);
120+
} catch {
121+
setAccount(undefined);
122+
}
123+
}
124+
})();
125+
}, [provider, walletClient]);
126+
127+
const connect = async () => {
128+
if (!walletClient) return;
129+
130+
const addrs = await requestAddresses(walletClient);
131+
setAccount(addrs[0] as Address);
132+
133+
try {
134+
const hex = await provider!.request({ method: "eth_chainId" });
135+
const id = parseInt(hex as string, 16);
136+
setWalletChainId(id);
137+
setWalletChain(byId(id));
138+
} catch {}
139+
};
140+
141+
const disconnect = async () => {
142+
setAccount(undefined);
143+
144+
try {
145+
await walletClient?.transport.request({
146+
method: "wallet_revokePermissions",
147+
params: [{ eth_accounts: {} }],
148+
});
149+
} catch {}
150+
};
151+
152+
const injectedAvailable = !!injectedProvider;
153+
const portoAvailable = !!portoProvider;
154+
const providerAvailable = !!provider;
155+
6156
return (
7-
<>
8-
<div className="container">
9-
<img src="/banner.png" alt="Foundry" className="banner" />
10-
<ConnectButton />
11-
</div>
12-
</>
157+
<div className="container">
158+
<h1>Foundry</h1>
159+
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>
183+
</div>
184+
)}
185+
186+
{account ? (
187+
<>
188+
<div style={{ marginBottom: 8 }}>Connected: {account}</div>
189+
<div style={{ display: "flex", gap: 8 }}>
190+
<button onClick={disconnect}>Disconnect</button>
191+
</div>
192+
</>
193+
) : (
194+
<button onClick={connect} disabled={!providerAvailable}>
195+
{providerAvailable ? "Connect Wallet" : "No Provider Available"}
196+
</button>
197+
)}
198+
</div>
13199
);
14200
};

src/main.tsx

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,13 @@
11
import { StrictMode } from "react";
22
import { createRoot } from "react-dom/client";
3-
import { RainbowKitProvider, darkTheme } from "@rainbow-me/rainbowkit";
4-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5-
import { WagmiProvider } from "wagmi";
6-
import { porto } from "wagmi/connectors";
7-
import { createConfig, http } from "wagmi";
8-
import { mainnet, base } from "wagmi/chains";
93

104
import "./styles/reset.css";
115
import "./styles/global.css";
12-
import "@rainbow-me/rainbowkit/styles.css";
136

147
import { App } from "./App.tsx";
158

16-
const queryClient = new QueryClient();
17-
18-
const config = createConfig({
19-
chains: [mainnet, base],
20-
connectors: [porto()],
21-
transports: {
22-
[mainnet.id]: http(),
23-
[base.id]: http(),
24-
},
25-
});
26-
279
createRoot(document.getElementById("root")!).render(
2810
<StrictMode>
29-
<WagmiProvider config={config}>
30-
<QueryClientProvider client={queryClient}>
31-
<RainbowKitProvider theme={darkTheme()}>
32-
<App />
33-
</RainbowKitProvider>
34-
</QueryClientProvider>
35-
</WagmiProvider>
11+
<App />
3612
</StrictMode>
3713
);

src/styles/global.css

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
@font-face {
2-
font-family: "Inter-Regular";
3-
src: url("/Inter-Regular.woff2") format("woff2");
4-
font-weight: normal;
5-
font-style: normal;
6-
font-display: swap;
7-
}
8-
91
html,
102
body,
113
#root {
@@ -14,4 +6,5 @@ body,
146
overflow: hidden;
157
font-family: "Inter-Regular", sans-serif;
168
background-color: #13151b;
9+
color: #f8f8f8;
1710
}

vite.config.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
import { defineConfig } from "vite";
22
import react from "@vitejs/plugin-react-swc";
3+
import mkcert from "vite-plugin-mkcert";
34

4-
// https://vite.dev/config/
55
export default defineConfig({
6-
plugins: [react()],
6+
plugins: [mkcert(), react()],
7+
build: {
8+
outDir: "dist",
9+
assetsDir: ".",
10+
cssCodeSplit: false,
11+
modulePreload: false,
12+
rollupOptions: {
13+
input: "index.html",
14+
output: {
15+
inlineDynamicImports: true,
16+
manualChunks: undefined,
17+
entryFileNames: "main.js",
18+
chunkFileNames: "main.js",
19+
assetFileNames: (assetInfo) =>
20+
assetInfo.name?.endsWith(".css") ? "styles.css" : "[name][extname]",
21+
},
22+
},
23+
},
724
});

0 commit comments

Comments
 (0)