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 @@ -24,7 +24,7 @@ import {
type ShieldedWalletClient,
createShieldedWalletClient,
} from "seismic-viem";
import { Abi, Address, Chain, http } from "viem";
import { Abi, Address, Chain, http, hexToString } from "viem";
import { privateKeyToAccount } from "viem/accounts";

import { getShieldedContractWithCheck } from "../lib/utils";
Expand Down Expand Up @@ -144,6 +144,7 @@ async rob(playerName: string) {
console.log(`- Player ${playerName} reading rob()`)
const contract = this.getPlayerContract(playerName)
const result = await contract.read.rob() // signed read
console.log(`- Player ${playerName} robbed secret:`, result)
const decoded = hexToString(result as `0x${string}`)
console.log(`- Player ${playerName} robbed secret:`, decoded)
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ Open `.env` and paste the following:

```properties
CHAIN_ID=31337
VITE_CHAIN_ID=31337
RPC_URL=http://127.0.0.1:8545
ALICE_PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
BOB_PRIVKEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
```

**What's Happening Here?**

- `CHAIN_ID=31337`: `31337` is the default chain ID for `sanvil` (your local Seismic node).
- `CHAIN_ID=31337`: Used to locate the deployment broadcast file for the correct chain.
- `VITE_CHAIN_ID=31337`: Used for chain selection (`sanvil` vs testnet). `31337` is the default chain ID for `sanvil` (your local Seismic node).
- `RPC_URL=http://127.0.0.1:8545`: This is the RPC URL for interacting with the local Seismic node.
- `ALICE_PRIVKEY` and `BOB_PRIVKEY`: These are Alice and Bob's private keys, allowing them to play the game. (These are standard test keys provided by `sanvil`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ bun add seismic-react@1.1.1 seismic-viem@1.1.1 viem@^2.22.3 \
@tailwindcss/vite tailwindcss@^4
```

The Vite config below uses the SWC plugin for faster builds. Install it as a dev dependency:

```bash
bun add -d @vitejs/plugin-react-swc
```

### 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.
Expand Down Expand Up @@ -88,7 +94,7 @@ import {
} from 'seismic-react'
import { sanvil, seismicTestnet } from 'seismic-react/rainbowkit'
import { http } from 'viem'
import { Config, WagmiProvider } from 'wagmi'
import { type Config, WagmiProvider } from 'wagmi'

import { AuthProvider } from '@/components/chain/WalletConnectButton'
import Home from '@/pages/Home'
Expand Down Expand Up @@ -188,6 +194,56 @@ The provider stack nests four layers, each adding functionality:

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.

### Supporting files

Before wiring up the entry point, create the supporting modules that `main.tsx` and `App.tsx` will import.

**Redux store** — Create `src/store/store.ts`:

```typescript
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
reducer: {},
})
```

**MUI theme** — Create `src/theme.ts`:

```typescript
import { createTheme } from '@mui/material/styles'

const theme = createTheme({
palette: {
mode: 'dark',
},
})

export default theme
```

**Page components** — Create `src/pages/Home.tsx`:

```typescript
import ClownPuncher from '@/components/game/ClownPuncher'

const Home = () => <ClownPuncher />
export default Home
```

Create `src/pages/NotFound.tsx`:

```typescript
const NotFound = () => <div>404 - Page not found</div>
export default NotFound
```

**Stylesheets** — Create `src/App.css` (empty for now) and replace `src/index.css` with:

```css
@import "tailwindcss";
```

### Entry point: main.tsx

Create `src/main.tsx` to bootstrap the app with theme and state management:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ cp packages/contracts/out/ClownBeatdown.sol/ClownBeatdown.json \
packages/web/src/abis/contracts/ClownBeatdown.json
```

You'll also need to add the deployed contract address and chain ID to the JSON file. The ABI file should contain `abi`, `address`, and `chainId` fields.
You'll also need to add the deployed contract address and chain ID to the JSON file. After copying, edit the file to include `address` and `chainId` at the top level. The final structure should look like:

```json
{
"address": "0xYourDeployedAddress",
"chainId": 31337,
"abi": [
{ "type": "constructor", "inputs": [...] },
{ "type": "function", "name": "hit", ... },
...
]
}
```

You can find the deployed address in `packages/contracts/broadcast/ClownBeatdown.s.sol/31337/run-latest.json` under `transactions[0].contractAddress`.

### Contract type definition

Expand Down Expand Up @@ -58,8 +72,8 @@ This hook wraps the contract methods into callable functions with proper error h
import { useCallback, useEffect, useState } from "react";
import { useShieldedWallet } from "seismic-react";
import {
ShieldedPublicClient,
ShieldedWalletClient,
type ShieldedPublicClient,
type ShieldedWalletClient,
addressExplorerUrl,
txExplorerUrl,
} from "seismic-viem";
Expand Down Expand Up @@ -181,6 +195,40 @@ Notice the different contract namespaces used for each method:

This distinction between `twrite`, `read`, and `tread` is the key difference from a standard Ethereum dApp.

### Supporting components

Before building the game actions hook, create the helper components and hooks it depends on.

**Explorer toast** — Create `src/components/chain/ExplorerToast.tsx`:

```typescript
import React from 'react'

type ExplorerToastProps = {
url: string
text: string
hash: string
}

export const ExplorerToast: React.FC<ExplorerToastProps> = ({ url, text, hash }) => (
<a href={url} target="_blank" rel="noopener noreferrer">
{text}{hash.slice(0, 10)}...
</a>
)
```

