Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Set up Privy with Seismic for email and social login
import { seismicTestnet } from "seismic-react/rainbowkit";
// Privy needs the chain in viem format
const seismicChain = {
id: seismicTestnet.id,
name: seismicTestnet.name,
nativeCurrency: seismicTestnet.nativeCurrency,
rpcUrls: seismicTestnet.rpcUrls,
blockExplorers: seismicTestnet.blockExplorers,
};import { createConfig } from "@privy-io/wagmi";
import { http } from "viem";
import { seismicTestnet } from "seismic-react/rainbowkit";
const wagmiConfig = createConfig({
chains: [seismicTestnet],
transports: {
[seismicTestnet.id]: http(),
},
});import { PrivyProvider } from '@privy-io/react-auth'
import { WagmiProvider } from '@privy-io/wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
const queryClient = new QueryClient()
function App({ children }: { children: React.ReactNode }) {
return (
<PrivyProvider
appId="YOUR_PRIVY_APP_ID"
config={{
defaultChain: seismicChain,
supportedChains: [seismicChain],
embeddedWallets: {
createOnLogin: 'users-without-wallets',
},
}}
>
<QueryClientProvider client={queryClient}>
<WagmiProvider config={wagmiConfig}>
<ShieldedWalletProvider config={wagmiConfig}>
{children}
</ShieldedWalletProvider>
</WagmiProvider>
</QueryClientProvider>
</PrivyProvider>
)
}import { usePrivy } from '@privy-io/react-auth'
function LoginButton() {
const { login, logout, authenticated, user } = usePrivy()
if (authenticated) {
return (
<div>
<p>Logged in as {user?.email?.address || user?.wallet?.address}</p>
<button onClick={logout}>Log out</button>
</div>
)
}
return <button onClick={login}>Sign in</button>
}import { useShieldedWallet } from 'seismic-react'
function MyComponent() {
const { walletClient, loaded, error } = useShieldedWallet()
if (!loaded) return <div>Initializing shielded wallet...</div>
if (error) return <div>Error: {error}</div>
if (!walletClient) return <div>Sign in to get started.</div>
return <div>Shielded wallet ready!</div>
}'use client'
import { PrivyProvider, usePrivy } from '@privy-io/react-auth'
import { WagmiProvider, createConfig } from '@privy-io/wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider, useShieldedWallet } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
import { http } from 'viem'
const seismicChain = {
id: seismicTestnet.id,
name: seismicTestnet.name,
nativeCurrency: seismicTestnet.nativeCurrency,
rpcUrls: seismicTestnet.rpcUrls,
blockExplorers: seismicTestnet.blockExplorers,
}
const wagmiConfig = createConfig({
chains: [seismicTestnet],
transports: {
[seismicTestnet.id]: http(),
},
})
const queryClient = new QueryClient()
function LoginButton() {
const { login, logout, authenticated, user } = usePrivy()
if (authenticated) {
return (
<div>
<p>Logged in as {user?.email?.address || user?.wallet?.address}</p>
<button onClick={logout}>Log out</button>
</div>
)
}
return <button onClick={login}>Sign in</button>
}
function WalletStatus() {
const { walletClient, publicClient, loaded, error } = useShieldedWallet()
if (!loaded) return <p>Initializing shielded wallet...</p>
if (error) return <p>Error: {error}</p>
if (!walletClient) return <p>Sign in to get started.</p>
return (
<div>
<p>Shielded wallet ready</p>
<p>Public client: {publicClient ? 'Available' : 'Loading...'}</p>
</div>
)
}
export default function App() {
return (
<PrivyProvider
appId="YOUR_PRIVY_APP_ID"
config={{
defaultChain: seismicChain,
supportedChains: [seismicChain],
embeddedWallets: {
createOnLogin: 'users-without-wallets',
},
}}
>
<QueryClientProvider client={queryClient}>
<WagmiProvider config={wagmiConfig}>
<ShieldedWalletProvider config={wagmiConfig}>
<LoginButton />
<WalletStatus />
</ShieldedWalletProvider>
</WagmiProvider>
</QueryClientProvider>
</PrivyProvider>
)
}balanceOf, transfer, approve, and transferFrom do.solcYou're two commands away from running an encrypted protocol
git clone "https://github.com/SeismicSystems/seismic-starter.git"
cd seismic-starter/packages/contracts
sforge test -vv0x00...0// Standard ERC20 — balances visible to everyone
mapping(address => uint256) public balanceOf;
function transfer(address to, uint256 amount) public {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}// Seismic SRC20 — balances shielded by default
mapping(address => suint256) balanceOf; // uint256 → suint256
function transfer(address to, suint256 amount) public { // uint256 → suint256
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}git clone "https://github.com/SeismicSystems/seismic-starter.git"
cd seismic-starter/packages/contracts
sforge test -vvTypeScript client libraries for Seismic
sbytes and a shielded index using suint256.cd packages/contracts# Assuming you are currently in the contracts directory
cd ../clibun init -ypackages/contracts/src/ClownBeatdown.solcurl https://sh.rustup.rs -sSf | shcurl -L \
-H "Accept: application/vnd.github.v3.raw" \
"https://api.github.com/repos/SeismicSystems/seismic-foundry/contents/sfoundryup/install?ref=seismic" | bash
source ~/.zshenv # or ~/.bashrc, depending on your shellsfoundryup
source ~/.zshenv # or ~/.bashrc, depending on your shellsforge clean # run in your project's contract directorybun install
bun run dev:webimport { createConfig, http } from "wagmi";
import { injected } from "wagmi/connectors";
import { seismicTestnet } from "seismic-viem";
export const wagmiConfig = createConfig({
chains: [seismicTestnet],
connectors: [injected({ target: "metaMask" })],
transports: {
[seismicTestnet.id]: http(),
},
});sforge init && rm -rf .github.env
broadcast/
cache/# Remove the Counter files
rm -f src/Counter.sol test/Counter.t.sol script/Counter.s.sol
# Create empty ClownBeatdown files in the same locations
touch src/ClownBeatdown.sol test/ClownBeatdown.t.sol script/ClownBeatdown.s.solmkdir -p src && mv index.ts src/{
"name": "clown-beatdown-cli",
"license": "MIT License",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"dotenv": "^16.4.7",
"seismic-viem": "1.1.1",
"viem": "^2.22.3"
},
"devDependencies": {
"@types/node": "^22.7.6",
"typescript": "^5.6.3"
}
}node_modules// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
contract ClownBeatdown {
mapping(uint256 => sbytes) secrets; // Pool of possible secrets (shielded).
uint256 secretsCount; // Number of secrets for modular arithmetic.
suint256 secretIndex; // Shielded index into the secrets mapping.
uint256 round; // The current round number (used by _randomIndex).
constructor(uint256 _clownStamina) {
round = 1; // Start with the first round.
}
}function addSecret(string memory _secret) public {
secrets[secretsCount] = sbytes(_secret);
secretsCount++;
secretIndex = suint256(_randomIndex()); // Re-pick a random secret.
}// Generate a pseudo-random index into the secrets array.
function _randomIndex() private view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, round))) % secretsCount;
}privateKey: the private key to create the client forsuint256[] private balances; // dynamic — shielded length
sbool[4] private flags; // fixed — length 4 is public
function example(uint256 i) public {
balances[i] = suint256(100); // valid — uint256 index
flags[0] = sbool(true); // valid — literal index
}contracts subdirectory will house the Seismic smart contract(s) and test(s) for the project, while the cli will house the interface to interact with the contracts.seismic-viem) handles Seismic transaction construction automatically when you use shielded write functions.GET http://localhost:3001/api/tokens{
"count": 2,
"tokens": [
{
"address": "0xabc123...",
"name": "My Private Token",
"symbol": "MPT",
"decimals": 18,
"owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"total_supply": "1000000000000000000000000"
},
{
"address": "0xdef456...",
"name": "Another Token",
"symbol": "AT",
"decimals": 6,
"owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"total_supply": "500000000000"
}
]
}GET http://localhost:3001/api/token/0xabc...{
"name": "My Private Token",
"symbol": "MPT",
"decimals": 18,
"owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"total_supply": "1000000000000000000000000"
}{
"error": "Invalid address: ..."
}const walletClient = await createShieldedWalletClient({
chain: seismicChain,
transport: httpTransport,
privateKey: "0xabcdef...",
});const contract = getShieldedContract({
abi: myContractAbi,
address: "0x1234...",
client: shieldedWalletClient,
});// Perform a shielded write
await contract.write.myFunction([arg1, arg2], { gas: 50000n });
// Perform a signed read
const value = await contract.read.getValue();
console.log("Value:", value);mapping(address => suint256) private balances; // valid
mapping(uint256 => sbool) private flags; // valid
mapping(address => saddress) private recipients; // valid
mapping(saddress => uint256) private lookup; // INVALID — shielded keymkdir clown-beatdown
cd clown-beatdownmkdir -p packages/contracts packages/clibun init -y && rm index.ts && rm tsconfig.json && touch .prettierrc && touch .gitmodules{
"workspaces": ["packages/**"],
"dependencies": {},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.1",
"prettier": "^3.4.2"
}
}{
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "es5",
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": [
"<TYPES>^(?!@)([^.].*$)</TYPES>",
"<TYPES>^@(.*)$</TYPES>",
"<TYPES>^[./]</TYPES>",
"^(?!@)([^.].*$)",
"^@(.*)$",
"^[./]"
],
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}# Compiler files
cache/
out/
# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
# Docs
docs/
# Dotenv file
.env
node_modules/[submodule "packages/contracts/lib/forge-std"]
path = packages/contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std// This function should ONLY be called via a Seismic transaction
function deposit(suint256 amount) external {
balances[msg.sender] += amount;
}sforge test// BAD: Unnecessary unshielding and re-shielding
suint256 a = /* ... */;
suint256 b = /* ... */;
uint256 temp = uint256(a) + uint256(b); // Both values leaked in trace
suint256 result = suint256(temp);
// GOOD: Stay in the shielded domain
suint256 a = /* ... */;
suint256 b = /* ... */;
suint256 result = a + b; // No values leakedimport { seismicTestnet } from "seismic-react/rainbowkit";import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My App",
projectId: "YOUR_PROJECT_ID",
chains: [seismicTestnet],
});import { http, createConfig } from "wagmi";
import { seismicTestnet } from "seismic-react/rainbowkit";
const config = createConfig({
chains: [seismicTestnet],
transports: {
[seismicTestnet.id]: http(),
},
});import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import { config } from './config'
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}npm install seismic-react seismic-viem wagmi viem @rainbow-me/rainbowkit @tanstack/react-querymapping(address => suint256) balanceOf;
mapping(address => mapping(address => suint256)) allowance;
suint256 totalSupply;
function transfer(address to, suint256 amount) public {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}Deploy a private SRC20 token on Seismic testnet with a single command — no compiler required
bunx create-src20bunx create-src20 \
--name "My Private Token" \
--symbol "MPT" \
--supply 1000000 \
--key 0xYourPrivateKey Create SRC20 Token on Seismic
Deploying to Seismic testnet...
Token deployed!
Address: 0xabc...
Name: My Private Token
Symbol: MPT
Supply: 1,000,000
Owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Tx: 0xdef...
Explorer: https://seismic-testnet.socialscan.io/address/0xabc...Solidity interface reference for SRC20Factory and SRC20Token
contract SRC20Factory {
event TokenCreated(
address indexed creator,
address indexed token,
string name,
string symbol
);
address[] public tokens;
function createToken(
string memory name,
string memory symbol,
uint8 decimals,
suint256 initialSupply
) external returns (address);
function getTokenCount() external view returns (uint256);
}mkdir -p packages/cli/lib
touch packages/cli/lib/constants.ts packages/cli/lib/utils.ts
cd packages/cli/lib_randomIndex()sbytesbytes0x67 — decrypt data with AES-GCMsymboldecimalsownertotalSupplymintburntransfertransferFromapprovebalanceallowancebalanceOfSignedpermitimport { join } from "path";
const CONTRACT_NAME = "ClownBeatdown";
const CONTRACT_DIR = join(__dirname, "../../contracts");
export { CONTRACT_NAME, CONTRACT_DIR };app.ts:suint256 reserve0;
suint256 reserve1;
mapping(address => suint256) liquidity;
function swap(address tokenIn, suint256 amountIn) internal returns (suint256 amountOut) {
if (tokenIn == token0) {
amountOut = (amountIn * reserve1) / (reserve0 + amountIn);
reserve0 += amountIn;
reserve1 -= amountOut;
} else {
amountOut = (amountIn * reserve0) / (reserve1 + amountIn);
reserve1 += amountIn;
reserve0 -= amountOut;
}
}import "@openzeppelin/contracts/access/AccessControl.sol";
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
mapping(address => suint256) balanceOf;
function getBalance(address account) public view returns (uint256) {
require(
msg.sender == account || hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized"
);
return uint256(balanceOf[account]);
}
function transfer(address to, suint256 amount) public {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}mapping(address => sbool) hasVoted;
suint256 yesVotes;
suint256 noVotes;
uint256 public votingEnd;
function vote(sbool inFavor) public {
require(block.timestamp < votingEnd, "Voting ended");
require(!bool(hasVoted[msg.sender]), "Already voted");
hasVoted[msg.sender] = sbool(true);
if (bool(inFavor)) {
yesVotes += 1s;
} else {
noVotes += 1s;
}
}
function getResults() public view returns (uint256 yes, uint256 no) {
require(block.timestamp >= votingEnd, "Voting still open");
yes = uint256(yesVotes);
no = uint256(noVotes);
}mapping(address => suint256) bids;
suint256 highestBid;
saddress highestBidder;
uint256 public auctionEnd;
function bid(suint256 amount) public {
require(block.timestamp < auctionEnd, "Auction ended");
bids[msg.sender] = amount;
if (uint256(amount) > uint256(highestBid)) {
highestBid = amount;
highestBidder = saddress(msg.sender);
}
}
function getWinner() public view returns (address winner, uint256 amount) {
require(block.timestamp >= auctionEnd, "Auction still open");
winner = address(highestBidder);
amount = uint256(highestBid);
}async function createToken(
client: ShieldedWalletClient,
params: CreateTokenParams,
): Promise<CreateTokenResult>;interface CreateTokenResult {
tokenAddress: Address; // address of the deployed SRC20Token contract
txHash: Hash; // transaction hash
}async function getTokenInfo(
client: PublicClient,
tokenAddress: Address,
): Promise<TokenInfo>;interface TokenInfo {
name: string;
symbol: string;
decimals: number;
owner: Address;
totalSupply: bigint; // in base units
}function getFactoryAddress(chainId: number): Address;const FACTORY_ADDRESSES: Record<number, Address> = {
5124: "0x87F850cbC2cFfac086F20d0d7307E12d06fA2127",
};address token = factory.tokens(0);contract SRC20Token is SRC20 {
address public owner;
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
suint256 _initialSupply,
address _owner
);
function totalSupply() public view returns (uint256);
function mint(address to, suint256 amount) external; // owner only
function burn(address from, suint256 amount) external; // owner only
}import fs from "fs";
import { type ShieldedWalletClient, getShieldedContract } from "seismic-viem";
import { Abi, Address } from "viem";
async function getShieldedContractWithCheck(
walletClient: ShieldedWalletClient,
abi: Abi,
address: Address,
) {
const contract = getShieldedContract({
abi: abi,
address: address,
client: walletClient,
});
const code = await walletClient.getCode({
address: address,
});
if (!code) {
throw new Error("Please deploy contract before running this script.");
}
return contract;
}
function readContractAddress(broadcastFile: string): `0x${string}` {
const broadcast = JSON.parse(fs.readFileSync(broadcastFile, "utf8"));
if (!broadcast.transactions?.[0]?.contractAddress) {
throw new Error("Invalid broadcast file format");
}
return broadcast.transactions[0].contractAddress;
}
function readContractABI(abiFile: string): Abi {
const abi = JSON.parse(fs.readFileSync(abiFile, "utf8"));
if (!abi.abi) {
throw new Error("Invalid ABI file format");
}
return abi.abi;
}
export { getShieldedContractWithCheck, readContractAddress, readContractABI };suint256 a = suint256(10);
suint256 b = suint256(3);
// == EXAMPLES
a > b // sbool(true)
a | b // suint256(11)
a << 2 // suint256(40)
a % b // suint256(1)sint256 a = sint256(-10);
sint256 b = sint256(3);
// == EXAMPLES
a < b // sbool(true)
a + b // sint256(-7)
a * b // sint256(-30)sbool a = sbool(true);
sbool b = sbool(false);
// == EXAMPLES
a && b // sbool(false)
!b // sbool(true)saddress a = saddress(0x123);
saddress b = saddress(0x456);
// == VALID EXAMPLES
a == b // sbool(false)
b.code
b.codehash
// == INVALID EXAMPLES
a.balance // must cast to address first
a.call("") // must cast to address firstsbytes32 a = sbytes32(0xabcd);
sbytes1 b = sbytes1(0xff);suint256 a = suint256(42); // explicit cast
suint256 b = 42s; // s suffix — same resultimport { sanvil } from "seismic-react/rainbowkit";import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { sanvil } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My App",
projectId: "YOUR_PROJECT_ID",
chains: [sanvil],
});import { http, createConfig } from "wagmi";
import { sanvil } from "seismic-react/rainbowkit";
const config = createConfig({
chains: [sanvil],
transports: {
[sanvil.id]: http(),
},
});import { localSeismicDevnet } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My App",
projectId: "YOUR_PROJECT_ID",
chains: [localSeismicDevnet],
});curl -L https://raw.githubusercontent.com/SeismicSystems/seismic-foundry/seismic/sfoundryup/install | bash
sfoundryupsanvil# Assuming you are in packages/cli/lib
cd ../src
touch app.tsimport {
type ShieldedContract,
type ShieldedWalletClient,
createShieldedWalletClient,
} from "seismic-viem";
import { Abi, Address, Chain, http, hexToString } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { getShieldedContractWithCheck } from "../lib/utils";interface AppConfig {
players: Array<{
name: string; // Name of the player
privateKey: string; // Private key for the player's wallet
}>;
wallet: {
chain: Chain; // Blockchain network (e.g., Seismic Testnet or sanvil)
rpcUrl: string; // RPC URL for blockchain communication
};
contract: {
abi: Abi; // The contract's ABI for interaction
address: Address; // The contract's deployed address
};
}export class App {
private config: AppConfig; // Holds all app configuration
private playerClients: Map<string, ShieldedWalletClient> = new Map(); // Maps player names to their wallet clients
private playerContracts: Map<string, ShieldedContract> = new Map(); // Maps player names to their contract instances
constructor(config: AppConfig) {
this.config = config;
}
}async init() {
for (const player of this.config.players) {
// Create a wallet client for the player
const walletClient = await createShieldedWalletClient({
chain: this.config.wallet.chain,
transport: http(this.config.wallet.rpcUrl),
account: privateKeyToAccount(player.privateKey as `0x${string}`),
})
this.playerClients.set(player.name, walletClient) // Map the client to the player
// Initialize the player's contract instance and ensure the contract is deployed
const contract = await getShieldedContractWithCheck(
walletClient,
this.config.contract.abi,
this.config.contract.address
)
this.playerContracts.set(player.name, contract) // Map the contract to the player
}
}private getPlayerContract(playerName: string): ShieldedContract {
const contract = this.playerContracts.get(playerName)
if (!contract) {
throw new Error(`Shielded contract for player ${playerName} not found`)
}
return contract
}async reset(playerName: string) {
console.log(`- Player ${playerName} writing reset()`)
const contract = this.getPlayerContract(playerName)
await contract.write.reset([])
}async hit(playerName: string) {
console.log(`- Player ${playerName} writing hit()`)
const contract = this.getPlayerContract(playerName)
await contract.write.hit([])
}async rob(playerName: string) {
console.log(`- Player ${playerName} reading rob()`)
const contract = this.getPlayerContract(playerName)
const result = await contract.read.rob() // signed read
const decoded = hexToString(result as `0x${string}`)
console.log(`- Player ${playerName} robbed secret:`, decoded)
}import { useShieldedWallet } from "seismic-react";import { useShieldedWallet } from 'seismic-react'
function WalletInfo() {
const { address, walletClient, publicClient } = useShieldedWallet()
if (!walletClient) return <div>Connect your wallet</div>
return <div>Connected: {address}</div>
}import { useShieldedWallet } from 'seismic-react'
function App() {
const { loaded, walletClient, address } = useShieldedWallet()
if (!loaded) {
return <div>Initializing shielded wallet...</div>
}
if (!walletClient) {
return <div>Please connect your wallet</div>
}
return <div>Wallet ready: {address}</div>
}import { useShieldedWallet } from 'seismic-react'
function WalletStatus() {
const { loaded, error, walletClient } = useShieldedWallet()
if (!loaded) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
if (!walletClient) return <div>Not connected</div>
return <div>Wallet connected</div>
}import { useShieldedWallet } from 'seismic-react'
function DirectClientUsage() {
const { publicClient, walletClient } = useShieldedWallet()
async function getBlockNumber() {
if (!publicClient) return
const block = await publicClient.getBlockNumber()
console.log('Current block:', block)
}
async function sendRawTransaction() {
if (!walletClient) return
const hash = await walletClient.sendTransaction({
to: '0x...',
value: 0n,
})
console.log('Transaction hash:', hash)
}
return (
<div>
<button onClick={getBlockNumber}>Get Block</button>
<button onClick={sendRawTransaction}>Send Tx</button>
</div>
)
}const { walletClient, loaded } = useShieldedWallet()
if (!loaded) return <div>Loading wallet...</div>
if (!walletClient) return <div>Please connect your wallet</div>const { writeContract, isLoading } = useShieldedWriteContract({ address, abi, functionName: 'transfer', args })
return (
<button onClick={writeContract} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Transfer'}
</button>
)const { signedRead, error } = useSignedReadContract({
address,
abi,
functionName: "balanceOf",
});
async function fetchBalance() {
const result = await signedRead();
if (error) {
console.error("Read failed:", error.message);
}
}import {
seismicTestnet,
sanvil,
localSeismicDevnet,
createSeismicDevnet,
} from "seismic-react/rainbowkit";import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My App",
projectId: "YOUR_PROJECT_ID",
chains: [seismicTestnet],
});Are you deploying to the public testnet?
-> Use seismicTestnet
Are you developing locally with Sanvil?
-> Use sanvil
Are you running a local seismic-reth node in --dev mode?
-> Use localSeismicDevnet
Are you connecting to a custom or self-hosted Seismic node?
-> Use createSeismicDevnet()WagmiProvider
└─ QueryClientProvider
└─ [Wallet Provider] (RainbowKit / Privy / AppKit)
└─ ShieldedWalletProvider
└─ Your Appimport { seismicTestnet } from "seismic-react/rainbowkit";class PublicContract:
def __init__(
self,
w3: Web3,
address: ChecksumAddress,
abi: list[dict[str, Any]],
) -> None:
...from seismic_web3 import create_public_client, PublicContract
# Create client without private key
w3 = create_public_client(
rpc_url="https://testnet-1.seismictest.net/rpc",
)
# Create read-only contract instance
contract = PublicContract(
w3=w3,
address="0x1234567890123456789012345678901234567890",
abi=CONTRACT_ABI,
)
# Read public contract state (auto-decoded)
total_supply = contract.tread.totalSupply() # int
print(f"Total supply: {total_supply}")
balance = contract.tread.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") # int
print(f"Balance: {balance}")# Single return values are returned directly
number = contract.tread.getNumber() # int
name = contract.tread.getName() # str
is_active = contract.tread.isActive() # bool
# Multiple outputs are returned as a tuple
user_name, user_balance, active = contract.tread.getUserInfo(
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
)# Read array of addresses (auto-decoded to list)
holders = contract.tread.getHolders()
print(f"Found {len(holders)} holders")
for holder in holders:
print(f" - {holder}")try:
value = contract.tread.getNumber()
print(f"Value: {value}")
except ValueError as e:
print(f"RPC error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")import { createSeismicDevnet } from "seismic-react/rainbowkit";import { createSeismicDevnet } from "seismic-react/rainbowkit";
const myDevnet = createSeismicDevnet({
nodeHost: "my-node.example.com",
});
// RPC: https://my-node.example.com/rpc
// WSS: wss://my-node.example.com/wsimport { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { createSeismicDevnet } from "seismic-react/rainbowkit";
const myChain = createSeismicDevnet({
nodeHost: "my-node.example.com",
});
const config = getDefaultConfig({
appName: "My App",
projectId: "YOUR_PROJECT_ID",
chains: [myChain],
});import { http, createConfig } from "wagmi";
import { createSeismicDevnet } from "seismic-react/rainbowkit";
const myChain = createSeismicDevnet({
nodeHost: "my-node.example.com",
});
const config = createConfig({
chains: [myChain],
transports: {
[myChain.id]: http(),
},
});const myChain = createSeismicDevnet({
nodeHost: "my-node.example.com",
explorerUrl: "https://explorer.example.com",
});packages/contracts/test/ClownBeatdown.t.sol// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {ClownBeatdown} from "../src/ClownBeatdown.sol";
contract ClownBeatdownTest is Test {
ClownBeatdown public clownBeatdown;
function setUp() public {
clownBeatdown = new ClownBeatdown(2);
clownBeatdown.addSecret("Secret A");
clownBeatdown.addSecret("Secret B");
clownBeatdown.addSecret("Secret C");
}
}uint256 publicNumber = 100;
// Implicit casting -- will NOT compile
suint256 shielded = publicNumber; // Error
// Explicit casting -- correct
suint256 shielded = suint256(publicNumber); // OKsaddress to PayableValidator staking operations on the Seismic deposit contract
npm install seismic-viem viemyarn add seismic-viem viempnpm add seismic-viem viembun add seismic-viem viem(value, is_private)suint256CSTORECLOADis_privatetrueclownStamina), which represents how many hits the clown can withstand before going down. Let's define the stamina and initialize it in the constructor:.env in packages/cli:function test_Hit() public {
clownBeatdown.hit();
assertEq(clownBeatdown.getClownStamina(), 1);
}function test_KnockoutAndRob() public {
clownBeatdown.hit();
clownBeatdown.hit();
// rob() should return one of the secrets
bytes memory secret = clownBeatdown.rob();
assertTrue(
keccak256(secret) == keccak256(bytes("Secret A")) ||
keccak256(secret) == keccak256(bytes("Secret B")) ||
keccak256(secret) == keccak256(bytes("Secret C"))
);
}function test_Reset() public {
clownBeatdown.hit();
clownBeatdown.hit();
clownBeatdown.reset();
assertEq(clownBeatdown.getClownStamina(), 2); // Stamina should be reset to 2
}function test_SecretCanChangeAfterReset() public {
// Knock out and rob in round 1
clownBeatdown.hit();
clownBeatdown.hit();
bytes memory secret1 = clownBeatdown.rob();
// Reset and knock out again in round 2
clownBeatdown.reset();
clownBeatdown.hit();
clownBeatdown.hit();
bytes memory secret2 = clownBeatdown.rob();
// Both should be valid secrets (they may or may not differ depending on randomness)
assertTrue(
keccak256(secret1) == keccak256(bytes("Secret A")) ||
keccak256(secret1) == keccak256(bytes("Secret B")) ||
keccak256(secret1) == keccak256(bytes("Secret C"))
);
assertTrue(
keccak256(secret2) == keccak256(bytes("Secret A")) ||
keccak256(secret2) == keccak256(bytes("Secret B")) ||
keccak256(secret2) == keccak256(bytes("Secret C"))
);
}function test_CannotHitWhenDown() public {
clownBeatdown.hit();
clownBeatdown.hit();
vm.expectRevert("CLOWN_ALREADY_DOWN");
clownBeatdown.hit();
}function test_CannotRobWhenStanding() public {
clownBeatdown.hit();
vm.expectRevert("CLOWN_STILL_STANDING");
clownBeatdown.rob();
}function test_CannotResetWhenStanding() public {
vm.expectRevert("CLOWN_STILL_STANDING");
clownBeatdown.reset();
}function test_RevertWhen_NonContributorTriesToRob() public {
address nonContributor = address(0xabcd);
// Knock out the clown
clownBeatdown.hit();
clownBeatdown.hit();
// Non-contributor should be rejected
vm.prank(nonContributor);
vm.expectRevert("NOT_A_CONTRIBUTOR");
clownBeatdown.rob();
// Original contributor can still rob
bytes memory secret = clownBeatdown.rob();
assertTrue(secret.length > 0);
}function test_ContributorInRound2() public {
address contributorRound2 = address(0xabcd);
// Round 1: knocked out by address(this)
clownBeatdown.hit();
clownBeatdown.hit();
bytes memory secret1 = clownBeatdown.rob();
assertTrue(secret1.length > 0);
// Reset for round 2
clownBeatdown.reset();
// Round 2: knocked out by contributorRound2
vm.prank(contributorRound2);
clownBeatdown.hit();
vm.prank(contributorRound2);
clownBeatdown.hit();
// contributorRound2 can rob in round 2
vm.prank(contributorRound2);
bytes memory secret2 = clownBeatdown.rob();
assertTrue(secret2.length > 0);
// address(this) cannot rob in round 2 (not a contributor this round)
vm.expectRevert("NOT_A_CONTRIBUTOR");
clownBeatdown.rob();
}sforge build
sforge testbool flag = true;
sbool shieldedFlag = sbool(flag); // OK
address addr = msg.sender;
saddress shieldedAddr = saddress(addr); // OK
int256 signed = -42;
sint256 shieldedSigned = sint256(signed); // OK
bytes32 hash = keccak256("secret");
sbytes32 shieldedHash = sbytes32(hash); // OKuint256 publicValue = 100;
suint256 shieldedValue = suint256(publicValue);suint256 shieldedValue = /* ... */;
uint256 publicValue = uint256(shieldedValue);address payable exposed = payable(address(someSaddressValue));
exposed.transfer(1 ether);address payable pay = /* ... */;
saddress shielded = saddress(address(pay));suint128 smaller = suint128(42);
suint256 larger = suint256(smaller); // Widening: safe, no data loss
suint256 big = suint256(1000);
suint128 small = suint128(big); // Narrowing: may truncatesuint256 unsigned = suint256(100);
sint256 signed = sint256(unsigned); // Reinterprets the bitssuint256 private balance;
// Will NOT compile -- cannot return shielded type from external function
function getBalance() external view returns (suint256) { ... }
// Correct -- unshield before returning
function getBalance() external view returns (uint256) {
return uint256(balance);
}suint256 private amount;
function sendToExternal(address externalContract) external {
IExternalContract(externalContract).deposit(uint256(amount));
}// The `amount` parameter arrives encrypted via a Seismic transaction (0x4A)
// and is decrypted inside the TEE -- it is never publicly visible.
function deposit(suint256 amount) external {
balances[msg.sender] += amount;
}// Example: Unintended leak through intermediate cast
suint256 private secretA;
suint256 private secretB;
function leakyCompare() external view returns (bool) {
// BAD: Both secrets are unshielded just to compare them publicly
return uint256(secretA) > uint256(secretB);
}
function safeCompare() external view returns (sbool) {
// BETTER: Compare in the shielded domain, no information leaves
return secretA > secretB;
// Note: sbool cannot be returned from external -- this is illustrative.
// In practice, you would use the comparison result internally.
}// ESM (recommended)
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
// CJS
const { createShieldedWalletClient, seismicTestnet } = require("seismic-viem");mkdir my-seismic-app && cd my-seismic-app
npm init -y
npm install seismic-viem viemimport { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
const blockNumber = await client.getBlockNumber();
console.log("Connected! Block:", blockNumber);npx tsx index.ts{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
}
}npm ls viem
# Should show [email protected]node --version
# Should be >= v18.0.0# Shielded write — encrypted calldata, returns tx hash
tx_hash = contract.write.setNumber(42)
# Shielded read — encrypted signed call, auto-decoded
number = contract.read.getNumber() # int
is_odd = contract.read.isOdd() # bool
# Transparent write — standard send_transaction
tx_hash = contract.twrite.setNumber(42)
# Transparent read — standard eth_call, auto-decoded
number = contract.tread.getNumber() # int
# Debug write — returns plaintext + encrypted views + tx hash
debug = contract.dwrite.setNumber(42)
debug.plaintext_tx.data # unencrypted calldata
debug.shielded_tx.data # encrypted calldata
debug.tx_hash # transaction hashtx_hash = contract.write.deposit(value=10**18, gas=100_000, gas_price=10**9)interface IExampleVault {
// ── Public reads (no msg.sender dependency → .tread) ─────
function getNumber() external view returns (uint256);
function isOdd() external view returns (bool);
function isActive() external view returns (bool);
function getName() external view returns (string memory);
function getConfig() external view returns (uint256 maxDeposit, uint256 feeRate, bool paused);
function getHolders() external view returns (address[] memory);
function getItemCount() external view returns (uint256);
function getItems(uint256 offset, uint256 limit)
external view returns (uint256[] memory);
function getUserInfo(address user)
external view returns (string memory name, uint256 balance, bool active);
// ── Shielded reads (use msg.sender → require .read) ──────
function getSecretBalance() external view returns (suint256);
// ── Writes ───────────────────────────────────────────────
function setNumber(uint256 value) external;
function deposit() external payable;
function withdraw(suint256 amount) external;
function batchTransfer(
address[] calldata recipients,
suint256[] calldata amounts
) external;
}from seismic_web3.contract.abi import encode_shielded_calldata
data = encode_shielded_calldata(abi, "setNumber", [42])// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract ERC20 {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = _initialSupply;
}
function transfer(address to, uint256 amount) public returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public returns (bool) {
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
require(balanceOf[from] >= amount, "Insufficient balance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
return true;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract SRC20 {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply; // stays public
mapping(address => suint256) balanceOf; // CHANGED: uint256 -> suint256, removed public
mapping(address => mapping(address => suint256)) allowance; // CHANGED: uint256 -> suint256, removed public
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = suint256(_initialSupply); // CHANGED: cast to suint256
}
function transfer(address to, suint256 amount) public returns (bool) { // CHANGED: uint256 -> suint256
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, uint256(amount)); // CHANGED: cast back for event
return true;
}
function approve(address spender, suint256 amount) public returns (bool) { // CHANGED: uint256 -> suint256
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, uint256(amount)); // CHANGED: cast back for event
return true;
}
function transferFrom(address from, address to, suint256 amount) public returns (bool) { // CHANGED: uint256 -> suint256
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
require(balanceOf[from] >= amount, "Insufficient balance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, uint256(amount)); // CHANGED: cast back for event
return true;
}
}- mapping(address => uint256) public balanceOf;
+ mapping(address => suint256) balanceOf;- mapping(address => mapping(address => uint256)) public allowance;
+ mapping(address => mapping(address => suint256)) allowance;- function transfer(address to, uint256 amount) public returns (bool) {
+ function transfer(address to, suint256 amount) public returns (bool) {- balanceOf[msg.sender] = _initialSupply;
+ balanceOf[msg.sender] = suint256(_initialSupply);- emit Transfer(msg.sender, to, amount);
+ emit Transfer(msg.sender, to, uint256(amount));mapping(address => suint256) balanceOf;function transfer(address to, suint256 amount) public returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, uint256(amount));
return true;
}function transferFrom(address from, address to, suint256 amount) public returns (bool) {
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
require(balanceOf[from] >= amount, "Insufficient balance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, uint256(amount));
return true;
}mapping(address => mapping(address => suint256)) allowance;function approve(address spender, suint256 amount) public returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, uint256(amount));
return true;
}function mint(address to, suint256 amount) public {
// In production, add access control here (e.g., onlyOwner)
totalSupply += uint256(amount);
balanceOf[to] += amount;
emit Transfer(address(0), to, uint256(amount));
}suint256 totalSupply;constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = suint256(_initialSupply);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {SRC20} from "../src/SRC20.sol";
contract SRC20Test is Test {
SRC20 public token;
address public alice;
address public bob;
function setUp() public {
token = new SRC20("Shielded Token", "SRC", 1000000e18);
alice = address(this); // deployer holds initial supply
bob = address(0xB0B);
}
}function test_Transfer() public {
// Transfer 100 tokens from alice (deployer) to bob
bool success = token.transfer(bob, 100e18s);
assertTrue(success);
// Verify balances using the internal test helper
// In sforge tests, the test contract can read shielded values directly
assertEq(token.getBalanceForTest(bob), 100e18);
assertEq(token.getBalanceForTest(alice), 999900e18);
}// Only for testing -- remove before deployment
function getBalanceForTest(address account) external view returns (uint256) {
return uint256(balanceOf[account]);
}function test_RevertWhen_InsufficientBalance() public {
vm.prank(bob); // bob has no tokens
vm.expectRevert("Insufficient balance");
token.transfer(alice, suint256(1e18));
}function test_TransferFrom() public {
// Alice approves bob to spend 500 tokens
token.approve(bob, 500e18s);
// Bob transfers 200 tokens from alice to himself
vm.prank(bob);
bool success = token.transferFrom(alice, bob, 200e18s);
assertTrue(success);
// Verify balances
assertEq(token.getBalanceForTest(bob), 200e18);
assertEq(token.getBalanceForTest(alice), 999800e18);
}function test_RevertWhen_InsufficientAllowance() public {
token.approve(bob, suint256(50e18));
vm.prank(bob);
vm.expectRevert("Insufficient allowance");
token.transferFrom(alice, bob, suint256(100e18));
}sforge test uint256 initialClownStamina; // Starting stamina restored on reset.
uint256 clownStamina; // Remaining stamina before the clown is down.
constructor(uint256 _clownStamina) {
initialClownStamina = _clownStamina; // Set starting stamina.
clownStamina = _clownStamina; // Initialize remaining stamina.
round = 1; // Start with the first round.
} // Event to log hits.
event Hit(uint256 indexed round, address indexed hitter, uint256 remaining);
// Hit the clown to reduce stamina.
function hit() public requireStanding {
clownStamina--; // Decrease stamina.
emit Hit(round, msg.sender, clownStamina); // Log the hit.
}
// Modifier to ensure the clown is still standing.
modifier requireStanding() {
require(clownStamina > 0, "CLOWN_ALREADY_DOWN");
_;
} // Get the current clown stamina.
function getClownStamina() public view returns (uint256) {
return clownStamina;
} // Reveal secret once the clown is down and the caller contributed.
function rob() public view requireDown returns (bytes memory) {
sbytes memory secret = secrets[uint256(secretIndex)];
return bytes(secret); // Return the randomly selected secret.
}
// Modifier to ensure the clown is down.
modifier requireDown() {
require(clownStamina == 0, "CLOWN_STILL_STANDING");
_;
}// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
contract ClownBeatdown {
uint256 initialClownStamina; // Starting stamina restored on reset.
uint256 clownStamina; // Remaining stamina before the clown is down.
uint256 round; // The current round number.
mapping(uint256 => sbytes) secrets; // Pool of possible secrets (shielded).
uint256 secretsCount; // Number of secrets for modular arithmetic.
suint256 secretIndex; // Shielded index into the secrets mapping.
// Event to log hits.
event Hit(uint256 indexed round, address indexed hitter, uint256 remaining);
constructor(uint256 _clownStamina) {
initialClownStamina = _clownStamina; // Set starting stamina.
clownStamina = _clownStamina; // Initialize remaining stamina.
round = 1; // Start with the first round.
}
// Get the current clown stamina.
function getClownStamina() public view returns (uint256) {
return clownStamina;
}
function addSecret(string memory _secret) public {
secrets[secretsCount] = sbytes(_secret);
secretsCount++;
secretIndex = suint256(_randomIndex()); // Re-pick a random secret.
}
// Hit the clown to reduce stamina.
function hit() public requireStanding {
clownStamina--; // Decrease stamina.
emit Hit(round, msg.sender, clownStamina); // Log the hit.
}
// Reveal secret once the clown is down.
function rob() public view requireDown returns (bytes memory) {
sbytes memory secret = secrets[uint256(secretIndex)];
return bytes(secret); // Return the randomly selected secret.
}
// Generate a pseudo-random index into the secrets array.
function _randomIndex() private view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, round))) % secretsCount;
}
// Modifier to ensure the clown is down.
modifier requireDown() {
require(clownStamina == 0, "CLOWN_STILL_STANDING");
_;
}
// Modifier to ensure the clown is still standing.
modifier requireStanding() {
require(clownStamina > 0, "CLOWN_ALREADY_DOWN");
_;
}
}touch .envCHAIN_ID=31337
VITE_CHAIN_ID=31337
RPC_URL=http://127.0.0.1:8545
ALICE_PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
BOB_PRIVKEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690dimport dotenv from "dotenv";
import { join } from "path";
import { sanvil, seismicTestnet } from "seismic-viem";
import { CONTRACT_DIR, CONTRACT_NAME } from "../lib/constants";
import { readContractABI, readContractAddress } from "../lib/utils";
import { App } from "./app";
// Load environment variables from .env file
dotenv.config();async function main() {
if (!process.env.CHAIN_ID || !process.env.RPC_URL) {
console.error('Please set your environment variables.')
process.exit(1)
}const broadcastFile = join(
CONTRACT_DIR,
"broadcast",
`${CONTRACT_NAME}.s.sol`,
process.env.CHAIN_ID,
"run-latest.json",
);
const abiFile = join(
CONTRACT_DIR,
"out",
`${CONTRACT_NAME}.sol`,
`${CONTRACT_NAME}.json`,
);const chain =
process.env.VITE_CHAIN_ID === sanvil.id.toString() ? sanvil : seismicTestnet;const players = [
{ name: "Alice", privateKey: process.env.ALICE_PRIVKEY! },
{ name: "Bob", privateKey: process.env.BOB_PRIVKEY! },
];const app = new App({
players,
wallet: {
chain,
rpcUrl: process.env.RPC_URL!,
},
contract: {
abi: readContractABI(abiFile),
address: readContractAddress(broadcastFile),
},
});
await app.init();console.log("=== Round 1 ===");
await app.hit("Alice");
await app.hit("Alice");
await app.hit("Alice");
// Alice robs the clown's secret in round 1
await app.rob("Alice");console.log("=== Round 2 ===");
await app.reset("Bob");
await app.hit("Bob");
await app.hit("Bob");
await app.hit("Bob");
// Bob robs the clown's secret in round 2
await app.rob("Bob"); // Alice tries to rob in round 2, should fail by reverting
console.log('=== Testing Access Control ===')
console.log("Attempting Alice's rob() in Bob's round (should revert)")
try {
await app.rob('Alice')
console.error('Expected rob() to revert but it succeeded')
process.exit(1)
} catch (error) {
console.log('Received expected revert')
}
}main();bun dev=== Round 1 ===
- Player Alice writing hit()
- Player Alice writing hit()
- Player Alice writing hit()
- Player Alice reading rob()
- Player Alice robbed secret: The cake is a lie
=== Round 2 ===
- Player Bob writing reset()
- Player Bob writing hit()
- Player Bob writing hit()
- Player Bob writing hit()
- Player Bob reading rob()
- Player Bob robbed secret: 42 is the answer
=== Testing Access Control ===
Attempting Alice's rob() in Bob's round (should revert)
Received expected revertdepositContractWalletActions -- write: deposit()import {
DEPOSIT_CONTRACT_ADDRESS,
depositContractPublicActions,
depositContractWalletActions,
} from "seismic-viem";import {
createShieldedPublicClient,
createShieldedWalletClient,
depositContractPublicActions,
depositContractWalletActions,
seismicTestnet,
} from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
}).extend(depositContractPublicActions);
const walletClient = (
await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
})
).extend(depositContractWalletActions);const depositRoot = await publicClient.getDepositRoot({});const depositCount = await publicClient.getDepositCount({});import { parseEther } from "viem";
const txHash = await walletClient.deposit({
nodePubkey: "0x...", // ED25519 public key (32 bytes)
consensusPubkey: "0x...", // BLS12-381 public key (48 bytes)
withdrawalCredentials: "0x...", // commitment to withdrawal pubkey
nodeSignature: "0x...", // ED25519 signature (64 bytes)
consensusSignature: "0x...", // BLS12-381 signature (96 bytes)
depositDataRoot: "0x...", // SHA-256 of SSZ-encoded DepositData
value: parseEther("32"),
});npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query seismic-react seismic-viemimport { createAppKit } from "@reown/appkit/react";
import { WagmiAdapter } from "@reown/appkit-adapter-wagmi";
import { seismicTestnet } from "seismic-react/rainbowkit";
const projectId = "YOUR_WALLETCONNECT_PROJECT_ID";
const wagmiAdapter = new WagmiAdapter({
projectId,
chains: [seismicTestnet],
networks: [seismicTestnet],
});
createAppKit({
adapters: [wagmiAdapter],
projectId,
networks: [seismicTestnet],
metadata: {
name: "My Seismic App",
description: "A Seismic-powered dApp",
url: "https://myapp.com",
icons: ["https://myapp.com/icon.png"],
},
});import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
const queryClient = new QueryClient()
function App({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={wagmiAdapter.wagmiConfig}>
<QueryClientProvider client={queryClient}>
<ShieldedWalletProvider config={wagmiAdapter.wagmiConfig}>
{children}
</ShieldedWalletProvider>
</QueryClientProvider>
</WagmiProvider>
)
}function Header() {
return (
<header>
<appkit-button />
</header>
)
}import { useShieldedWallet } from 'seismic-react'
function MyComponent() {
const { walletClient, loaded, error } = useShieldedWallet()
if (!loaded) return <div>Initializing shielded wallet...</div>
if (error) return <div>Error: {error}</div>
if (!walletClient) return <div>Connect your wallet to get started.</div>
return <div>Shielded wallet ready!</div>
}'use client'
import { createAppKit } from '@reown/appkit/react'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider, useShieldedWallet } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const projectId = 'YOUR_WALLETCONNECT_PROJECT_ID'
const wagmiAdapter = new WagmiAdapter({
projectId,
chains: [seismicTestnet],
networks: [seismicTestnet],
})
createAppKit({
adapters: [wagmiAdapter],
projectId,
networks: [seismicTestnet],
metadata: {
name: 'My Seismic App',
description: 'A Seismic-powered dApp',
url: 'https://myapp.com',
icons: ['https://myapp.com/icon.png'],
},
})
const queryClient = new QueryClient()
function WalletStatus() {
const { walletClient, publicClient, loaded, error } = useShieldedWallet()
if (!loaded) return <p>Initializing shielded wallet...</p>
if (error) return <p>Error: {error}</p>
if (!walletClient) return <p>Connect your wallet to get started.</p>
return (
<div>
<p>Shielded wallet ready</p>
<p>Public client: {publicClient ? 'Available' : 'Loading...'}</p>
</div>
)
}
export default function App() {
return (
<WagmiProvider config={wagmiAdapter.wagmiConfig}>
<QueryClientProvider client={queryClient}>
<ShieldedWalletProvider config={wagmiAdapter.wagmiConfig}>
<appkit-button />
<WalletStatus />
</ShieldedWalletProvider>
</QueryClientProvider>
</WagmiProvider>
)
}class AsyncPublicContract:
def __init__(
self,
w3: AsyncWeb3,
address: ChecksumAddress,
abi: list[dict[str, Any]],
) -> None:
...import asyncio
from seismic_web3 import create_async_public_client, AsyncPublicContract
async def main():
# Create async client without private key
w3 = create_async_public_client(
provider_url="https://testnet-1.seismictest.net/rpc",
)
# Create read-only contract instance
contract = AsyncPublicContract(
w3=w3,
address="0x1234567890123456789012345678901234567890",
abi=CONTRACT_ABI,
)
# Read public contract state (must await, auto-decoded)
total_supply = await contract.tread.totalSupply() # int
print(f"Total supply: {total_supply}")
balance = await contract.tread.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") # int
print(f"Balance: {balance}")
asyncio.run(main())async def concurrent_reads(contract: AsyncPublicContract):
# Execute multiple reads concurrently (all auto-decoded)
total_supply, decimals, symbol, name = await asyncio.gather(
contract.tread.totalSupply(),
contract.tread.decimals(),
contract.tread.symbol(),
contract.tread.name(),
)
print(f"Name: {name}")
print(f"Symbol: {symbol}")
print(f"Decimals: {decimals}")
print(f"Supply: {total_supply}")async def return_types(contract: AsyncPublicContract):
# Single output values are returned directly
number = await contract.tread.getNumber() # int
name = await contract.tread.getName() # str
active = await contract.tread.isActive() # bool
# Multiple outputs return a tuple
user_name, balance, is_active = await contract.tread.getUserInfo(
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
)async def array_example(contract: AsyncPublicContract):
# Read array of addresses (auto-decoded to list)
holders = await contract.tread.getHolders()
print(f"Found {len(holders)} holders")
# Query additional data for each holder concurrently
balances = await asyncio.gather(
*[contract.tread.balanceOf(holder) for holder in holders]
)
for holder, balance in zip(holders, balances):
print(f" {holder}: {balance}")async def error_handling(contract: AsyncPublicContract):
try:
value = await contract.tread.getNumber()
print(f"Value: {value}")
except ValueError as e:
print(f"RPC error: {e}")
except asyncio.TimeoutError:
print("Request timed out")
except Exception as e:
print(f"Unexpected error: {e}")Install seismic-react and configure peer dependencies
React hooks and providers for Seismic, composing with wagmi to add shielded wallet management, encrypted transactions, and signed reads to React apps.
npm install seismic-reactimport { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
<YourApp />
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}Set up RainbowKit with Seismic for wallet connection
Python SDK for Seismic, built on web3.py
pip install seismic-web3uv add seismic-web3import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Wallet client — full capabilities (requires private key)
w3 = SEISMIC_TESTNET.wallet_client(pk)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Shielded write — calldata is encrypted
tx_hash = contract.write.setNumber(42)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
# Signed read — encrypted eth_call, proves your identity
result = contract.read.getNumber()# Public client — read-only (no private key needed)
public = SEISMIC_TESTNET.public_client()
contract = public.seismic.contract(address="0x...", abi=ABI)
result = contract.tread.getNumber()Creating sync and async Seismic clients
w3.seismic)w3.seismic).treadifelse// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SRC20 is AccessControl {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => suint256) balanceOf;
mapping(address => mapping(address => suint256)) allowance;
mapping(address => bool) public frozen;
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
bytes32 public constant AUDITOR_ROLE = keccak256("AUDITOR_ROLE");
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
event AccountFrozen(address indexed account);
event AccountUnfrozen(address indexed account);
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = suint256(_initialSupply);
// Deployer gets admin role and can grant other roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// --- Standard token functions (with freeze check) ---
function transfer(address to, suint256 amount) public returns (bool) {
require(!frozen[msg.sender], "Account frozen");
require(!frozen[to], "Recipient frozen");
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, uint256(amount));
return true;
}
function approve(address spender, suint256 amount) public returns (bool) {
require(!frozen[msg.sender], "Account frozen");
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, uint256(amount));
return true;
}
function transferFrom(address from, address to, suint256 amount) public returns (bool) {
require(!frozen[from], "Account frozen");
require(!frozen[to], "Recipient frozen");
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
require(balanceOf[from] >= amount, "Insufficient balance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, uint256(amount));
return true;
}
// --- User balance query (signed read) ---
function getBalance(address account) external view returns (uint256) {
require(msg.sender == account, "Only owner can view balance");
return uint256(balanceOf[account]);
}
// --- Compliance functions ---
function complianceBalanceOf(address account) external view returns (uint256) {
require(
hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized: requires COMPLIANCE_ROLE"
);
return uint256(balanceOf[account]);
}
function complianceFreeze(address account) external {
require(
hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized: requires COMPLIANCE_ROLE"
);
frozen[account] = true;
emit AccountFrozen(account);
}
function complianceUnfreeze(address account) external {
require(
hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized: requires COMPLIANCE_ROLE"
);
frozen[account] = false;
emit AccountUnfrozen(account);
}
// --- Auditor functions ---
function auditBalanceOf(address account) external view returns (uint256) {
require(
hasRole(AUDITOR_ROLE, msg.sender),
"Not authorized: requires AUDITOR_ROLE"
);
return uint256(balanceOf[account]);
}
}// Grant compliance role to a specific address
token.grantRole(COMPLIANCE_ROLE, complianceOfficerAddress);
// Grant auditor role
token.grantRole(AUDITOR_ROLE, auditorAddress);const token = getShieldedContract({
abi: src20Abi,
address: SRC20_ADDRESS,
client: adminWalletClient,
});
// Grant compliance role
await token.write.grantRole([COMPLIANCE_ROLE, complianceOfficerAddress]);const complianceClient = await createShieldedWalletClient({
chain: seismicDevnet,
transport: http(RPC_URL),
account: privateKeyToAccount(COMPLIANCE_OFFICER_KEY),
});
const complianceToken = getShieldedContract({
abi: src20Abi,
address: SRC20_ADDRESS,
client: complianceClient,
});
// This is a signed read -- response encrypted to the compliance officer's key
const balance = await complianceToken.read.complianceBalanceOf([targetAccount]);
console.log("Account balance:", balance);npm install seismic-viem seismic-react viem wagmi @tanstack/react-queryimport { ShieldedWalletProvider } from "seismic-react";
import { WagmiProvider, createConfig, http } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { seismicTestnet } from "seismic-viem";
const config = createConfig({
chains: [seismicTestnet],
transports: {
[seismicTestnet.id]: http("https://testnet-1.seismictest.net/rpc"),
},
});
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<ShieldedWalletProvider config={config}>
<TokenDashboard />
</ShieldedWalletProvider>
</QueryClientProvider>
</WagmiProvider>
);
}import { useShieldedContract } from "seismic-react";
import { src20Abi } from "./abi";
const SRC20_ADDRESS = "0x1234..."; // Your deployed contract address
function useToken() {
const contract = useShieldedContract({
address: SRC20_ADDRESS,
abi: src20Abi,
});
return contract;
}import { useSignedReadContract } from 'seismic-react';
import { useAccount } from 'wagmi';
import { useEffect, useState } from 'react';
import { formatEther } from 'viem';
function BalanceDisplay() {
const { address } = useAccount();
const [balance, setBalance] = useState<bigint | null>(null);
const { signedRead, isLoading, error } = useSignedReadContract({
address: SRC20_ADDRESS,
abi: src20Abi,
functionName: 'balanceOf',
args: [address],
});
useEffect(() => {
if (signedRead) {
signedRead().then(setBalance);
}
}, [signedRead]);
if (isLoading) return <p>Loading balance...</p>;
if (error) return <p>Error loading balance</p>;
return (
<div>
<h2>Your Balance</h2>
<p>{formatEther(balance ?? 0n)} SRC</p>
</div>
);
}import { useShieldedWriteContract } from 'seismic-react';
import { parseEther } from 'viem';
import { useState } from 'react';
function TransferForm() {
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const { writeContract, isLoading, error, hash } = useShieldedWriteContract();
const handleTransfer = () => {
writeContract({
address: SRC20_ADDRESS,
abi: src20Abi,
functionName: 'transfer',
args: [recipient, parseEther(amount)],
});
};
return (
<div>
<h2>Transfer Tokens</h2>
<input
type="text"
placeholder="Recipient address (0x...)"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
<input
type="text"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<button onClick={handleTransfer} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Transfer'}
</button>
{hash && <p>Transfer submitted: {hash}</p>}
{error && <p>Error: {error.message}</p>}
</div>
);
}import { useAccount } from "wagmi";
import { BalanceDisplay } from "./BalanceDisplay";
import { TransferForm } from "./TransferForm";
function TokenDashboard() {
const { address, isConnected } = useAccount();
if (!isConnected) {
return (
<div>
<h1>SRC20 Token Dashboard</h1>
<p>Connect your wallet to get started.</p>
{/* Add your wallet connect button here (e.g., RainbowKit, Privy, AppKit) */}
</div>
);
}
return (
<div>
<h1>SRC20 Token Dashboard</h1>
<p>Connected: {address}</p>
<BalanceDisplay />
<TransferForm />
</div>
);
} // Event to log resets.
event Reset(uint256 indexed newRound, uint256 remainingClownStamina);
// Reset the beatdown for a new round.
function reset() public requireDown {
clownStamina = initialClownStamina; // Reset stamina.
secretIndex = suint256(_randomIndex()); // Pick a new random secret.
round++; // Move to the next round.
emit Reset(round, clownStamina); // Log the reset.
} // Tracks the number of hits per player per round.
mapping(uint256 => mapping(address => uint256)) hitsPerRound; // Hit the clown to reduce stamina.
function hit() public requireStanding {
clownStamina--; // Decrease stamina.
hitsPerRound[round][msg.sender]++; // Record the player's hit for the current round.
emit Hit(round, msg.sender, clownStamina); // Log the hit.
} // Modifier to ensure the caller has contributed in the current round.
modifier onlyContributor() {
require(hitsPerRound[round][msg.sender] > 0, "NOT_A_CONTRIBUTOR");
_;
} // Reveal secret once the clown is down and the caller contributed.
function rob() public view requireDown onlyContributor returns (bytes memory) {
sbytes memory secret = secrets[uint256(secretIndex)];
return bytes(secret); // Return the randomly selected secret.
}// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
contract ClownBeatdown {
uint256 initialClownStamina; // Starting stamina restored on reset.
uint256 clownStamina; // Remaining stamina before the clown is down.
uint256 round; // The current round number.
mapping(uint256 => sbytes) secrets; // Pool of possible secrets (shielded).
uint256 secretsCount; // Number of secrets for modular arithmetic.
suint256 secretIndex; // Shielded index into the secrets mapping.
// Tracks the number of hits per player per round.
mapping(uint256 => mapping(address => uint256)) hitsPerRound;
// 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.
// Event to log resets.
event Reset(uint256 indexed newRound, uint256 remainingClownStamina);
constructor(uint256 _clownStamina) {
initialClownStamina = _clownStamina; // Set starting stamina.
clownStamina = _clownStamina; // Initialize remaining stamina.
round = 1; // Start with the first round.
}
// Get the current clown stamina.
function getClownStamina() public view returns (uint256) {
return clownStamina;
}
function addSecret(string memory _secret) public {
secrets[secretsCount] = sbytes(_secret);
secretsCount++;
secretIndex = suint256(_randomIndex()); // Re-pick a random secret.
}
// Hit the clown to reduce stamina.
function hit() public requireStanding {
clownStamina--; // Decrease stamina.
hitsPerRound[round][msg.sender]++; // Record the player's hit for the current round.
emit Hit(round, msg.sender, clownStamina); // Log the hit.
}
// Reset the beatdown for a new round.
function reset() public requireDown {
clownStamina = initialClownStamina; // Reset stamina.
secretIndex = suint256(_randomIndex()); // Pick a new random secret.
round++; // Move to the next round.
emit Reset(round, clownStamina); // Log the reset.
}
// Reveal secret once the clown is down and the caller contributed.
function rob() public view requireDown onlyContributor returns (bytes memory) {
sbytes memory secret = secrets[uint256(secretIndex)];
return bytes(secret); // Return the randomly selected secret.
}
// Generate a pseudo-random index into the secrets array.
function _randomIndex() private view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, round))) % secretsCount;
}
// Modifier to ensure the clown is down.
modifier requireDown() {
require(clownStamina == 0, "CLOWN_STILL_STANDING");
_;
}
// Modifier to ensure the clown is still standing.
modifier requireStanding() {
require(clownStamina > 0, "CLOWN_ALREADY_DOWN");
_;
}
// Modifier to ensure the caller has contributed in the current round.
modifier onlyContributor() {
require(hitsPerRound[round][msg.sender] > 0, "NOT_A_CONTRIBUTOR");
_;
}
}npm install seismic-reactyarn add seismic-reactpnpm add seismic-reactbun add seismic-reactnpm install react wagmi viem seismic-viem @tanstack/react-querynpm install @rainbow-me/rainbowkitimport { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My Seismic App",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [seismicTestnet],
});import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
<YourApp />
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// Main entry
import {
ShieldedWalletProvider,
useShieldedWallet,
useShieldedContract,
useShieldedWriteContract,
useSignedReadContract,
} from "seismic-react";
// Chain configs for RainbowKit
import { seismicTestnet, sanvil } from "seismic-react/rainbowkit";npm install seismic-viemnpm install wagmi@latest viem@latest// Correct
import { seismicTestnet } from "seismic-react/rainbowkit";
// Incorrect -- will not resolve
import { seismicTestnet } from "seismic-react";{
"resolutions": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}npm install seismic-react seismic-viem wagmi viem @rainbow-me/rainbowkit @tanstack/react-query// config.ts
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
export const config = getDefaultConfig({
appName: "Seismic Basic dApp",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [seismicTestnet],
});// providers.tsx
'use client'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import { config } from './config'
import '@rainbow-me/rainbowkit/styles.css'
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// ShieldedCounter.tsx
'use client'
import { useShieldedWriteContract, useSignedReadContract } from 'seismic-react'
import { useState } from 'react'
const CONTRACT_ADDRESS = '0x...' as const
const ABI = [
{
name: 'increment',
type: 'function',
stateMutability: 'nonpayable',
inputs: [],
outputs: [],
},
{
name: 'getCount',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'uint256' }],
},
] as const
export function ShieldedCounter() {
const [count, setCount] = useState<string | null>(null)
const { writeContract, isLoading: isWriting, hash, error: writeError } = useShieldedWriteContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'increment',
})
const { signedRead, isLoading: isReading, error: readError } = useSignedReadContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'getCount',
})
const handleIncrement = async () => {
try {
await writeContract()
} catch (err) {
console.error('Shielded write failed:', err)
}
}
const handleRead = async () => {
try {
const result = await signedRead()
setCount(result?.toString() ?? 'unknown')
} catch (err) {
console.error('Signed read failed:', err)
}
}
return (
<div>
<h2>Shielded Counter</h2>
<button onClick={handleIncrement} disabled={isWriting}>
{isWriting ? 'Sending...' : 'Increment (Shielded Write)'}
</button>
{hash && <p>Tx hash: {hash}</p>}
{writeError && <p>Write error: {writeError.message}</p>}
<button onClick={handleRead} disabled={isReading}>
{isReading ? 'Reading...' : 'Get Count (Signed Read)'}
</button>
{count !== null && <p>Count: {count}</p>}
{readError && <p>Read error: {readError.message}</p>}
</div>
)
}// App.tsx
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { useShieldedWallet } from 'seismic-react'
import { ShieldedCounter } from './ShieldedCounter'
export function App() {
const { loaded, error } = useShieldedWallet()
return (
<div>
<h1>Seismic Basic dApp</h1>
<ConnectButton />
{error && <p>Wallet error: {error}</p>}
{loaded ? (
<ShieldedCounter />
) : (
<p>Connect your wallet to interact with shielded contracts.</p>
)}
</div>
)
}User connects wallet
|
v
ShieldedWalletProvider creates shielded clients (ECDH with TEE)
|
v
useShieldedWriteContract useSignedReadContract
- encrypts calldata - signs the read request
- sends TxSeismic - node verifies identity
- returns tx hash - returns decrypted resultimport { useSignedReadContract } from 'seismic-react'
import { useState } from 'react'
const abi = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'uint256' }],
},
] as const
function ShieldedBalance() {
const [balance, setBalance] = useState<string | null>(null)
const { signedRead, isLoading, error } = useSignedReadContract({
address: '0x1234567890abcdef1234567890abcdef12345678',
abi,
functionName: 'balanceOf',
})
async function fetchBalance() {
const result = await signedRead()
if (result !== undefined) {
setBalance(result.toString())
}
}
return (
<div>
<button onClick={fetchBalance} disabled={isLoading}>
{isLoading ? 'Reading...' : 'Get Balance'}
</button>
{balance && <p>Balance: {balance}</p>}
{error && <p>Error: {error.message}</p>}
</div>
)
}import { useSignedReadContract } from 'seismic-react'
function ReadWithLoadingState() {
const { signedRead, isLoading, error } = useSignedReadContract({
address: CONTRACT_ADDRESS,
abi,
functionName: 'getSecret',
})
return (
<div>
<button onClick={signedRead} disabled={isLoading}>
{isLoading ? 'Fetching...' : 'Read Secret'}
</button>
{isLoading && <span>Please wait, signing and decrypting...</span>}
</div>
)
}import { useSignedReadContract } from 'seismic-react'
function ReadWithErrorHandling() {
const { signedRead, error } = useSignedReadContract({
address: CONTRACT_ADDRESS,
abi,
functionName: 'balanceOf',
})
async function handleRead() {
try {
const result = await signedRead()
console.log('Result:', result)
} catch (e) {
console.error('Signed read failed:', e)
}
}
return (
<div>
<button onClick={handleRead}>Read</button>
{error && <p style={{ color: 'red' }}>Last error: {error.message}</p>}
</div>
)
}import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My Seismic App",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [seismicTestnet],
});import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import '@rainbow-me/rainbowkit/styles.css'
const queryClient = new QueryClient()
function App({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}import { ConnectButton } from '@rainbow-me/rainbowkit'
function Header() {
return (
<header>
<ConnectButton />
</header>
)
}import { useShieldedWallet } from 'seismic-react'
function MyComponent() {
const { walletClient, loaded, error } = useShieldedWallet()
if (!loaded) return <div>Initializing shielded wallet...</div>
if (error) return <div>Error: {error}</div>
return <div>Connected!</div>
}// app/providers.tsx
'use client'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
import '@rainbow-me/rainbowkit/styles.css'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet, sanvil } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My Seismic App",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [
...(process.env.NODE_ENV === "development" ? [sanvil] : []),
seismicTestnet,
],
});'use client'
import { RainbowKitProvider, ConnectButton, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider, useShieldedWallet } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
import '@rainbow-me/rainbowkit/styles.css'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
function WalletStatus() {
const { walletClient, publicClient, loaded, error } = useShieldedWallet()
if (!loaded) return <p>Initializing shielded wallet...</p>
if (error) return <p>Error: {error}</p>
if (!walletClient) return <p>Connect your wallet to get started.</p>
return (
<div>
<p>Shielded wallet ready</p>
<p>Public client: {publicClient ? 'Available' : 'Loading...'}</p>
</div>
)
}
export default function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
<ConnectButton />
<WalletStatus />
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}pip install seismic-web3uv add seismic-web3import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# HTTP
w3 = await SEISMIC_TESTNET.async_wallet_client(pk)
# WebSocket (auto-selects ws_url from chain config)
w3 = await SEISMIC_TESTNET.async_wallet_client(pk, ws=True)from seismic_web3 import SEISMIC_TESTNET
# Sync
public = SEISMIC_TESTNET.public_client()
# Async
public = SEISMIC_TESTNET.async_public_client()import os
from seismic_web3 import SEISMIC_TESTNET, SANVIL, PrivateKey
# Seismic testnet
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Sanvil testnet
w3 = SANVIL.wallet_client(pk)import { useShieldedContract } from 'seismic-react'
const abi = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'uint256' }],
},
{
name: 'transfer',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [],
},
] as const
function MyContract() {
const { contract, error } = useShieldedContract({
abi,
address: '0x1234567890abcdef1234567890abcdef12345678',
})
if (error) return <div>Error: {error.message}</div>
if (!contract) return <div>Loading contract...</div>
return <div>Contract ready</div>
}import { useShieldedContract } from 'seismic-react'
import { useState } from 'react'
function TokenActions() {
const [balance, setBalance] = useState<bigint | null>(null)
const { contract } = useShieldedContract({ abi, address: CONTRACT_ADDRESS })
async function readBalance() {
if (!contract) return
const result = await contract.read.balanceOf()
setBalance(result as bigint)
}
async function transfer() {
if (!contract) return
const hash = await contract.write.transfer(['0xRecipient...', 100n])
console.log('Transfer tx:', hash)
}
return (
<div>
<button onClick={readBalance}>Check Balance</button>
{balance !== null && <p>Balance: {balance.toString()}</p>}
<button onClick={transfer}>Transfer</button>
</div>
)
}const abi = [
{
name: "increment",
type: "function",
stateMutability: "nonpayable",
inputs: [],
outputs: [],
},
{
name: "number",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "uint256" }],
},
] as const;
// TypeScript now infers the available methods and their argument/return types
const { contract } = useShieldedContract({ abi, address: "0x..." });cd packages
bun create vite web --template react-ts
cd webbun add [email protected] [email protected] viem@^2.22.3 \
wagmi@^2.0.0 @rainbow-me/rainbowkit@^2.0.0 \
@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-redux@^9.2.0 @reduxjs/toolkit@^2.5.1 \
@tailwindcss/vite tailwindcss@^4bun add -d @vitejs/plugin-react-swcimport { resolve } from "path";
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
envDir: resolve(__dirname, "../.."),
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});VITE_CHAIN_ID=31337
VITE_RPC_URL=http://127.0.0.1:8545
VITE_FAUCET_URL=https://faucet-2.seismicdev.net/import React from 'react'
import { PropsWithChildren, useCallback } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import {
type OnAddressChangeParams,
ShieldedWalletProvider,
} from 'seismic-react'
import { sanvil, seismicTestnet } from 'seismic-react/rainbowkit'
import { http } from 'viem'
import { type 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 './App.css'
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]
const config = getDefaultConfig({
appName: 'Seismic Starter',
projectId: 'd705c8eaf9e6f732e1ddb8350222cdac',
// @ts-expect-error: this is fine
chains: CHAINS,
ssr: false,
})
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)}`],
})
},
[]
)
return (
<WagmiProvider config={config}>
<QueryClientProvider client={client}>
<RainbowKitProvider>
<ShieldedWalletProvider
config={config}
options={{
publicTransport,
publicChain,
onAddressChange: handleAddressChange,
}}
>
<AuthProvider>{children}</AuthProvider>
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}
const App: React.FC = () => {
return (
<BrowserRouter>
<Providers config={config}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Providers>
</BrowserRouter>
)
}
export default Appimport { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {},
})import { createTheme } from '@mui/material/styles'
const theme = createTheme({
palette: {
mode: 'dark',
},
})
export default themeimport ClownPuncher from '@/components/game/ClownPuncher'
const Home = () => <ClownPuncher />
export default Homeconst NotFound = () => <div>404 - Page not found</div>
export default NotFound@import "tailwindcss";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>
)// BAD: Leaks the value of `isVIP` via gas difference
sbool isVIP = /* ... */;
if (isVIP) {
discount = 50s;
} else {
// extra work only in the else branch — gas difference reveals which path ran
suint256 tmp = 0s;
for (uint256 i = 0; i < 10; i++) {
tmp = tmp + 1s;
}
discount = tmp;
}// BETTER: Both arms are identical operations (single assignment), same gas either way
discount = isVIP ? 50s : 0s;// Both forms embed `42` in bytecode
suint256 a = suint256(42);
suint256 b = 42s;// BAD: Leaks the value of `shieldedCount` via gas
suint256 shieldedCount = /* ... */;
for (uint256 i = 0; i < uint256(shieldedCount); i++) {
// Each iteration is visible in gas cost
}// BETTER: Fixed-size loop with constant iteration count
uint256 constant MAX_ITERATIONS = 100;
for (uint256 i = 0; i < MAX_ITERATIONS; i++) {
// Use a shielded condition to decide whether to actually do work.
// IMPORTANT: Both the "real work" and "no-op" paths must use
// the same gas -- e.g., write to the same slots, do the same
// number of arithmetic ops, etc.
}// BAD: Anyone can call this and read the shielded value
suint256 private _secretBalance;
function secretBalance() external view returns (uint256) {
return uint256(_secretBalance);
}suint256 private _secretBalance;
function secretBalance() external view returns (uint256) {
require(msg.sender == owner, "Not authorized");
return uint256(_secretBalance);
}// Will NOT compile
suint256 public secretBalance;
// Will NOT compile
function getSecret() external view returns (suint256) {
return secretBalance;
}// This function accepts shielded input, but nothing prevents calling it
// with a regular (non-Seismic) transaction — which would leak `amount`.
function deposit(suint256 amount) external {
balances[msg.sender] += amount;
}// BAD: Gas cost reveals the value of `shieldedExp`
suint256 base = 2s;
suint256 shieldedExp = /* ... */;
suint256 result = base ** shieldedExp; // Gas cost leaks shieldedExpenum Status { Active, Inactive, Suspended }
// Only 3 possible values (0, 1, 2) — shielding provides little protection
suint256 shieldedStatus = suint256(uint256(Status.Active));// Will NOT compile — compiler error
suint256 immutable SECRET = 42s;
// Will NOT compile — compiler error
suint256 constant MY_VALUE = 1s;// In theory, a proposer could simulate this output and decide
// whether to include the transaction based on the result.
function drawWinner() external {
suint256 rand = rng256();
uint256 winnerIndex = uint256(rand) % participants.length;
winner = participants[winnerIndex];
}// An attacker wraps the call and reverts on unfavorable outcomes,
// making every attempt risk-free.
function playLotteryRiskFree() external {
bool winner = lotteryContract.playLottery();
require(winner, "Better luck next time");
}suint256 x = 42s; // inferred as suint256
suint8 small = 7s; // inferred as suint8
sint256 neg = -1s; // inferred as sint256suint256 x = suint256(42);
suint8 small = suint8(7);
sint256 neg = sint256(-1);suint256 a = 1_000s; // underscores
suint256 b = 0xDEADs; // hex
suint256 c = 1e5s; // scientific notation
sint256 d = -42s; // unary minussuint8 a = 255s; // suint8
suint32 b = 1_000s; // suint32
sint128 c = -1s; // sint128
suint256 x = 10s;
suint256 y = x + 5s; // 5s inferred as suint256 from contextsuint256 x = 1s + 1; // Error — mixed shielded/non-shielded
suint256 x = 1s + 1s; // OKuint256 x = 42s; // Error — cannot assign shielded to uint2560x66// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract PrivateToken {
address public owner;
mapping(address => suint256) private balances;
mapping(address => bytes) public publicKeys;
sbytes32 private contractPrivateKey;
bytes public contractPublicKey;
event Transfer(address indexed from, address indexed to, bytes encryptedAmount);
constructor() {
owner = msg.sender;
}
// Owner sets the contract keypair via a Seismic transaction (type 0x4A)
// so the private key is encrypted in calldata and never exposed.
function setContractKey(sbytes32 _privateKey, bytes memory _publicKey) external {
require(msg.sender == owner, "Only owner");
require(bytes32(contractPrivateKey) == bytes32(0), "Already set");
contractPrivateKey = _privateKey;
contractPublicKey = _publicKey;
}
function registerPublicKey(bytes memory pubKey) external {
publicKeys[msg.sender] = pubKey;
}
function transfer(address to, suint256 amount) public {
require(bytes32(contractPrivateKey) != bytes32(0), "Contract key not set");
balances[msg.sender] -= amount;
balances[to] += amount;
bytes memory recipientPubKey = publicKeys[to];
if (recipientPubKey.length > 0) {
// 1. Shared secret via ECDH
bytes32 sharedSecret = ecdh(contractPrivateKey, recipientPubKey);
// 2. Derive encryption key via HKDF
sbytes32 encKey = sbytes32(hkdf(abi.encodePacked(sharedSecret)));
// 3. Encrypt the amount via AES-GCM
uint96 nonce = uint96(bytes12(keccak256(abi.encodePacked(msg.sender, to, block.number))));
bytes memory encrypted = aes_gcm_encrypt(encKey, nonce, abi.encode(uint256(amount)));
// 4. Emit with encrypted bytes
emit Transfer(msg.sender, to, encrypted);
}
}
}// BAD: This exposes the confidential value to everyone
event Transfer(address from, address to, uint256 amount);
function transfer(address to, suint256 amount) public {
// ...
emit Transfer(msg.sender, to, uint256(amount)); // Leaks the amount!
}createSeismicDevnetuseShieldedContractwagmi config
└─ ShieldedWalletProvider
├─ ShieldedPublicClient (encrypted reads)
├─ ShieldedWalletClient (encrypted writes)
└─ Hooks
├─ useShieldedWallet Access wallet/public clients
├─ useShieldedContract Contract instance with ABI binding
├─ useShieldedWriteContract Encrypted write transactions
└─ useSignedReadContract Signed, encrypted readsWeb3 (standard web3.py)
├── eth (standard)
├── net (standard)
└── seismic (Seismic-specific) ✨
├── send_shielded_transaction()
├── signed_call()
├── get_tee_public_key()
├── deposit()
└── contract() → ShieldedContract
├── .write (shielded)
├── .read (signed)
├── .twrite (transparent)
├── .tread (transparent)
└── .dwrite (debug)import { useShieldedWriteContract } from "seismic-react";import { useShieldedWriteContract } from 'seismic-react'
const abi = [
{
name: 'transfer',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [],
},
] as const
function TransferToken() {
const { writeContract, isLoading, error, hash } = useShieldedWriteContract({
address: '0x1234567890abcdef1234567890abcdef12345678',
abi,
functionName: 'transfer',
args: ['0xRecipientAddress...', 1000n],
})
return (
<div>
<button onClick={writeContract} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Transfer'}
</button>
{hash && <p>Transaction: {hash}</p>}
{error && <p>Error: {error.message}</p>}
</div>
)
}import { useShieldedWriteContract } from 'seismic-react'
import { useEffect } from 'react'
function WriteWithTracking() {
const { writeContract, hash, isLoading } = useShieldedWriteContract({
address: CONTRACT_ADDRESS,
abi,
functionName: 'increment',
})
useEffect(() => {
if (hash) {
console.log('Transaction confirmed:', hash)
}
}, [hash])
return (
<div>
<button onClick={writeContract} disabled={isLoading}>
Increment
</button>
{hash && (
<a href={`https://seismic-testnet.socialscan.io/tx/${hash}`} target="_blank" rel="noreferrer">
View on explorer
</a>
)}
</div>
)
}import { useShieldedWriteContract } from 'seismic-react'
function WriteWithStates() {
const { writeContract, isLoading, error, hash } = useShieldedWriteContract({
address: CONTRACT_ADDRESS,
abi,
functionName: 'setNumber',
args: [42n],
})
return (
<div>
<button onClick={writeContract} disabled={isLoading}>
{isLoading ? 'Encrypting & sending...' : 'Set Number'}
</button>
{isLoading && <p>Transaction in progress...</p>}
{error && <p style={{ color: 'red' }}>Failed: {error.message}</p>}
{hash && <p style={{ color: 'green' }}>Success: {hash}</p>}
</div>
)
}import { useShieldedWriteContract } from 'seismic-react'
function WriteWithGasOverride() {
const { writeContract, isLoading } = useShieldedWriteContract({
address: CONTRACT_ADDRESS,
abi,
functionName: 'expensiveOperation',
gas: 500_000n,
gasPrice: 20_000_000_000n,
})
return (
<button onClick={writeContract} disabled={isLoading}>
Execute
</button>
)
}TypeScript client library for Seismic, composing with viem to add shielded transactions, encrypted calldata, and signed reads.
npm install seismic-viem viemimport { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
// Smart write — auto-detects shielded params, encrypts only when needed
const hash = await client.writeContract({
address: "0x...",
abi: myContractAbi,
functionName: "transfer", // has suint256/saddress params → encrypted automatically
args: ["0x...", 100n],
});
// Smart read — auto-detects shielded params, uses signed read only when needed
const balance = await client.readContract({
address: "0x...",
abi: myContractAbi,
functionName: "balanceOf", // has saddress param → signed read automatically
args: ["0x..."],
});Contract interaction namespaces for shielded and transparent operations
contract = w3.seismic.contract(address="0x...", abi=ABI)Create sync Web3 instance with full Seismic wallet capabilities
w3.eth, w3.net)w3.seismic)packages/contracts/script// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {ClownBeatdown} from "../src/ClownBeatdown.sol";
contract ClownBeatdownScript is Script {
ClownBeatdown public clownBeatdown;
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVKEY");
vm.startBroadcast(deployerPrivateKey);
clownBeatdown = new ClownBeatdown(3);
vm.stopBroadcast();
console.log("Deployed at:", address(clownBeatdown));
}
}# Initializes a project called `Counter`
sforge init Counter# Run tests for the Counter contract
sforge test# Use sforge scripts to deploy the Counter contract
# Running `sanvil` @ http://localhost:8545
# Set the private key in the env
export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Address - 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
# Run the script and broadcast the deploy transaction
sforge script script/Counter.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --private-key $PRIVATE_KEYseismic-viem
├── Client Layer
│ ├── createShieldedPublicClient — read-only, TEE key, precompiles
│ └── createShieldedWalletClient — full capabilities, encryption pipeline
├── Contract Layer
│ ├── getShieldedContract — .read / .write (smart) / .sread / .swrite (force shielded) / .tread / .twrite (force transparent) / .dwrite
│ ├── shieldedWriteContract — standalone encrypted write
│ └── signedReadContract — standalone signed read
├── Chain Configs
│ ├── seismicTestnet — public testnet (chain ID 5124)
│ ├── sanvil — local dev (chain ID 31337)
│ └── createSeismicDevnet — custom chain factory
├── Encryption
│ ├── getEncryption — ECDH key exchange → AES key
│ └── AesGcmCrypto — encrypt/decrypt calldata
└── Precompiles
├── rng — random number generation
├── ecdh — key exchange
├── aesGcmEncrypt / Decrypt — on-chain encryption
├── hkdf — key derivation
└── secp256k1Sig — signature generationsanvilRPC_URL=http://127.0.0.1:8545
PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80source .env
sforge script script/ClownBeatdown.s.sol:ClownBeatdownScript \
--rpc-url $RPC_URL \
--broadcastscast 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 $PRIVKEYsanvilsuint256 balance = 100s;(value, is_private)mapping(address => suint256) balanceOf;
function transfer(address to, suint256 amount) public {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}solidity alloy-core alloy-trie revm revm-inspectors alloy-evm reth foundry compilers foundry-fork-db
| | | | | | | | | |
ssolc seismic- seismic- seismic- seismic-revm- seismic- seismic- seismic- seismic- seismic-
alloy-core trie revm inspectors evm reth foundry compilers foundry-fork-db


