Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,43 @@ bun add seismic-react@1.1.1 seismic-viem@1.1.1 viem@^2.22.3 \
@tanstack/react-query@^5.55.3 \
@mui/material@^6.4.3 @emotion/react @emotion/styled \
framer-motion@^12.7.3 react-router-dom@^7.1.4 \
react-toastify@^11.0.5 use-sound@^5.0.0
react-toastify@^11.0.5 use-sound@^5.0.0 \
react-redux@^9.2.0 @reduxjs/toolkit@^2.5.1 \
@tailwindcss/vite tailwindcss@^4
```

### Copy public assets

Copy the `public/` folder from the [seismic-starter](https://github.com/SeismicSystems/seismic-starter) repo into `packages/web/public/`. This includes the clown sprites, button images, background, logo, and audio files used by the game UI.

### Configure Vite

Update `vite.config.ts` to add a path alias for cleaner imports:
Update `vite.config.ts`:

```typescript
import { resolve } from "path";
import { defineConfig } from "vite";

import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { defineConfig } from "vite";

// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
envDir: resolve(__dirname, "../.."),
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@": resolve(__dirname, "src"),
},
},
});
```

The `envDir` points to the monorepo root so that `.env` files at the top level are available to the web package.

### Environment variables

Create a `.env` file in `packages/web`:
Create a `.env` file at the monorepo root:

```properties
VITE_CHAIN_ID=31337
Expand All @@ -68,42 +79,80 @@ The key architectural pattern in a seismic-react app is the **provider stack**.
Create `src/App.tsx`:

```typescript
import React from 'react'
import { PropsWithChildren, useCallback } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import {
RainbowKitProvider,
darkTheme,
getDefaultConfig,
} from '@rainbow-me/rainbowkit'
import '@rainbow-me/rainbowkit/styles.css'
type OnAddressChangeParams,
ShieldedWalletProvider,
} from 'seismic-react'
import { sanvil, seismicTestnet } from 'seismic-react/rainbowkit'
import { http } from 'viem'
import { Config, WagmiProvider } from 'wagmi'

import { AuthProvider } from '@/components/chain/WalletConnectButton'
import Home from '@/pages/Home'
import NotFound from '@/pages/NotFound'
import { getDefaultConfig } from '@rainbow-me/rainbowkit'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import { sanvil, seismicTestnet } from 'seismic-viem'
import { http, WagmiProvider, useAccount } from 'wagmi'

import { AuthProvider } from './components/chain/WalletConnectButton'
import Home from './pages/Home'
import './App.css'

// Select chain based on environment variable
const chainConfig =
import.meta.env.VITE_CHAIN_ID === String(sanvil.id) ? sanvil : seismicTestnet
const configuredChainId = String(import.meta.env.VITE_CHAIN_ID ?? '')
const isSanvilConfig =
configuredChainId === 'sanvil' || configuredChainId === String(sanvil.id)
const CHAIN = isSanvilConfig ? sanvil : seismicTestnet
const CHAINS = [CHAIN]

// Configure wagmi with RainbowKit defaults
const config = getDefaultConfig({
appName: 'Clown Beatdown',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [chainConfig],
transports: {
[chainConfig.id]: http(import.meta.env.VITE_RPC_URL),
},
appName: 'Seismic Starter',
projectId: 'd705c8eaf9e6f732e1ddb8350222cdac',
// @ts-expect-error: this is fine
chains: CHAINS,
ssr: false,
})

const queryClient = new QueryClient()
const client = new QueryClient()

const Providers: React.FC<PropsWithChildren<{ config: Config }>> = ({
config,
children,
}) => {
const publicChain = CHAINS[0]
const publicTransport = http(publicChain.rpcUrls.default.http[0])
const handleAddressChange = useCallback(
async ({ publicClient, address }: OnAddressChangeParams) => {
if (publicClient.chain.id !== sanvil.id) return

const existingBalance = await publicClient.getBalance({ address })
if (existingBalance > 0n) return

const setBalance = publicClient.request as unknown as (args: {
method: string
params?: unknown[]
}) => Promise<unknown>

await setBalance({
method: 'anvil_setBalance',
params: [address, `0x${(10_000n * 10n ** 18n).toString(16)}`],
})
},
[]
)

function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider theme={darkTheme()}>
<ShieldedWalletProvider>
<QueryClientProvider client={client}>
<RainbowKitProvider>
<ShieldedWalletProvider
config={config}
options={{
publicTransport,
publicChain,
onAddressChange: handleAddressChange,
}}
>
<AuthProvider>{children}</AuthProvider>
</ShieldedWalletProvider>
</RainbowKitProvider>
Expand All @@ -112,13 +161,20 @@ function Providers({ children }: { children: React.ReactNode }) {
)
}

export default function App() {
const App: React.FC = () => {
return (
<Providers>
<Home />
</Providers>
<BrowserRouter>
<Providers config={config}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Providers>
</BrowserRouter>
)
}

export default App
```

### What's happening here?
Expand All @@ -128,6 +184,36 @@ The provider stack nests four layers, each adding functionality:
1. **WagmiProvider** — manages wallet connections and chain state via wagmi hooks (`useAccount`, `useConnect`, etc.)
2. **QueryClientProvider** — provides React Query for caching and background data fetching
3. **RainbowKitProvider** — adds a polished wallet connect modal UI
4. **ShieldedWalletProvider** — the Seismic-specific layer from `seismic-react` that derives a shielded wallet client from the connected wagmi account, enabling shielded reads and writes
4. **ShieldedWalletProvider** — the Seismic-specific layer from `seismic-react` that derives a shielded wallet client from the connected wagmi account, enabling shielded reads and writes. It takes `config` and `options` — the options include `publicTransport`, `publicChain`, and an `onAddressChange` callback.

The `onAddressChange` handler auto-funds new wallets when running on `sanvil` (local dev), so you don't need to manually send ETH to test accounts.

### Entry point: main.tsx

This is the same pattern you'd use for any Seismic dApp. The `ShieldedWalletProvider` is the only Seismic-specific addition to a standard wagmi + RainbowKit setup.
Create `src/main.tsx` to bootstrap the app with theme and state management:

```typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { ToastContainer } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'

import App from '@/App.tsx'
import { store } from '@/store/store'
import theme from '@/theme.ts'
import { ThemeProvider } from '@mui/material/styles'

import './index.css'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<ThemeProvider theme={theme}>
<Provider store={store}>
<App />
<ToastContainer />
</Provider>
</ThemeProvider>
</StrictMode>
)
```
Loading
Loading