**Toast notifications** — Create `src/hooks/useToastNotifications.ts`:

```typescript
import { toast } from 'react-toastify'

export const useToastNotifications = () => ({
notifySuccess: (msg: string) => toast.success(msg),
notifyError: (msg: string) => toast.error(msg),
notifyInfo: (msg: string | React.ReactElement) => toast.info(msg),
})
```

### useGameActions hook

This hook orchestrates the game logic, managing state and coordinating contract calls with UI feedback. Create `src/hooks/useGameActions.ts`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ bun init -y
3. Now, create an `src/` folder and move `index.ts` there.

```bash
mkdir -p src && mv -t src index.ts
mkdir -p src && mv index.ts src/
```

4. Now, edit `package.json` to be the following:
Expand All @@ -33,7 +33,7 @@ mkdir -p src && mv -t src index.ts
},
"dependencies": {
"dotenv": "^16.4.7",
"seismic-viem": "1.0.9",
"seismic-viem": "1.1.1",
"viem": "^2.22.3"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cd packages/contracts
2. **Initialize a project with `sforge`:**

```bash
sforge init --no-commit && rm -rf .github
sforge init && rm -rf .github
```

This command will:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In this chapter, you'll learn to create and initialize the secrets pool — a co

### Defining the secrets pool

The **secrets pool** is the collection of hidden strings that the clown carries. Using Seismic's **`sbytes`** type, each secret is shielded on-chain — encrypted and invisible to observers. A shielded **`suint256`** index determines which secret gets revealed when the clown is robbed. Open up `packages/contracts/ClownBeatdown.sol` and define the state variables:
The **secrets pool** is the collection of hidden strings that the clown carries. Using Seismic's **`sbytes`** type, each secret is shielded on-chain — encrypted and invisible to observers. A shielded **`suint256`** index determines which secret gets revealed when the clown is robbed. Open up `packages/contracts/src/ClownBeatdown.sol` and define the state variables:

```solidity
// SPDX-License-Identifier: MIT License
Expand All @@ -26,6 +26,8 @@ contract ClownBeatdown {
}
```

We accept `_clownStamina` in the constructor but won't use it until Chapter 2, when we add the stamina system.

### Add the addSecret function

Next, let's implement a function to add secrets to the pool. The `addSecret` function takes a plain `string` and converts it to `sbytes` for shielded storage:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ contract ClownBeatdown {
// Tracks the number of hits per player per round.
mapping(uint256 => mapping(address => uint256)) hitsPerRound;

// Events to log hits, shakes, and resets.
// Events to log hits and resets.

// Event to log hits.
event Hit(uint256 indexed round, address indexed hitter, uint256 remaining); // Logged when a hit lands.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,14 @@ contract ClownBeatdownScript is Script {

vm.startBroadcast(deployerPrivateKey);
clownBeatdown = new ClownBeatdown(3);
clownBeatdown.addSecret("The moon is made of cheese");
clownBeatdown.addSecret("Clowns rule the underworld");
clownBeatdown.addSecret("The cake is a lie");
clownBeatdown.addSecret("42 is the answer");
clownBeatdown.addSecret("Never trust a smiling clown");
vm.stopBroadcast();

console.log("Deployed at:", address(clownBeatdown));
}
}
```

This script will deploy a new instance of the ClownBeatdown contract with an initial stamina of 3 and populate it with 5 secrets.
This script will deploy a new instance of the ClownBeatdown contract with an initial stamina of 3. We'll add secrets separately in the next step, since `addSecret` performs shielded writes that need to be sent as on-chain transactions.

### Deploying the contract

Expand All @@ -62,7 +59,7 @@ PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

The `RPC_URL` denotes the port on which `sanvil` is running and the `PRIVKEY` is one of the standard `sanvil` testing private keys.

3. Now, from `packages/contracts`, run
3. Now, from `packages/contracts`, deploy the contract:

```bash
source .env
Expand All @@ -71,4 +68,21 @@ sforge script script/ClownBeatdown.s.sol:ClownBeatdownScript \
--broadcast
```

Your contract should be up and deployed to your local Seismic node!
The output will show the deployed contract address (e.g. `0x5FbDB2315678afecb367f032d93F642f64180aa3`).

4. Add secrets to the deployed contract using `scast send`. Replace `<CONTRACT_ADDRESS>` with the address from the previous step:

```bash
scast send <CONTRACT_ADDRESS> "addSecret(string)" "The moon is made of cheese" \
--rpc-url $RPC_URL --private-key $PRIVKEY
scast send <CONTRACT_ADDRESS> "addSecret(string)" "Clowns rule the underworld" \
--rpc-url $RPC_URL --private-key $PRIVKEY
scast send <CONTRACT_ADDRESS> "addSecret(string)" "The cake is a lie" \
--rpc-url $RPC_URL --private-key $PRIVKEY
scast send <CONTRACT_ADDRESS> "addSecret(string)" "42 is the answer" \
--rpc-url $RPC_URL --private-key $PRIVKEY
scast send <CONTRACT_ADDRESS> "addSecret(string)" "Never trust a smiling clown" \
--rpc-url $RPC_URL --private-key $PRIVKEY
```

Your contract should be up and deployed to your local Seismic node with 5 secrets!
Loading