# Privacy-sensitive transaction
tx_hash = contract.write.transfer(recipient, 1000)
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)# Encrypted read that proves your identity — auto-decoded
balance = contract.read.balanceOf() # int# Public transaction
tx_hash = contract.twrite.approve(spender, amount)
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)# Public read — auto-decoded
total_supply = contract.tread.totalSupply() # int# Debug transaction with inspection
result = contract.dwrite.transfer(recipient, 1000)
# Inspect plaintext calldata
print(f"Plaintext: {result.plaintext_tx.data.to_0x_hex()}")
print(f"Encrypted: {result.shielded_tx.data.to_0x_hex()}")
print(f"Tx hash: {result.tx_hash.to_0x_hex()}")
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(result.tx_hash)# BAD: SRC20 balanceOf uses msg.sender — returns 0x0's balance
balance = contract.tread.balanceOf() # 0# GOOD: Proves your identity
balance = contract.read.balanceOf() # Your actual balance# Wasteful: Encrypts already-public data
tx_hash = contract.write.approve(spender, amount)# More efficient: Use transparent write
tx_hash = contract.twrite.approve(spender, amount)encryption = get_encryption(network_pk, encryption_sk)w3.seismic = SeismicNamespace(w3, encryption, private_key)web3.py functionalitydef create_wallet_client(
rpc_url: str,
private_key: PrivateKey,
*,
encryption_sk: PrivateKey | None = None,
) -> Web3import os
from seismic_web3 import create_wallet_client, PrivateKey
# Load private key
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Create wallet client
w3 = create_wallet_client(
"https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
# Now use w3.seismic for Seismic operations
contract = w3.seismic.contract(address, abi)
tx_hash = contract.swrite.transfer(recipient, 1000)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Recommended: use chain config instead of raw URL
w3 = SEISMIC_TESTNET.wallet_client(private_key)
# Equivalent to:
# w3 = create_wallet_client(SEISMIC_TESTNET.rpc_url, private_key=private_key)import os
from seismic_web3 import create_wallet_client, PrivateKey
signing_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
encryption_key = PrivateKey(os.urandom(32)) # Custom encryption keypair
w3 = create_wallet_client(
"https://testnet-1.seismictest.net/rpc",
private_key=signing_key,
encryption_sk=encryption_key,
)import os
from seismic_web3 import create_wallet_client, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = create_wallet_client("https://testnet-1.seismictest.net/rpc", private_key=private_key)
# All standard web3.py operations work
block = w3.eth.get_block("latest")
balance = w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
chain_id = w3.eth.chain_idw3 = Web3(Web3.HTTPProvider(rpc_url))network_pk = get_tee_public_key(w3)encryption_sk = encryption_sk or PrivateKey(os.urandom(32))1. Client generates an ephemeral secp256k1 keypair (or uses a provided one)
2. Client fetches the TEE public key from the node via seismic_getTeePublicKey
3. ECDH(client_sk, tee_pk) → shared secret
4. Shared secret → AES-256 key (via key derivation)
5. For each transaction:
a. Generate a random 12-byte nonce
b. Encode TxSeismicMetadata as Additional Authenticated Data (AAD)
c. AES-GCM encrypt(plaintext_calldata, nonce, aad) → ciphertext
d. Include encryptionPubkey + nonce in the transaction's SeismicTxExtras fieldsimport { getEncryption } from "seismic-viem";import { createShieldedPublicClient, getEncryption } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
// Fetch the TEE public key from the node
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
const teePublicKey = await publicClient.getTeePublicKey();
// Derive encryption keys without constructing a full wallet client
const encryption = getEncryption(teePublicKey);
console.log("AES key:", encryption.aesKey);
console.log("Encryption public key:", encryption.encryptionPublicKey);
console.log("Encryption private key:", encryption.encryptionPrivateKey);
// Or provide your own private key for deterministic key derivation
const deterministicEncryption = getEncryption(
teePublicKey,
"0xYourSecp256k1PrivateKey",
);import { createShieldedWalletClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
// Access the derived AES key
const aesKey = walletClient.getEncryption();
console.log("AES key:", aesKey);
// Access the encryption public key
const encPubKey = walletClient.getEncryptionPublicKey();
console.log("Encryption public key:", encPubKey);import { ShieldedWalletProvider } from "seismic-react";type OnAddressChangeParams = {
publicClient: ShieldedPublicClient;
walletClient: ShieldedWalletClient;
address: Hex;
};interface WalletClientContextType {
publicClient: ShieldedPublicClient | null;
walletClient: ShieldedWalletClient | null;
address: Hex | null;
error: string | null;
loaded: boolean;
}import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
export default function App({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}'use client'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}WagmiProvider
└─ QueryClientProvider
└─ RainbowKitProvider
└─ ShieldedWalletProvider
└─ Your Appimport { ShieldedWalletProvider } from 'seismic-react'
function App() {
const handleAddressChange = async ({
publicClient,
walletClient,
address,
}) => {
console.log('Connected:', address)
// Example: read user's shielded balance on connect
const balance = await publicClient.readContract({
address: TOKEN_ADDRESS,
abi: tokenAbi,
functionName: 'balanceOf',
args: [address],
})
console.log('Balance:', balance)
}
return (
<ShieldedWalletProvider
config={config}
options={{ onAddressChange: handleAddressChange }}
>
<YourApp />
</ShieldedWalletProvider>
)
}import { useShieldedWallet } from 'seismic-react'
function MyComponent() {
const { error, loaded } = useShieldedWallet()
if (!loaded) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
return <div>Connected</div>
}import { getShieldedContract } from "seismic-viem";import { getShieldedContract } from "seismic-viem";
const abi = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "account", type: "saddress" }],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "saddress" },
{ name: "amount", type: "suint256" },
],
outputs: [{ name: "", type: "bool" }],
},
{
name: "totalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "saddress" },
{ name: "amount", type: "suint256" },
],
outputs: [{ name: "", type: "bool" }],
},
] as const;
const contract = getShieldedContract({
abi,
address: "0x1234567890abcdef1234567890abcdef12345678",
client,
});// transfer() has saddress and suint256 params → shielded write (type 0x4A)
const hash = await contract.write.transfer(["0x1234...", 100n]);
// totalSupply() has no shielded params → transparent read
const supply = await contract.read.totalSupply();
// balanceOf() has saddress param → signed read (encrypted, proves identity)
const balance = await contract.read.balanceOf(["0x1234..."]);// Force signed read even though totalSupply() has no shielded params
const supply = await contract.sread.totalSupply();// Force shielded write even though approve() might not need encryption
const hash = await contract.swrite.approve(["0x1234...", 100n], {
gas: 100_000n,
});const supply = await contract.tread.totalSupply();const hash = await contract.twrite.approve(["0x1234...", 100n]);const { plaintextTx, shieldedTx, txHash } = await contract.dwrite.transfer([
"0x1234...",
100n,
]);
console.log("Plaintext calldata:", plaintextTx.data);
console.log("Encrypted calldata:", shieldedTx.data);
console.log("Transaction hash:", txHash);const abi = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "account", type: "address" }],
outputs: [{ name: "", type: "uint256" }],
},
] as const;
const contract = getShieldedContract({ abi, address: "0x...", client });
// TypeScript knows:
// - contract.read.balanceOf exists
// - first arg is [Address]
// - return type is bigint
const balance = await contract.read.balanceOf(["0x1234..."]);w3 = Web3(Web3.HTTPProvider(rpc_url))w3.seismic = SeismicPublicNamespace(w3)def create_public_client(rpc_url: str) -> Web3from seismic_web3 import create_public_client
# Create public client
w3 = create_public_client("https://testnet-1.seismictest.net/rpc")
# Query TEE public key
tee_pk = w3.seismic.get_tee_public_key()
print(f"TEE public key: {tee_pk.to_0x_hex()}")
# Query deposit info
root = w3.seismic.get_deposit_root()
count = w3.seismic.get_deposit_count()
print(f"Deposit root: {root.to_0x_hex()}, count: {count}")from seismic_web3 import SEISMIC_TESTNET
# Recommended: use chain config instead of raw URL
w3 = SEISMIC_TESTNET.public_client()
# Equivalent to:
# w3 = create_public_client(SEISMIC_TESTNET.rpc_url)from seismic_web3 import create_public_client
w3 = create_public_client("https://testnet-1.seismictest.net/rpc")
# Create contract wrapper (read-only)
contract = w3.seismic.contract(
address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
abi=contract_abi,
)
# Only transparent reads are available
result = contract.tread.balanceOf("0x1234...")
# Shielded operations are NOT available
# contract.swrite.transfer(...) # AttributeError: no swrite on public client
# contract.sread.getBalance(...) # AttributeError: no sread on public clientfrom seismic_web3 import create_public_client
w3 = create_public_client("https://testnet-1.seismictest.net/rpc")
# All standard web3.py read operations work
block = w3.eth.get_block("latest")
print(f"Latest block: {block['number']}")
balance = w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")
chain_id = w3.eth.chain_id
print(f"Chain ID: {chain_id}")from seismic_web3 import create_public_client
def get_chain_stats(rpc_url: str):
w3 = create_public_client(rpc_url)
# Get latest block
block = w3.eth.get_block("latest")
# Get deposit info
deposit_root = w3.seismic.get_deposit_root()
deposit_count = w3.seismic.get_deposit_count()
# Get TEE info
tee_pk = w3.seismic.get_tee_public_key()
return {
"block_number": block["number"],
"block_hash": block["hash"].to_0x_hex(),
"deposit_root": deposit_root.to_0x_hex(),
"deposit_count": deposit_count,
"tee_public_key": tee_pk.to_0x_hex(),
}
stats = get_chain_stats("https://testnet-1.seismictest.net/rpc")
print(stats)Sync contract wrapper with shielded and transparent namespaces
.write - Encrypted Write.read - Encrypted Read.twrite - Transparent Write.tread - Transparent Read.dwrite - Debug Writemsg.sendereth_callmsg.senderaddress(0)truesuint256uint256function balanceOf(address account) external view returns (uint256) {
require(msg.sender == account, "Only owner can view balance");
return uint256(balanceOf[account]);
}function allowanceOf(address owner, address spender) external view returns (uint256) {
require(
msg.sender == owner || msg.sender == spender,
"Not authorized"
);
return uint256(allowance[owner][spender]);
}import { createShieldedWalletClient, getShieldedContract, seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
// Create a shielded wallet client
const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http("https://testnet-1.seismictest.net/rpc"),
account: privateKeyToAccount(PRIVATE_KEY),
});
// Get a contract instance
const token = getShieldedContract({
abi: src20Abi,
address: SRC20_ADDRESS,
client: walletClient,
});
// Read balance -- this is automatically a signed read
const balance = await token.read.balanceOf([walletClient.account.address]);
console.log("My balance:", balance);const balance = await walletClient.readContract({
address: SRC20_ADDRESS,
abi: src20Abi,
functionName: "balanceOf",
args: [walletClient.account.address],
});gas_price: int | None - Gas price in wei (default: network suggested)security: [SeismicSecurityParams](../api-reference/transaction-types/seismic-security-params.md) | None - Security parameters for expiryclass ShieldedContract:
def __init__(
self,
w3: Web3,
encryption: EncryptionState,
private_key: PrivateKey,
address: ChecksumAddress,
abi: list[dict[str, Any]],
eip712: bool = False,
) -> None:
...from seismic_web3 import create_wallet_client, ShieldedContract
w3 = create_wallet_client(
rpc_url="https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
contract = ShieldedContract(
w3=w3,
encryption=w3.seismic.encryption,
private_key=private_key,
address="0x1234567890123456789012345678901234567890",
abi=CONTRACT_ABI,
)
# Encrypted write - calldata hidden on-chain
tx_hash = contract.write.setNumber(42)
print(f"Transaction: {tx_hash.to_0x_hex()}")
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Status: {receipt['status']}")# Encrypted read — calldata and result hidden, auto-decoded
number = contract.read.getNumber() # int
print(f"Number: {number}")# Transparent write - calldata visible on-chain
tx_hash = contract.twrite.setNumber(42)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
# Transparent read — standard eth_call, auto-decoded
number = contract.tread.getNumber()
print(f"Result: {number}")# Debug write - returns plaintext and encrypted views
debug_result = contract.dwrite.transfer("0xRecipient...", 1000)
print(f"Transaction hash: {debug_result.tx_hash.to_0x_hex()}")
print(f"Plaintext data: {debug_result.plaintext_tx.data.to_0x_hex()}")
print(f"Encrypted data: {debug_result.shielded_tx.data.to_0x_hex()}")
# Transaction is actually broadcast
receipt = w3.eth.wait_for_transaction_receipt(debug_result.tx_hash)# Custom gas and value
tx_hash = contract.write.deposit(
value=10**18, # 1 ETH
gas=200_000,
gas_price=20 * 10**9, # 20 gwei
)
# With security parameters
from seismic_web3.transaction_types import SeismicSecurityParams
security = SeismicSecurityParams(blocks_window=100)
tx_hash = contract.write.withdraw(
amount,
security=security,
)# Enable EIP-712 for typed data signing
contract = ShieldedContract(
w3=w3,
encryption=w3.seismic.encryption,
private_key=private_key,
address=contract_address,
abi=CONTRACT_ABI,
eip712=True, # Use EIP-712 instead of raw signing
)
tx_hash = contract.write.setNumber(123)# Most common pattern - let the client create the contract
from seismic_web3 import create_wallet_client
w3 = create_wallet_client(
rpc_url="https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
# Client's contract() method creates ShieldedContract
contract = w3.seismic.contract(address=contract_address, abi=CONTRACT_ABI)
# Now use any namespace
tx_hash = contract.write.setNumber(42)import { createShieldedPublicClient } from "seismic-viem";const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});import { createShieldedPublicClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
// Standard viem public actions work as usual
const blockNumber = await publicClient.getBlockNumber();
const balance = await publicClient.getBalance({
address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
});import { createShieldedPublicClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
const teePublicKey = await publicClient.getTeePublicKey();
console.log("TEE public key:", teePublicKey);import { createShieldedPublicClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
// Generate a random number
const randomValue = await publicClient.rng({ numBytes: 1 });
console.log("Random value:", randomValue);
// ECDH key exchange
const sharedSecret = await publicClient.ecdh({
sk: "0x...",
pk: "0x...",
});
// AES-GCM encryption
const ciphertext = await publicClient.aesGcmEncryption({
aesKey: "0x...",
plaintext: "0x...",
nonce: "0x...",
});
// HKDF key derivation
const derivedKey = await publicClient.hdfk("0x...");const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
const txUrl = publicClient.txExplorerUrl({ txHash: "0xabc123..." });
const txLogs = publicClient.txExplorerUrl({
txHash: "0xabc123...",
tab: "logs",
});
const addrUrl = publicClient.addressExplorerUrl({
address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
});
const blockUrl = publicClient.blockExplorerUrl({ blockNumber: 12345 });
const tokenUrl = publicClient.tokenExplorerUrl({
address: "0xYourTokenAddress",
tab: "holders",
});import {
addressExplorerUrl,
blockExplorerUrl,
getExplorerUrl,
tokenExplorerUrl,
txExplorerUrl,
} from "seismic-viem";
const url = txExplorerUrl({
chain: seismicTestnet,
txHash: "0xabc...",
tab: "logs",
});
// Low-level form used by all helpers internally:
const raw = getExplorerUrl(seismicTestnet, {
item: "address",
id: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
tab: "logs",
});.twrite.tread.dwriteclass AsyncShieldedContract:
def __init__(
self,
w3: AsyncWeb3,
encryption: EncryptionState,
private_key: PrivateKey,
address: ChecksumAddress,
abi: list[dict[str, Any]],
eip712: bool = False,
) -> None:
...import asyncio
from seismic_web3 import create_async_wallet_client, AsyncShieldedContract
async def main():
w3 = await create_async_wallet_client(
provider_url="https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
contract = AsyncShieldedContract(
w3=w3,
encryption=w3.seismic.encryption,
private_key=private_key,
address="0x1234567890123456789012345678901234567890",
abi=CONTRACT_ABI,
)
# Encrypted write - must await
tx_hash = await contract.write.setNumber(42)
print(f"Transaction: {tx_hash.to_0x_hex()}")
# Wait for confirmation
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Status: {receipt['status']}")
asyncio.run(main())async def read_example(contract: AsyncShieldedContract):
# Encrypted read — auto-decoded, must await
number = await contract.read.getNumber() # int
print(f"Number: {number}")async def transparent_example(contract: AsyncShieldedContract, w3: AsyncWeb3):
# Transparent write - calldata visible on-chain
tx_hash = await contract.twrite.setNumber(42)
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Status: {receipt['status']}")
# Transparent read — standard eth_call, auto-decoded
number = await contract.tread.getNumber()
print(f"Result: {number}")async def debug_example(contract: AsyncShieldedContract, w3: AsyncWeb3):
# Debug write - returns plaintext and encrypted views
debug_result = await contract.dwrite.transfer("0xRecipient...", 1000)
print(f"Transaction hash: {debug_result.tx_hash.to_0x_hex()}")
print(f"Plaintext data: {debug_result.plaintext_tx.data.to_0x_hex()}")
print(f"Encrypted data: {debug_result.shielded_tx.data.to_0x_hex()}")
# Transaction is actually broadcast
receipt = await w3.eth.wait_for_transaction_receipt(debug_result.tx_hash)
print(f"Confirmed in block: {receipt['blockNumber']}")async def concurrent_example(contract: AsyncShieldedContract):
# Execute multiple reads concurrently — each is auto-decoded
balances = await asyncio.gather(
contract.tread.balanceOf("0xAddress1..."),
contract.tread.balanceOf("0xAddress2..."),
contract.tread.balanceOf("0xAddress3..."),
)
for i, balance in enumerate(balances):
print(f"Balance {i}: {balance}")async def advanced_write(contract: AsyncShieldedContract):
# Custom gas and value
tx_hash = await contract.write.deposit(
value=10**18, # 1 ETH
gas=200_000,
gas_price=20 * 10**9, # 20 gwei
)
# With security parameters
from seismic_web3.transaction_types import SeismicSecurityParams
security = SeismicSecurityParams(blocks_window=100)
tx_hash = await contract.write.withdraw(
amount,
security=security,
)async def batch_writes(contract: AsyncShieldedContract, recipients: list[str]):
# Send multiple transactions concurrently
tx_hashes = await asyncio.gather(
*[contract.write.transfer(recipient, 100) for recipient in recipients]
)
print(f"Sent {len(tx_hashes)} transactions")
# Wait for all confirmations
receipts = await asyncio.gather(
*[w3.eth.wait_for_transaction_receipt(tx_hash) for tx_hash in tx_hashes]
)
successful = sum(1 for r in receipts if r['status'] == 1)
print(f"{successful}/{len(receipts)} successful")async def eip712_example():
w3 = await create_async_wallet_client(...)
# Enable EIP-712 for typed data signing
contract = AsyncShieldedContract(
w3=w3,
encryption=w3.seismic.encryption,
private_key=private_key,
address=contract_address,
abi=CONTRACT_ABI,
eip712=True, # Use EIP-712 instead of raw signing
)
tx_hash = await contract.write.setNumber(123)async def client_pattern():
# Most common pattern - let the client create the contract
w3 = await create_async_wallet_client(
provider_url="https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
# Client's contract() method creates AsyncShieldedContract
contract = w3.seismic.contract(address=contract_address, abi=CONTRACT_ABI)
# Now use any namespace (must await)
tx_hash = await contract.write.setNumber(42)async def error_handling(contract: AsyncShieldedContract):
try:
tx_hash = await contract.write.withdraw(123)
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt['status'] != 1:
print("Transaction failed on-chain")
except ValueError as e:
print(f"RPC error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")async def context_pattern():
async with create_async_wallet_client(...) as w3:
contract = AsyncShieldedContract(...)
tx_hash = await contract.write.setNumber(42)
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
# Connection automatically closedCreate async Web3 instance with public (read-only) Seismic access
w3.eth, w3.net)w3.seismic)Create async Web3 instance with full Seismic wallet capabilities
w3.eth, w3.net)w3.seismic)web3.py functionality.treaddef create_async_public_client(
provider_url: str,
*,
ws: bool = False,
) -> AsyncWeb3from seismic_web3 import create_async_public_client
# Create async public client
w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")
# Query TEE public key
tee_pk = await w3.seismic.get_tee_public_key()
print(f"TEE public key: {tee_pk.to_0x_hex()}")
# Query deposit info
root = await w3.seismic.get_deposit_root()
count = await w3.seismic.get_deposit_count()
print(f"Deposit root: {root.to_0x_hex()}, count: {count}")from seismic_web3 import create_async_public_client
# WebSocket provider for persistent connection
w3 = create_async_public_client(
"wss://testnet-1.seismictest.net/ws",
ws=True,
)
# Subscribe to new blocks
async for block in w3.eth.subscribe("newHeads"):
print(f"New block: {block['number']}")
# Query deposit count at each new block
count = await w3.seismic.get_deposit_count()
print(f"Current deposit count: {count}")from seismic_web3 import SEISMIC_TESTNET
# Recommended: use chain config with HTTP
w3 = SEISMIC_TESTNET.async_public_client()
# Or with WebSocket (uses ws_url from chain config)
w3 = SEISMIC_TESTNET.async_public_client(ws=True)import asyncio
from seismic_web3 import create_async_public_client
async def main():
w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")
# Get current block
block = await w3.eth.get_block("latest")
print(f"Latest block: {block['number']}")
# Get balance
address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
balance = await w3.eth.get_balance(address)
print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")
# Query deposit info
deposit_count = await w3.seismic.get_deposit_count()
print(f"Total deposits: {deposit_count}")
asyncio.run(main())from seismic_web3 import create_async_public_client
async def query_contract():
w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")
# Create contract wrapper (read-only)
contract = w3.seismic.contract(
address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
abi=contract_abi,
)
# Only transparent reads are available
result = await contract.tread.balanceOf("0x1234...")
print(f"Balance: {result}")from seismic_web3 import create_async_public_client
import asyncio
async def monitor_deposits():
w3 = create_async_public_client("wss://testnet-1.seismictest.net/ws", ws=True)
last_count = await w3.seismic.get_deposit_count()
print(f"Starting deposit count: {last_count}")
async for block in w3.eth.subscribe("newHeads"):
current_count = await w3.seismic.get_deposit_count()
if current_count > last_count:
print(f"New deposits detected! Count: {current_count}")
print(f"Block: {block['number']}")
last_count = current_count
asyncio.run(monitor_deposits())from seismic_web3 import create_async_public_client
import asyncio
async def get_chain_stats():
w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")
# Run multiple queries in parallel
block, tee_pk, deposit_root, deposit_count = await asyncio.gather(
w3.eth.get_block("latest"),
w3.seismic.get_tee_public_key(),
w3.seismic.get_deposit_root(),
w3.seismic.get_deposit_count(),
)
return {
"block_number": block["number"],
"tee_public_key": tee_pk.to_0x_hex(),
"deposit_root": deposit_root.to_0x_hex(),
"deposit_count": deposit_count,
}from seismic_web3 import create_async_public_client
async with create_async_public_client(
"wss://testnet-1.seismictest.net/ws",
ws=True,
) as w3:
# WebSocket connection will be properly closed
block = await w3.eth.get_block("latest")
print(f"Block: {block['number']}")if ws:
provider = WebSocketProvider(provider_url)
else:
provider = AsyncHTTPProvider(provider_url)w3 = AsyncWeb3(provider)w3.seismic = AsyncSeismicPublicNamespace(w3)encryption_skNoneencryption = get_encryption(network_pk, encryption_sk)w3.seismic = AsyncSeismicNamespace(w3, encryption, private_key)web3.py functionalityasync def create_async_wallet_client(
provider_url: str,
private_key: PrivateKey,
*,
encryption_sk: PrivateKey | None = None,
ws: bool = False,
) -> AsyncWeb3import os
from seismic_web3 import create_async_wallet_client, PrivateKey
# Load private key
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Create async wallet client
w3 = await create_async_wallet_client(
"https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
# Now use w3.seismic for Seismic operations
contract = w3.seismic.contract(address, abi)
tx_hash = await contract.swrite.transfer(recipient, 1000)
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)import os
from seismic_web3 import create_async_wallet_client, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# WebSocket provider for persistent connection
w3 = await create_async_wallet_client(
"wss://testnet-1.seismictest.net/ws",
private_key=private_key,
ws=True,
)
# Subscribe to new blocks
async for block in w3.eth.subscribe("newHeads"):
print(f"New block: {block['number']}")import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Recommended: use chain config with HTTP
w3 = await SEISMIC_TESTNET.async_wallet_client(private_key)
# Or with WebSocket (uses ws_url from chain config)
w3 = await SEISMIC_TESTNET.async_wallet_client(private_key, ws=True)import os
from seismic_web3 import create_async_wallet_client, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Use context manager to ensure cleanup
async with create_async_wallet_client(
"wss://testnet-1.seismictest.net/ws",
private_key=private_key,
ws=True,
) as w3:
contract = w3.seismic.contract(address, abi)
tx_hash = await contract.swrite.transfer(recipient, 1000)
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)import asyncio
import os
from seismic_web3 import create_async_wallet_client, PrivateKey
async def main():
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = await create_async_wallet_client(
"https://testnet-1.seismictest.net/rpc",
private_key=private_key,
)
# Get current block
block = await w3.eth.get_block("latest")
print(f"Latest block: {block['number']}")
# Get balance
address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
balance = await w3.eth.get_balance(address)
print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")
asyncio.run(main())import os
from seismic_web3 import create_async_wallet_client, PrivateKey
async def setup_client():
signing_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
encryption_key = PrivateKey(os.urandom(32)) # Custom encryption keypair
w3 = await create_async_wallet_client(
"https://testnet-1.seismictest.net/rpc",
private_key=signing_key,
encryption_sk=encryption_key,
)
return w3if ws:
provider = WebSocketProvider(provider_url)
else:
provider = AsyncHTTPProvider(provider_url)w3 = AsyncWeb3(provider)network_pk = await async_get_tee_public_key(w3)encryption_sk = encryption_sk or PrivateKey(os.urandom(32))encrypt() and decrypt() methods that handle AES-GCM encryption with metadata-bound Additional Authenticated Data (AAD).@dataclass
class EncryptionState:
"""Holds the AES key and encryption keypair derived from ECDH.
Created by :func:`get_encryption` during client setup. Pure
computation - works in both sync and async contexts.
Attributes:
aes_key: 32-byte AES-256 key derived from ECDH + HKDF.
encryption_pubkey: Client's compressed secp256k1 public key.
encryption_private_key: Client's secp256k1 private key.
"""
aes_key: Bytes32
encryption_pubkey: CompressedPublicKey
encryption_private_key: PrivateKeydef encrypt(
self,
plaintext: HexBytes,
nonce: EncryptionNonce,
metadata: TxSeismicMetadata,
) -> HexBytesfrom seismic_web3 import get_encryption, EncryptionNonce
from hexbytes import HexBytes
import os
# Setup encryption state
encryption = get_encryption(tee_public_key, client_private_key)
# Encrypt calldata
plaintext = HexBytes("0x1234abcd...")
nonce = EncryptionNonce(os.urandom(12))
ciphertext = encryption.encrypt(
plaintext=plaintext,
nonce=nonce,
metadata=tx_metadata,
)
# Ciphertext is len(plaintext) + 16 bytes (auth tag)
assert len(ciphertext) == len(plaintext) + 16def decrypt(
self,
ciphertext: HexBytes,
nonce: EncryptionNonce,
metadata: TxSeismicMetadata,
) -> HexBytesfrom seismic_web3 import get_encryption
from cryptography.exceptions import InvalidTag
encryption = get_encryption(tee_public_key, client_private_key)
try:
plaintext = encryption.decrypt(
ciphertext=encrypted_data,
nonce=nonce,
metadata=tx_metadata,
)
print(f"Decrypted: {plaintext.to_0x_hex()}")
except InvalidTag:
print("Decryption failed: authentication tag mismatch")import os
from seismic_web3 import create_wallet_client, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = create_wallet_client("https://testnet-1.seismictest.net/rpc", private_key=private_key)
# Access encryption state
encryption = w3.seismic.encryption
print(f"AES key: {encryption.aes_key.to_0x_hex()}")
print(f"Client pubkey: {encryption.encryption_pubkey.to_0x_hex()}")import os
from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
from hexbytes import HexBytes
# Get TEE public key from node
tee_pk = CompressedPublicKey("0x02abcd...")
# Create encryption state
client_sk = PrivateKey(os.urandom(32))
encryption = get_encryption(tee_pk, client_sk)
# Build transaction metadata (see TxSeismicMetadata docs)
metadata = ... # TxSeismicMetadata for the transaction being encrypted
# Encrypt some data
plaintext = HexBytes("0x1234abcd")
nonce = os.urandom(12)
ciphertext = encryption.encrypt(
plaintext=plaintext,
nonce=nonce,
metadata=metadata,
)
# Decrypt it back
decrypted = encryption.decrypt(
ciphertext=ciphertext,
nonce=nonce,
metadata=metadata,
)
assert decrypted == plaintextimport os
from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
# Use a deterministic key (e.g., derived from mnemonic)
client_sk = PrivateKey.from_hex_str(os.environ["CLIENT_KEY"])
# Or use a random ephemeral key
# client_sk = PrivateKey(os.urandom(32))
tee_pk = CompressedPublicKey("0x02abcd...")
encryption = get_encryption(tee_pk, client_sk)
# Store client_sk securely if you need to recreate the same encryption state laterfrom seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
from cryptography.exceptions import InvalidTag
import os
encryption = get_encryption(tee_pk, client_sk)
plaintext = b"Hello, Seismic!"
nonce = os.urandom(12)
# Encrypt
ciphertext = encryption.encrypt(plaintext, nonce, metadata)
# Decrypt with correct parameters
assert encryption.decrypt(ciphertext, nonce, metadata) == plaintext
# Decrypt with wrong nonce - should fail
wrong_nonce = os.urandom(12)
try:
encryption.decrypt(ciphertext, wrong_nonce, metadata)
assert False, "Should have raised InvalidTag"
except InvalidTag:
print("Authentication failed as expected")def __post_init__(self) -> None:
self._crypto = AesGcmCrypto(self.aes_key)Send encrypted write transactions with shieldedWriteContract
Watch and decrypt SRC20 token events
0x4A transaction with the encrypted calldata and Seismic-specific fieldsgetShieldedContract.read.write.tread.twriteimport { shieldedWriteContract } from "seismic-viem";import { shieldedWriteContract } from "seismic-viem";
const hash = await shieldedWriteContract(client, {
address: "0x1234567890abcdef1234567890abcdef12345678",
abi: myContractAbi,
functionName: "transfer",
args: ["0xRecipient...", 100n],
gas: 100_000n,
});import { shieldedWriteContractDebug } from "seismic-viem";
const { plaintextTx, shieldedTx, txHash } = await shieldedWriteContractDebug(
client,
{
address: "0x1234567890abcdef1234567890abcdef12345678",
abi: myContractAbi,
functionName: "transfer",
args: ["0xRecipient...", 100n],
},
);
console.log("Plaintext calldata:", plaintextTx.data);
console.log("Encrypted calldata:", shieldedTx.data);
console.log("Transaction hash:", txHash);const hash = await client.sendShieldedTransaction({
to: "0x1234567890abcdef1234567890abcdef12345678",
data: "0x...", // raw calldata
value: 0n,
gas: 100_000n,
gasPrice: 1_000_000_000n,
});const hash = await shieldedWriteContract(
client,
{
address: "0x...",
abi: myContractAbi,
functionName: "transfer",
args: ["0x...", 100n],
},
{
blocksWindow: 50n, // expires after 50 blocks instead of 100
},
);if client_sk is None:
client_sk = PrivateKey(os.urandom(32))aes_key = generate_aes_key(client_sk, network_pk)def get_encryption(
network_pk: CompressedPublicKey,
client_sk: PrivateKey | None = None,
) -> EncryptionStatefrom seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
# Get TEE public key (from node)
tee_pk = CompressedPublicKey("0x02abcd...")
# Derive encryption state (random ephemeral key)
encryption = get_encryption(tee_pk)
print(f"AES key: {encryption.aes_key.to_0x_hex()}")
print(f"Client pubkey: {encryption.encryption_pubkey.to_0x_hex()}")import os
from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
tee_pk = CompressedPublicKey("0x02abcd...")
# Use a deterministic client key
client_sk = PrivateKey.from_hex_str(os.environ["CLIENT_KEY"])
encryption = get_encryption(tee_pk, client_sk)import os
from seismic_web3 import get_encryption, get_tee_public_key, PrivateKey
from web3 import Web3
# This is what create_wallet_client() does internally
w3 = Web3(Web3.HTTPProvider("https://testnet-1.seismictest.net/rpc"))
# Step 1: Fetch TEE public key
network_pk = get_tee_public_key(w3)
# Step 2: Derive encryption state
signing_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
encryption = get_encryption(network_pk, client_sk=None) # Random ephemeral key
# Step 3: Attach to client
# w3.seismic = SeismicNamespace(w3, encryption, signing_key)from seismic_web3 import get_encryption, CompressedPublicKey
tee_pk = CompressedPublicKey("0x02abcd...")
# Each call generates a new random key
encryption1 = get_encryption(tee_pk)
encryption2 = get_encryption(tee_pk)
# Different keys
assert encryption1.encryption_private_key != encryption2.encryption_private_key
assert encryption1.aes_key != encryption2.aes_keyimport os
from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
from seismic_web3.crypto.secp import private_key_to_compressed_public_key
tee_pk = CompressedPublicKey("0x02abcd...")
client_sk = PrivateKey.from_hex_str(os.environ["CLIENT_KEY"])
encryption = get_encryption(tee_pk, client_sk)
# Verify public key derivation
expected_pubkey = private_key_to_compressed_public_key(client_sk)
assert encryption.encryption_pubkey == expected_pubkey
# Verify keys are stored correctly
assert encryption.encryption_private_key == client_skClient has: client_sk (private), client_pk (public)
TEE has: tee_sk (private), tee_pk (public)
Client computes: shared_secret = ECDH(client_sk, tee_pk)
TEE computes: shared_secret = ECDH(tee_sk, client_pk)
Both derive: aes_key = HKDF(shared_secret)from seismic_web3 import get_encryption, CompressedPublicKey
# New random key for each session (recommended)
def create_session_encryption(tee_pk: CompressedPublicKey):
return get_encryption(tee_pk) # Random keyfrom seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
import os
# Load persisted key from secure storage
def load_encryption(tee_pk: CompressedPublicKey):
client_sk = PrivateKey.from_hex_str(os.environ["ENCRYPTION_KEY"])
return get_encryption(tee_pk, client_sk)from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
# Rotate to a new key periodically
def rotate_encryption_key(tee_pk: CompressedPublicKey):
new_client_sk = PrivateKey(os.urandom(32))
return get_encryption(tee_pk, new_client_sk)import {
src20PublicActions,
src20WalletActions,
} from "seismic-viem";const unwatch = await walletClient.watchSRC20Events({
address: "0xYourTokenAddress",
onTransfer: (log) => {
console.log(`Transfer ${log.from} -> ${log.to}: ${log.decryptedAmount}`);
},
onApproval: (log) => {
console.log(
`Approval ${log.owner} -> ${log.spender}: ${log.decryptedAmount}`,
);
},
onError: (err) => console.error("decrypt failed:", err),
});
// stop watching
unwatch();const viewingKey: Hex = "0x..."; // 32-byte AES key
const unwatch = await publicClient.watchSRC20EventsWithKey(viewingKey, {
address: "0xYourTokenAddress",
onTransfer: (log) => console.log(log),
onApproval: (log) => console.log(log),
onError: (err) => console.error(err),
});type DecryptedTransferLog = {
from: Address;
to: Address;
encryptKeyHash: Hex; // keccak256 of the viewing key
encryptedAmount: Hex; // original AES-GCM ciphertext
decryptedAmount: bigint; // plaintext amount
transactionHash: Hex;
blockNumber: bigint;
};
type DecryptedApprovalLog = {
owner: Address;
spender: Address;
encryptKeyHash: Hex;
encryptedAmount: Hex;
decryptedAmount: bigint;
transactionHash: Hex;
blockNumber: bigint;
};client_pubkey = private_key_to_compressed_public_key(client_sk)return EncryptionState(
aes_key=aes_key,
encryption_pubkey=client_pubkey,
encryption_private_key=client_sk,
)contract RegularStorage {
struct RegularStruct {
uint64 a; // Slot 0 (packed)
uint128 b; // Slot 0 (packed)
uint64 c; // Slot 0 (packed)
}
RegularStruct regularData;
/*
Storage Layout:
- Slot 0: [a | b | c]
*/
}contract ShieldedStorage {
struct ShieldedStruct {
suint64 a; // Slot 0
suint128 b; // Slot 1
suint64 c; // Slot 2
}
ShieldedStruct shieldedData;
/*
Storage Layout:
- Slot 0: [a]
- Slot 1: [b]
- Slot 2: [c]
*/
}contract ManualSlotPacking {
// Use a deterministic slot derived from a namespace string to avoid collisions.
// keccak256("ManualSlotPacking.packed") = a fixed slot number.
function _packedSlot() internal pure returns (uint256 s) {
assembly {
s := keccak256(0, 0) // placeholder, we use a constant below
}
// Use a constant derived from a namespace to avoid storage collisions.
s = uint256(keccak256("ManualSlotPacking.packed"));
}
function packTwo(suint128 a, suint128 b) public {
uint256 slot = _packedSlot();
assembly {
let packed := or(shl(128, a), and(b, 0xffffffffffffffffffffffffffffffff))
cstore(slot, packed)
}
}
function unpackTwo() public view returns (uint128, uint128) {
uint256 slot = _packedSlot();
uint256 packed;
assembly {
packed := cload(slot)
}
uint128 a = uint128(packed >> 128);
uint128 b = uint128(packed);
return (a, b);
}
}Seismic precompiled contracts for cryptographic operations
import {
rng,
ecdh,
aesGcmEncrypt,
aesGcmDecrypt,
hdfk,
secp256k1Sig,
} from "seismic-viem";Precompile<P, R> TypecallPrecompileEmit transfer events with encrypted amounts using AES precompiles
// This will NOT compile
event Transfer(address indexed from, address indexed to, suint256 amount);encryptedAmount from the event log using AES-GCM Decrypt.readContractimport { rng } from "seismic-viem";
const randomValue = await rng(client, {
numBytes: 32,
});
console.log("Random value:", randomValue);import { createShieldedPublicClient } from "seismic-viem";
import { rng } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
const client = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
// Generate 32 random bytes
const randomValue = await rng(client, { numBytes: 32 });
console.log("Random 256-bit value:", randomValue);
// Generate 16 random bytes with a personalization string
const seededValue = await rng(client, {
numBytes: 16,
pers: "0x6d79617070",
});
console.log("Seeded random value:", seededValue);import { ecdh } from "seismic-viem";
const sharedSecret = await ecdh(client, {
sk: "0x...", // 32-byte secret key
pk: "0x...", // 33-byte compressed public key
});
console.log("Shared secret:", sharedSecret);import { ecdh } from "seismic-viem";
const sharedSecret = await ecdh(client, {
sk: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
pk: "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
});
console.log("Shared secret:", sharedSecret);import { aesGcmEncrypt } from "seismic-viem";
const ciphertext = await aesGcmEncrypt(client, {
aesKey: "0x...", // 32-byte AES key
nonce: 1, // numeric nonce
plaintext: "hello world",
});
console.log("Ciphertext:", ciphertext);import { aesGcmDecrypt } from "seismic-viem";
const plaintext = await aesGcmDecrypt(client, {
aesKey: "0x...", // same 32-byte AES key
nonce: 1, // same nonce used for encryption
ciphertext: "0x...",
});
console.log("Plaintext:", plaintext);import { aesGcmEncrypt, aesGcmDecrypt, rng } from "seismic-viem";
// Generate a random AES key using the RNG precompile
const aesKeyRaw = await rng(client, { numBytes: 32 });
const aesKey = `0x${aesKeyRaw.toString(16).padStart(64, "0")}` as const;
// Encrypt
const ciphertext = await aesGcmEncrypt(client, {
aesKey,
nonce: 1,
plaintext: "secret message",
});
// Decrypt
const plaintext = await aesGcmDecrypt(client, {
aesKey,
nonce: 1,
ciphertext,
});
console.log("Decrypted:", plaintext); // "secret message"import { hdfk } from "seismic-viem";
const derivedKey = await hdfk(client, "0x..."); // input key material
console.log("Derived key:", derivedKey);import { hdfk } from "seismic-viem";
const inputKeyMaterial = "0xdeadbeef";
const derivedKey = await hdfk(client, inputKeyMaterial);
console.log("Derived key:", derivedKey);import { secp256k1Sig } from "seismic-viem";
const signature = await secp256k1Sig(client, {
sk: "0x...", // 32-byte secret key
message: "hello", // message to sign
});
console.log("Signature:", signature);import { secp256k1Sig } from "seismic-viem";
const signature = await secp256k1Sig(client, {
sk: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
message: "sign this message",
});
console.log("r:", signature.r);
console.log("s:", signature.s);
console.log("v:", signature.v);import { createShieldedPublicClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
const client = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
// Call precompiles as client methods
const randomValue = await client.rng({ numBytes: 32 });
const sharedSecret = await client.ecdh({
sk: "0x...",
pk: "0x...",
});
const ciphertext = await client.aesGcmEncryption({
aesKey: "0x...",
nonce: 1,
plaintext: "hello",
});
const derivedKey = await client.hdfk("0x...");import { callPrecompile, rngPrecompile } from "seismic-viem";
// Access the precompile address directly
console.log("RNG precompile address:", rngPrecompile.address);
// Call any precompile by passing its object to callPrecompile
const randomValue = await callPrecompile({
client,
precompile: rngPrecompile,
args: { numBytes: 32 },
});event Transfer(address indexed from, address indexed to, bytes encryptedAmount);address public owner;
sbytes32 contractPrivateKey;
bytes public contractPublicKey;
constructor(
string memory _name,
string memory _symbol,
uint256 _initialSupply
) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = suint256(_initialSupply);
owner = msg.sender;
}
/// @notice Call this immediately after deployment using a Seismic transaction.
function setContractKey(bytes32 _privateKey, bytes memory _publicKey) external {
require(msg.sender == owner, "Only owner");
require(bytes32(contractPrivateKey) == bytes32(0), "Already set");
contractPrivateKey = sbytes32(_privateKey);
contractPublicKey = _publicKey;
}function _deriveSharedSecret(bytes memory recipientPublicKey) internal view returns (sbytes32) {
require(bytes32(contractPrivateKey) != bytes32(0), "Contract key not set");
// Call ECDH precompile at 0x65
// Note: private key comes FIRST, then public key
(bool success, bytes memory result) = address(0x65).staticcall(
abi.encodePacked(bytes32(contractPrivateKey), recipientPublicKey)
);
require(success, "ECDH failed");
return sbytes32(abi.decode(result, (bytes32)));
}function _deriveEncryptionKey(sbytes32 sharedSecret) internal view returns (sbytes32) {
// Call HKDF precompile at 0x68
// Pass raw key material bytes directly
(bool success, bytes memory result) = address(0x68).staticcall(
abi.encodePacked(bytes32(sharedSecret))
);
require(success, "HKDF failed");
return sbytes32(abi.decode(result, (bytes32)));
}function _encrypt(sbytes32 key, bytes12 nonce, bytes memory plaintext) internal view returns (bytes memory) {
// Call AES-GCM Encrypt precompile at 0x66
// Input format: key (32 bytes) + nonce (12 bytes) + plaintext
(bool success, bytes memory ciphertext) = address(0x66).staticcall(
abi.encodePacked(bytes32(key), nonce, plaintext)
);
require(success, "Encryption failed");
return ciphertext;
}function _emitEncryptedTransfer(
address from,
address to,
suint256 amount,
bytes memory recipientPublicKey
) internal {
// Derive shared secret between contract and recipient
sbytes32 sharedSecret = _deriveSharedSecret(recipientPublicKey);
// Derive encryption key
sbytes32 encKey = _deriveEncryptionKey(sharedSecret);
// Encrypt the amount (nonce can be derived or generated per-event)
bytes12 nonce = bytes12(keccak256(abi.encodePacked(from, to, block.number)));
bytes memory plaintext = abi.encode(uint256(amount));
bytes memory encryptedAmount = _encrypt(encKey, nonce, plaintext);
// Emit with encrypted data
emit Transfer(from, to, encryptedAmount);
}// Mapping of address to their public key (registered on-chain)
mapping(address => bytes) public publicKeys;
function registerPublicKey(bytes memory pubKey) external {
publicKeys[msg.sender] = pubKey;
}
function transfer(address to, suint256 amount) public returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
// Encrypt amount for the recipient
bytes memory recipientPubKey = publicKeys[to];
if (recipientPubKey.length > 0) {
_emitEncryptedTransfer(msg.sender, to, amount, recipientPubKey);
} else {
// Fallback: emit with zero if recipient has no registered key
emit Transfer(msg.sender, to, bytes(""));
}
return true;
}// Smart read -- balanceOf(saddress) has shielded param → signed read automatically
const myBalance = await contract.read.balanceOf(["0x1234..."]);
// Smart read -- totalSupply() has no shielded params → transparent read automatically
const totalSupply = await contract.read.totalSupply();
// Force signed read -- always encrypted, even for non-shielded functions
const supply = await contract.sread.totalSupply();import { signedReadContract } from "seismic-viem";import { signedReadContract } from "seismic-viem";
const balance = await signedReadContract(
client,
{
address: "0x1234567890abcdef1234567890abcdef12345678",
abi: myContractAbi,
functionName: "balanceOf",
args: ["0xMyAddress..."],
},
{
blocksWindow: 50n, // optional: expire after 50 blocks instead of the default 100
},
);import { signedCall } from "seismic-viem";
const result = await signedCall(client, {
to: "0x1234567890abcdef1234567890abcdef12345678",
data: "0x...", // raw calldata
account: client.account,
gas: 30_000_000n,
});// Smart read -- auto-detects: balanceOf(saddress) → signed, totalSupply() → transparent
const myBalance = await contract.read.balanceOf(["0x1234..."]);
const totalSupply = await contract.read.totalSupply();
// Force signed read -- always encrypted
const supply = await contract.sread.totalSupply();
// Force transparent read -- always plaintext
const supply2 = await contract.tread.totalSupply();Pre-configured chain definitions for Seismic networks
import {
seismicTestnet,
sanvil,
localSeismicDevnet,
createSeismicDevnet,
} from "seismic-viem";import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, sanvil } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: sanvil,
transport: http(),
account: privateKeyToAccount("0x..."),
});import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, localSeismicDevnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: localSeismicDevnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});Are you developing against a live network?
-> Use seismicTestnet
Are you running Sanvil locally for rapid iteration?
-> Use sanvil
Are you running seismic-reth --dev locally?
-> Use localSeismicDevnet
Do you need a custom chain configuration?
-> Use createSeismicDevnet()import { SEISMIC_TX_TYPE } from "seismic-viem";
console.log(SEISMIC_TX_TYPE); // 74 (0x4A)import { createSeismicDevnet } from "seismic-viem";
const myChain = createSeismicDevnet({
nodeHost: "https://my-seismic-node.example.com",
explorerUrl: "https://explorer.example.com",
});import { createSeismicAzTestnet, createSeismicGcpTestnet } from "seismic-viem";
// Azure-hosted testnet instance N
const azChain = createSeismicAzTestnet(1);
// GCP-hosted testnet instance N
const gcpChain = createSeismicGcpTestnet(1);interface SeismicTxExtras {
encryptionPubkey: Hex; // Client's compressed secp256k1 public key
encryptionNonce: Hex; // AES-GCM nonce for calldata encryption
messageVersion: number; // Seismic message format version
recentBlockHash: Hex; // Recent block hash for replay protection
expiresAtBlock: bigint; // Block number at which the transaction expires
signedRead: boolean; // Whether this is a signed read request
}signed_callonlyContributorHexhexToString()handleReset — validates the clown is KO, sends a shielded write via twrite.reset(), clears punch count and rob result, and refetches stamina{
"address": "0xYourDeployedAddress",
"chainId": 31337,
"abi": [
{ "type": "constructor", "inputs": [...] },
{ "type": "function", "name": "hit", ... },
...
]
}export type ContractInterface = {
chainId: number;
abi: Array<Record<string, unknown>>;
methodIdentifiers: Record<string, string>;
};
export type DeployedContract = ContractInterface & {
address: `0x${string}`;
};import { useShieldedContract } from "seismic-react";
import * as contractJson from "@/abis/contracts/ClownBeatdown.json" with { type: "json" };
import type { DeployedContract } from "@/types/contract";
export const useAppContract = () =>
useShieldedContract(contractJson as DeployedContract);import { useCallback, useEffect, useState } from "react";
import { useShieldedWallet } from "seismic-react";
import {
type ShieldedPublicClient,
type ShieldedWalletClient,
addressExplorerUrl,
txExplorerUrl,
} from "seismic-viem";
import { type Hex, hexToString } from "viem";
import { useAppContract } from "./useContract";
export const useContractClient = () => {
const [loaded, setLoaded] = useState(false);
const { walletClient, publicClient } = useShieldedWallet();
const { contract } = useAppContract();
useEffect(() => {
if (walletClient && publicClient && contract) {
setLoaded(true);
} else {
setLoaded(false);
}
}, [walletClient, publicClient, contract]);
const wallet = useCallback((): ShieldedWalletClient => {
if (!walletClient) {
throw new Error("Wallet client not found");
}
return walletClient;
}, [walletClient]);
const pubClient = useCallback((): ShieldedPublicClient => {
if (!publicClient) {
throw new Error("Public client not found");
}
return publicClient;
}, [publicClient]);
const walletAddress = useCallback((): Hex => {
return wallet().account.address;
}, [wallet]);
const appContract = useCallback((): ReturnType<
typeof useAppContract
>["contract"] => {
if (!contract) {
throw new Error("Contract not found");
}
return contract;
}, [contract]);
/*
function getClownStamina() external view returns (uint256);
function rob() external view returns (bytes32);
function hit() external;
function reset() external;
*/
const clownStamina = useCallback(async (): Promise<bigint> => {
return appContract().tread.getClownStamina();
}, [appContract]);
const rob = useCallback(async (): Promise<string> => {
const result = (await appContract().read.rob()) as Hex;
return hexToString(result);
}, [appContract]);
const hit = useCallback(async (): Promise<Hex> => {
return appContract().twrite.hit();
}, [appContract]);
const reset = useCallback(async (): Promise<Hex> => {
return appContract().twrite.reset();
}, [appContract]);
const txUrl = useCallback(
(txHash: Hex): string | null => {
return txExplorerUrl({ chain: pubClient().chain, txHash });
},
[pubClient],
);
const addressUrl = useCallback(
(address: Hex): string | null => {
return addressExplorerUrl({ chain: pubClient().chain, address });
},
[pubClient],
);
const waitForTransaction = useCallback(
async (hash: Hex) => {
return await pubClient().waitForTransactionReceipt({ hash });
},
[pubClient],
);
return {
loaded,
walletClient,
publicClient,
walletAddress,
appContract,
pubClient,
wallet,
clownStamina,
rob,
hit,
reset,
txUrl,
addressUrl,
waitForTransaction,
};
};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>
)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),
})import { useCallback, useEffect, useState } from "react";
import React from "react";
import { useSound } from "use-sound";
import { ExplorerToast } from "@/components/chain/ExplorerToast";
import { useContractClient } from "@/hooks/useContractClient";
import { useToastNotifications } from "@/hooks/useToastNotifications";
export const useGameActions = () => {
const [clownStamina, setClownStamina] = useState<number | null>(null);
const [currentRoundId] = useState<number | null>(1);
const {
loaded,
hit,
rob,
reset,
txUrl,
waitForTransaction,
clownStamina: readClownStamina,
} = useContractClient();
const { notifySuccess, notifyError, notifyInfo } = useToastNotifications();
const [isHitting, setIsHitting] = useState(false);
const [isResetting, setIsResetting] = useState(false);
const [isRobbing, setIsRobbing] = useState(false);
const [robResult, setRobResult] = useState<string | null>(null);
const [punchCount, setPunchCount] = useState(0);
const [playHit] = useSound("/audio/hit_sfx.wav", { volume: 0.1 });
const [playReset] = useSound("/audio/reset_sfx.wav", { volume: 0.1 });
const [playRob] = useSound("/audio/rob_sfx.wav", { volume: 0.1 });
const fetchGameRounds = useCallback(() => {
if (!loaded) return;
readClownStamina()
.then((stamina) => {
setClownStamina(Number(stamina));
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
console.error("Error fetching clown stamina:", message);
});
}, [loaded, readClownStamina]);
// Fetch initial state when contract is loaded
useEffect(() => {
fetchGameRounds();
}, [fetchGameRounds]);
const resetGameState = useCallback(() => {
setRobResult(null);
setPunchCount(0);
}, [punchCount]);
const handleHit = async () => {
playHit();
if (!loaded || isHitting) return;
setIsHitting(true);
hit()
.then((hash) => {
const url = txUrl(hash);
if (url) {
notifyInfo(
React.createElement(ExplorerToast, {
url: url,
text: "Sent punch tx: ",
hash: hash,
}),
);
} else {
notifyInfo(`Sent punch tx: ${hash}`);
}
if (clownStamina && clownStamina > 0) {
setPunchCount((prev) => {
const newCount = Math.min(prev + 1, 3);
return newCount;
});
}
return waitForTransaction(hash);
})
.then((receipt) => {
if (receipt.status === "success") {
notifySuccess("Punch successful");
// Re-read stamina from contract after successful hit
fetchGameRounds();
} else {
notifyError("Punch failed");
}
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
notifyError(`Error punching clown: ${message}`);
})
.finally(() => {
setIsHitting(false);
});
};
const handleReset = async () => {
playReset();
if (!loaded || isResetting) return;
if (clownStamina !== 0) {
notifyError("Clown must be KO to reset");
return;
}
setIsResetting(true);
reset()
.then((hash) => {
const url = txUrl(hash);
if (url) {
notifyInfo(
React.createElement(ExplorerToast, {
url: url,
text: "Sent reset tx: ",
hash: hash,
}),
);
} else {
notifyInfo(`Sent reset tx: ${hash}`);
}
setPunchCount(0);
return waitForTransaction(hash);
})
.then((receipt) => {
if (receipt.status === "success") {
notifySuccess("Reset successful");
setRobResult(null);
// Re-read stamina from contract after successful reset
fetchGameRounds();
} else {
notifyError("Reset failed");
}
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
notifyError(`Error resetting clown: ${message}`);
})
.finally(() => {
setIsResetting(false);
});
};
const handleRob = async () => {
playRob();
if (!loaded || isRobbing) return;
setIsRobbing(true);
rob()
.then((result) => {
setRobResult(result);
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
notifyError(`Error robbing clown: ${message}`);
})
.finally(() => {
setIsRobbing(false);
});
};
return {
loaded,
clownStamina,
currentRoundId,
isHitting,
isResetting,
isRobbing,
robResult,
punchCount,
fetchGameRounds,
resetGameState,
handleHit,
handleReset,
handleRob,
};
};Full-featured client with encryption, shielded writes, and signed reads
import { createShieldedWalletClient } from "seismic-viem";getEncryption() Standalone FunctionencryptionSksecp256k1Signature()getEncryption()const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});import { createShieldedWalletClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
// Smart write -- auto-detects shielded params in the ABI
// transfer(saddress, suint256) has shielded params → encrypted seismic tx
const hash = await walletClient.writeContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "transfer",
args: ["0xRecipient", 1000n],
});
// Smart read -- auto-detects shielded params in the ABI
// balanceOf(saddress) has shielded params → signed read
const balance = await walletClient.readContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "balanceOf",
args: ["0xMyAddress"],
});import { createShieldedWalletClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
// Shielded write: calldata is encrypted before submission
const hash = await walletClient.writeContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "transfer",
args: ["0xRecipient", 1000n],
});
const receipt = await walletClient.waitForTransactionReceipt({ hash });
console.log("Transaction confirmed in block:", receipt.blockNumber);// Signed read: proves caller identity to the node
const balance = await walletClient.readContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "balanceOf",
args: ["0xMyAddress"],
});
console.log("Shielded balance:", balance);
// Transparent read: standard unsigned eth_call (no caller proof)
const totalSupply = await walletClient.treadContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "totalSupply",
});
console.log("Total supply:", totalSupply);import {
createShieldedPublicClient,
createShieldedWalletClient,
} from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
// Create a shared public client
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
// Reuse it across multiple wallet clients
const walletClient1 = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0xPrivateKey1"),
publicClient,
});
const walletClient2 = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0xPrivateKey2"),
publicClient,
});// dwriteContract broadcasts a real shielded tx AND returns the plaintext
// and shielded tx views for inspection. txHash is a real on-chain hash.
const debugResult = await walletClient.dwriteContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "transfer",
args: ["0xRecipient", 1000n],
});
console.log("Plaintext tx:", debugResult.plaintextTx);
console.log("Shielded tx:", debugResult.shieldedTx);
console.log("Tx hash:", debugResult.txHash);const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
encryptionSk: "0xCustomEncryptionPrivateKey",
});
// Access encryption details
const aesKey = walletClient.getEncryption();
const encPubKey = walletClient.getEncryptionPublicKey();import { getEncryption } from "seismic-viem";
const encryption = getEncryption(teePublicKey, clientPrivateKey);
// encryption.aesKey -- the derived AES-256 key
// encryption.encryptionPrivateKey -- the client's secp256k1 private key
// encryption.encryptionPublicKey -- the client's secp256k1 public keyimport { motion, useAnimation } from 'framer-motion'
import { useEffect, useMemo } from 'react'
import { Box } from '@mui/material'
type ClownProps = {
isKO: boolean
isShakingAnimation: boolean
isHittingAnimation: boolean
punchCount: number
}
const ShowClown: React.FC<ClownProps> = ({
isKO,
isShakingAnimation,
isHittingAnimation,
punchCount,
}) => {
const controls = useAnimation()
useEffect(() => {
if (isShakingAnimation) {
controls.start({
rotate: [0, -5, 5, -5, 5, 0],
transition: { duration: 0.5 },
})
} else if (isHittingAnimation) {
controls.start({
scale: [1, 0.9, 1.1, 1],
transition: { duration: 0.3 },
})
}
}, [isShakingAnimation, isHittingAnimation, controls])
// Select the appropriate clown image based on punch count and KO state
// Using useMemo to prevent recalculating on every render
const clownImage = useMemo(() => {
// If shaking, show the shaking clown image
if (isShakingAnimation) {
return '/clown_shaking.png'
}
if (isKO) {
return '/clownko.png'
}
let imagePath
switch (punchCount) {
case 0:
imagePath = '/clown1.png'
break
case 1:
imagePath = '/clown2.png'
break
case 2:
case 3:
imagePath = '/clown3.png'
break
default:
imagePath = '/clown1.png'
}
return imagePath
}, [isKO, isShakingAnimation, punchCount])
return (
<Box
sx={{
width: { xs: '70%', sm: '70%', md: '80%', lg: '80%', xl: '100%' },
height: { xs: '100%', sm: '70%', md: '80%', lg: '80%', xl: '100%' },
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div className="relative">
<motion.div animate={controls} className="relative">
<img
src={clownImage}
alt="Clown"
style={{
maxWidth: '100%',
height: 'auto',
objectFit: 'contain',
}}
/>
</motion.div>
</div>
</Box>
)
}
export default ShowClownimport { useState } from 'react'
import { Box, type SxProps, type Theme } from '@mui/material'
type ButtonContainerProps = {
clownStamina: number | null
isHitting: boolean
isResetting: boolean
isRobbing: boolean
handleHit: () => void
handleReset: () => void
handleRob: () => void
position?: 'left' | 'right' | 'mobile'
}
type ActionButtonProps = {
onClick: () => void
active: boolean
src: string
alt: string
className: string
sx?: SxProps<Theme>
}
const ActionButton = ({
onClick,
active,
src,
alt,
className,
sx,
}: ActionButtonProps) => (
<Box
onClick={onClick}
component="div"
sx={{
cursor: active ? 'default' : 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
...sx,
}}
>
<img
src={src}
alt={alt}
className={className}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
</Box>
)
export default function ButtonContainer({
clownStamina,
isHitting,
isResetting,
isRobbing,
handleHit,
handleReset,
handleRob,
position = 'mobile',
}: ButtonContainerProps) {
const [showRobActive, setShowRobActive] = useState(false)
const [showResetActive, setShowResetActive] = useState(false)
const handleRobClick = () => {
if (!isRobbing) {
setShowRobActive(true)
setTimeout(() => {
setShowRobActive(false)
handleRob()
}, 200)
}
}
const handleResetClick = () => {
if (!isResetting) {
setShowResetActive(true)
setTimeout(() => {
setShowResetActive(false)
handleReset()
}, 200)
}
}
const isStanding = clownStamina !== null && clownStamina > 0
const robBtn = {
onClick: handleRobClick,
active: isRobbing,
src: showRobActive ? '/rob_active.png' : '/rob_btn.png',
alt: 'Rob',
className: 'look-btn',
}
const hitBtn = {
onClick: handleHit,
active: isHitting,
src: isHitting ? '/punch_active.png' : '/punch_btn.png',
alt: 'Punch',
className: 'punch-btn',
}
const resetBtn = {
onClick: handleResetClick,
active: isResetting,
src: showResetActive ? '/reset_active.png' : '/reset_btn.png',
alt: 'Reset',
className: 'reset-btn',
}
const rightBtn = isStanding ? hitBtn : resetBtn
if (position === 'left') {
return (
<Box
sx={{
width: { lg: '100%' },
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
pr: { lg: 4, xl: 6 },
}}
>
<ActionButton
{...robBtn}
sx={{ width: '20rem', marginRight: 6, height: '20rem' }}
/>
</Box>
)
}
if (position === 'right') {
return (
<Box
sx={{
width: { lg: '100%' },
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pl: { lg: 4, xl: 6 },
}}
>
<ActionButton
{...rightBtn}
sx={
isStanding
? {
marginLeft: { xs: 0, lg: 8 },
height: { lg: '18rem' },
}
: { width: '20rem', marginLeft: '3rem', height: '18rem' }
}
/>
</Box>
)
}
// Mobile layout — both buttons side by side
const MOBILE_SIZE = {
xs: '12rem',
sm: '20rem',
md: '20rem',
lg: '30rem',
xl: '30rem',
}
return (
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: { xs: 0, sm: 0, md: 0, lg: 70, xl: 70 },
marginRight: { xs: 0, sm: 4, md: 4, lg: 6, xl: 0 },
marginLeft: { xs: 0, sm: 4, md: 4, lg: 6, xl: 0 },
}}
>
<ActionButton
{...robBtn}
sx={{ height: MOBILE_SIZE, width: MOBILE_SIZE }}
/>
<ActionButton
{...rightBtn}
sx={
isStanding
? {
marginRight: { xs: 0, sm: 4, md: 0, lg: 0, xl: 0 },
height: {
xs: '10rem',
sm: '18rem',
md: '20rem',
lg: '30rem',
xl: '30rem',
},
width: {
xs: '12rem',
sm: '14rem',
md: '28rem',
lg: '30rem',
xl: '30rem',
},
}
: { height: MOBILE_SIZE, width: MOBILE_SIZE }
}
/>
</Box>
)
}'use client'
import { useEffect, useRef, useState } from 'react'
import { useGameActions } from '@/hooks/useGameActions'
import {
Backdrop,
Box,
CircularProgress,
Container,
Fade,
Typography,
} from '@mui/material'
import { useAuth } from '../chain/WalletConnectButton'
import ButtonContainer from './ButtonContainer'
import EntryScreen from './EntryScreen'
import ShowClown from './ShowClown'
const ClownPuncher: React.FC = () => {
const { isAuthenticated } = useAuth()
const [showGame, setShowGame] = useState(false)
const [showSecretSplash, setShowSecretSplash] = useState(false)
const [showRobRefused, setShowRobRefused] = useState(false)
const prevRoundIdRef = useRef<number | null>(null)
const {
loaded,
currentRoundId,
clownStamina,
isHitting,
isResetting,
isRobbing,
robResult,
punchCount,
fetchGameRounds,
resetGameState,
handleHit,
handleReset,
handleRob,
} = useGameActions()
useEffect(() => {
// Only fetch data if authenticated and game is shown
if (isAuthenticated && showGame) {
fetchGameRounds()
}
}, [fetchGameRounds, isAuthenticated, showGame])
useEffect(() => {
// Only reset game state when first showing the game or when the round actually changes
if (
showGame &&
(prevRoundIdRef.current === null ||
(currentRoundId !== null && prevRoundIdRef.current !== currentRoundId))
) {
console.log(
'Round changed from',
prevRoundIdRef.current,
'to',
currentRoundId,
'- resetting game state'
)
resetGameState()
}
// Update the ref to the current round ID
prevRoundIdRef.current = currentRoundId
}, [currentRoundId, resetGameState, showGame])
// Show splash screen when lookResult changes to a non-null value
useEffect(() => {
if (robResult !== null) {
setShowSecretSplash(true)
}
}, [robResult])
// If not showing the game yet, show entry screen
if (!showGame) {
return <EntryScreen onEnter={() => setShowGame(true)} />
}
const onRob = () => {
if (clownStamina !== null && clownStamina > 0) {
setShowRobRefused(true)
return
}
handleRob()
}
const buttonProps = {
clownStamina,
isHitting,
isResetting,
isRobbing,
handleHit,
handleReset,
handleRob: onRob,
} as const
return (
<Container
sx={{
height: '100dvh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'start',
px: 4,
}}
>
<Box
sx={{
mt: { xs: 3, sm: 3, md: 5, lg: 4, xl: 10 },
height: {
xs: '30dvh',
},
mb: 2,
display: 'flex',
justifyContent: 'center',
}}
>
<img
src="/cblogo.png"
alt="Clown Beatdown Logo"
className="clown-logo"
/>
</Box>
{/* Splash Screen — secret revealed or rob refused */}
<Backdrop
sx={{
color: '#fff',
zIndex: (theme) => theme.zIndex.drawer + 1,
backgroundColor: 'rgba(0, 0, 0, 0.85)',
}}
open={(showSecretSplash && robResult !== null) || showRobRefused}
onClick={() => {
setShowSecretSplash(false)
setShowRobRefused(false)
}}
>
<Fade in={(showSecretSplash && robResult !== null) || showRobRefused}>
<Box
sx={{
backgroundColor: 'background.paper',
borderRadius: 4,
p: 5,
textAlign: 'center',
maxWidth: '90%',
boxShadow: 24,
}}
>
{showRobRefused ? (
<>
<Typography
variant="h4"
fontWeight="bold"
color="white"
gutterBottom
>
NOT SO FAST!
</Typography>
<Typography variant="h6" color="white" gutterBottom>
The clown isn't giving up that easily.
</Typography>
<Typography
variant="body1"
color="text.secondary"
sx={{ mt: 2 }}
>
Knock him out first!
</Typography>
</>
) : (
<>
<Typography
variant="h4"
fontWeight="bold"
color="white"
gutterBottom
>
SECRET REVEALED!
</Typography>
<Typography
variant="h1"
fontWeight="bold"
color="white"
gutterBottom
>
{robResult}
</Typography>
</>
)}
<Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}>
(Click anywhere to close)
</Typography>
</Box>
</Fade>
</Backdrop>
{loaded ? (
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', lg: 'row' },
justifyContent: { lg: 'space-between' },
alignItems: 'center',
width: '100%',
position: 'relative',
height: { lg: '500px', xl: '600px' },
my: { xs: 0, md: 5, lg: 0, xl: 1 },
}}
>
{/* Desktop: left buttons */}
<Box sx={{ display: { xs: 'none', lg: 'flex' } }}>
<ButtonContainer {...buttonProps} position="left" />
</Box>
{/* Clown — rendered once, responsive positioning */}
<Box
className="clown-container"
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: { lg: 'absolute' },
left: { lg: '50%' },
transform: { lg: 'translateX(-50%)' },
zIndex: 2,
width: { lg: '50%', xl: '40%' },
maxHeight: { xs: '35dvh', md: '30dvh', lg: 'none' },
}}
>
<ShowClown
isKO={clownStamina === 0}
isShakingAnimation={false}
isHittingAnimation={isHitting}
punchCount={punchCount}
/>
</Box>
{/* Desktop: right buttons */}
<Box sx={{ display: { xs: 'none', lg: 'flex' } }}>
<ButtonContainer {...buttonProps} position="right" />
</Box>
{/* Mobile: all buttons below clown */}
<Box
sx={{
display: { xs: 'flex', lg: 'none' },
width: '100%',
justifyContent: 'center',
alignItems: 'center',
marginBottom: { xs: 3, md: 5 },
}}
>
<ButtonContainer {...buttonProps} position="mobile" />
</Box>
</Box>
) : (
<CircularProgress size={32} />
)}
</Container>
)
}
export default ClownPuncherimport React, { useEffect, useState } from 'react'
import { Box, Container } from '@mui/material'
import { useAuth } from '../chain/WalletConnectButton'
type EntryScreenProps = {
onEnter: () => void
}
const EntryScreen: React.FC<EntryScreenProps> = ({ onEnter }) => {
const { isAuthenticated, isLoading, openConnectModal } = useAuth()
const [isAnimating, setIsAnimating] = useState(false)
// Automatically enter when user becomes authenticated
useEffect(() => {
if (isAuthenticated) {
setIsAnimating(true)
setTimeout(() => {
setIsAnimating(false)
onEnter()
}, 500)
}
}, [isAuthenticated, onEnter])
const handleLogoClick = () => {
setIsAnimating(true)
// If not authenticated, open wallet connect modal
if (!isAuthenticated) {
setTimeout(() => {
setIsAnimating(false)
openConnectModal()
}, 300)
}
}
return (
<Container
sx={{
height: '100dvh',
width: '100dvw',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
cursor: 'pointer',
transform: isAnimating ? 'scale(0.95)' : 'scale(1)',
transition: 'transform 0.2s ease-in-out',
}}
onClick={handleLogoClick}
>
<img
src="/cblogo.png"
alt="Clown Beatdown Logo"
style={{ maxWidth: '100%', height: 'auto' }}
className="clown-image"
/>
<Box
sx={{
mt: 6,
color: 'black',
fontSize: '1.25rem',
textAlign: 'center',
opacity: 0.8,
fontFamily: 'monospace',
border: '1px solid black',
borderRadius: '10px',
padding: '10px',
backgroundColor: 'var(--midColor)',
}}
>
{isLoading ? '...Loading...' : 'CLICK TO CONNECT'}
</Box>
</Box>
</Container>
)
}
export default EntryScreenimport React, { createContext, useContext, useEffect, useState } from 'react'
import { useAccount } from 'wagmi'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { useConnectModal } from '@rainbow-me/rainbowkit'
// Create authentication context
type AuthContextType = {
isAuthenticated: boolean
isLoading: boolean
openConnectModal: () => void
accountName?: string
}
const AuthContext = createContext<AuthContextType>({
isAuthenticated: false,
isLoading: true,
openConnectModal: () => {},
})
export const useAuth = () => useContext(AuthContext)
// Wallet icon component using SVG for better quality
const WalletIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-5 h-5"
>
<path d="M2.273 5.625A4.483 4.483 0 0 1 5.25 4.5h13.5c1.141 0 2.183.425 2.977 1.125A3 3 0 0 0 18.75 3H5.25a3 3 0 0 0-2.977 2.625ZM2.273 8.625A4.483 4.483 0 0 1 5.25 7.5h13.5c1.141 0 2.183.425 2.977 1.125A3 3 0 0 0 18.75 6H5.25a3 3 0 0 0-2.977 2.625ZM5.25 9a3 3 0 0 0-3 3v6a3 3 0 0 0 3 3h13.5a3 3 0 0 0 3-3v-6a3 3 0 0 0-3-3H15a.75.75 0 0 0-.75.75 2.25 2.25 0 0 1-4.5 0A.75.75 0 0 0 9 9H5.25Z" />
</svg>
)
const WalletButton: React.FC<
React.PropsWithChildren<
{ onClick: () => void } & React.HTMLAttributes<HTMLButtonElement>
>
> = ({ children, onClick, ...props }) => {
return (
<button onClick={onClick} className="" {...props}>
{children}
</button>
)
}
export const AuthProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const { openConnectModal } = useConnectModal() || {
openConnectModal: () => {},
}
const { address, isConnecting, isConnected, isDisconnected } = useAccount()
const [authState, setAuthState] = useState<AuthContextType>({
isAuthenticated: false,
isLoading: true,
openConnectModal: openConnectModal || (() => {}),
})
useEffect(() => {
setAuthState({
isAuthenticated: isConnected,
isLoading: isConnecting,
openConnectModal: openConnectModal || (() => {}),
accountName: address
? `${address.slice(0, 6)}...${address.slice(-4)}`
: undefined,
})
}, [isConnected, isConnecting, isDisconnected, address, openConnectModal])
return (
<AuthContext.Provider value={authState}>{children}</AuthContext.Provider>
)
}
const WalletConnectButton = () => {
return (
<ConnectButton.Custom>
{({
account,
openConnectModal,
chain,
openAccountModal,
openChainModal,
mounted,
authenticationStatus,
}) => {
if (!mounted || authenticationStatus === 'loading') {
return <></>
}
if (!account || authenticationStatus === 'unauthenticated') {
return (
<WalletButton onClick={openConnectModal}>
<span className="md:inline hidden">CONNECT WALLET</span>
<span className="md:hidden">
<WalletIcon />
</span>
</WalletButton>
)
}
if (chain?.unsupported) {
return (
<WalletButton onClick={openChainModal}>
<span className="md:inline hidden">Unsupported chain</span>
<span className="md:hidden">
<WalletIcon />
</span>
</WalletButton>
)
}
return (
<WalletButton onClick={openAccountModal}>
<span className="">
<WalletIcon />
</span>
</WalletButton>
)
}}
</ConnectButton.Custom>
)
}
export default WalletConnectButtoncd packages/web
bun dev