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...
Use sfoundry to write and test smart contract code locally before deployment
# 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_KEYYou'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 -vvcd packages/contracts# Assuming you are currently in the contracts directory
cd ../clibun init -y// 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 -vvcurl 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 directory// foundry tool -> seismic version of foundry tool
forge -> sforge
anvil -> sanvil
cast -> scastsanvilbun 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;
}suint256[] 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
}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 keysuint256 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 uint256Deploy 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...Query SRC20 tokens deployed through the factory via a REST API
cd packages/api
cargo runGET 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"
}
]
}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));
}
}TypeScript client libraries for Seismic
Seismic public testnet chain configuration
Complete runnable examples for Seismic React
0x67 — decrypt data with AES-GCMsymboldecimalsownertotalSupplymintburntransfertransferFromapprovebalanceallowancebalanceOfSignedpermitprivateKey: the private key to create the client forcontracts 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.utils.tsmkdir -p packages/cli/lib
touch packages/cli/lib/constants.ts packages/cli/lib/utils.ts
cd packages/cli/libsuint256 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);
}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",
};GET http://localhost:3001/api/token/0xabc...{
"name": "My Private Token",
"symbol": "MPT",
"decimals": 18,
"owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"total_supply": "1000000000000000000000000"
}{
"error": "Invalid address: ..."
}sanvilRPC_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 $PRIVKEYconst 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);mkdir 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-stdimport { join } from "path";
const CONTRACT_NAME = "ClownBeatdown";
const CONTRACT_DIR = join(__dirname, "../../contracts");
export { CONTRACT_NAME, CONTRACT_DIR };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 };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-queryimport { 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(),
},
});# 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
};
}A handle on stype unlocks all shielded computation and storage
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)React hooks for shielded transactions and signed reads
Integrate Seismic with popular wallet connection libraries
Access shielded public and wallet clients from context
RainbowKit-compatible chain configurations for Seismic networks
Factory function for custom Seismic chain configurations
Instantiating contracts and interacting through shielded and transparent namespaces
Sync contract wrapper with transparent read-only access
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)
}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
sfoundryupsanvilbalanceOf, transfer, approve, and transferFrom do.0x4A), not be cast from a public variable.uint256 publicNumber = 100;
// Implicit casting -- will NOT compile
suint256 shielded = publicNumber; // Error
// Explicit casting -- correct
suint256 shielded = suint256(publicNumber); // OKbool 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.
}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);
}
}WagmiProvider
└─ QueryClientProvider
└─ [Wallet Provider] (RainbowKit / Privy / AppKit)
└─ ShieldedWalletProvider
└─ Your Appimport { seismicTestnet } from "seismic-react/rainbowkit";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>
)
}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()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",
});contract = w3.seismic.contract(address="0x...", abi=ABI)# 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])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}") 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");
_;
}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");
}
}Watch and decrypt SRC20 token events
See exactly what changes between a standard ERC20 and a private SRC20
Implement transfer() and transferFrom() with suint256
Validator staking operations on the Seismic deposit contract
Get a ShieldedContract instance for reads and writes
Set up AppKit (WalletConnect) with Seismic
Async contract wrapper with transparent read-only access
byteskeccak256(viewingKey)uint256suint256CSTORECLOADSSTORESLOADpublicpublictrue // 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");
_;
}
}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 testimport 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 revertimport {
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;
};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>
)
}declare namespace JSX {
interface IntrinsicElements {
"appkit-button": React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement>,
HTMLElement
>;
}
}// 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(value: U256, is_private: bool)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);
}
}depositContractWalletActions -- 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"),
});import { useShieldedContract } from "seismic-react";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..." });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}")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>
)
}wagmi 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 readsSet up RainbowKit with Seismic for wallet connection
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query seismic-react seismic-viemimport { 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>
)
}Set up Privy with Seismic for email and social login
npm install @privy-io/react-auth @privy-io/wagmi wagmi viem @tanstack/react-query seismic-react seismic-viemimport { 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>
)
}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()Send encrypted write transactions with shieldedWriteContract
Authenticated read calls that prove caller identity
Send encrypted write transactions to shielded contracts
Solidity interface reference for SRC20Factory and SRC20Token
.tread// 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); // 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");
_;
}
}import { 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>
)
}// 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>
)
}uv 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)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 resultnpm install seismic-reactyarn add seismic-reactpnpm add seismic-reactbun add seismic-reactnpm install react wagmi viem seismic-viem @tanstack/react-queryimport { 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 @rainbow-me/rainbowkitcd packages
bun create vite web --template react-ts
cd webbun add seismic-react@1.1.1 seismic-viem@1.1.1 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>
)// 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 leakedcreateSeismicDevnetuseShieldedContract0x66// 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!
}Web3 (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)contract.swrite.functionName() -- force shielded via getShieldedContract. Always encrypts, regardless of parameter types.import { 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
},
);msg.sender// 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();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>
)
}msg.sendercontract 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);
}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
}ECDH key exchange, AES-GCM calldata encryption, and the Seismic encryption pipeline
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 fieldsgetEncryption(networkPk, clientSk?)import { getEncryption } from "seismic-viem";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..."],
});seismic-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 generation// 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;
}React context provider for shielded wallet and public client
Sync contract wrapper with shielded and transparent namespaces
Create sync Web3 instance with public (read-only) Seismic access
getShieldedContract.read.write.tread.twritenetwork_pkimport { 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 { 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..."]);// 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));// The storage layout is public, so the slot is known, and an `sbool`
// has only two possible values — trivially guessable from the trie.
sbool private isWhitelisted;// 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");
}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,
)from 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_skif client_sk is None:
client_sk = PrivateKey(os.urandom(32))aes_key = generate_aes_key(client_sk, network_pk)Client 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)# 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))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>
}class 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)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)Read-only client with TEE key access and precompile support
Async contract wrapper with shielded and transparent namespaces
truesuint256uint256npm install seismic-viem viemyarn add seismic-viem viempnpm add seismic-viem viembun add seismic-viem viemfunction 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],
});// 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 viem@2.x.xnode --version
# Should be >= v18.0.0suint256 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


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",
});class 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 closedFull-featured client with encryption, shielded writes, and signed reads
import { createShieldedWalletClient } from "seismic-viem";const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});Holds AES key and encryption keypair derived from ECDH
@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: PrivateKeyencryptionSksecp256k1Signature()getEncryption()web3.py functionality.treadsrc/types/contract.ts: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 keyfrom 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)mkdir -p packages/web/src/abis/contracts
cp packages/contracts/out/ClownBeatdown.sol/ClownBeatdown.json \
packages/web/src/abis/contracts/ClownBeatdown.json{
"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,
};
};encryption_private_key should be stored securely if deterministic keys are useddef 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)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>
);
}Seismic precompiled contracts for cryptographic operations
import {
rng,
ecdh,
aesGcmEncrypt,
aesGcmDecrypt,
hdfk,
secp256k1Sig,
} from "seismic-viem";Emit transfer events with encrypted amounts using AES precompiles
import { 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 },
});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
}bytes instead of uint256 for the amount:// This will NOT compile
event Transfer(address indexed from, address indexed to, suint256 amount);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;
}Create async Web3 instance with full Seismic wallet capabilities
encryption_skNoneencryption = get_encryption(network_pk, encryption_sk)w3.seismic = AsyncSeismicNamespace(w3, encryption, private_key)web3.py functionalityimport 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))import { 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