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...
You'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 -vv// 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;
}TypeScript client libraries for Seismic
git clone "https://github.com/SeismicSystems/seismic-starter.git"
cd seismic-starter/packages/contracts
sforge test -vv// foundry tool -> seismic version of foundry tool
forge -> sforge
anvil -> sanvil
cast -> scastsuint256 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);# 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_KEY// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
contract Walnut {
suint256 kernel; // The shielded kernel (number inside the Walnut)
event Shake(address indexed player);
// Constructor to initialize the kernel
// Note: use uint256 here, not suint256 — constructor calldata is not
// encrypted (CREATE/CREATE2 does not use Seismic transactions), so
// shielded constructor parameters would leak their values.
constructor(uint256 _kernel) {
kernel = suint256(_kernel);
}
}# Assuming you are currently in the contracts directory
cd ../packages/clibun init -ycd packages/contractsutils.tsmkdir -p packages/cli/lib
touch packages/cli/lib/constants.ts packages/cli/lib/utils.ts
cd packages/cli/libsuint256[], sbool[], saddress[], etc.) — the length is stored as shielded.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
}sanvilfunction shake(suint256 _numShakes) public {
kernel += _numShakes; // Increment the kernel value using the shielded parameter.
emit Shake(msg.sender); // Log the shake event.
}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 uint256mkdir -p src && mv -t src index.ts{
"name": "walnut-cli",
"license": "MIT License",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"dotenv": "^16.4.7",
"seismic-viem": "^1.1.0",
"viem": "^2.22.3"
},
"devDependencies": {
"@types/node": "^22.7.6",
"typescript": "^5.6.3"
}
}node_modulessforge init --no-commit && 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 Walnut files in the same locations
touch src/Walnut.sol test/Walnut.t.sol script/Walnut.s.solimport { join } from 'path'
const CONTRACT_NAME = 'Walnut'
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 }mapping(address => suint256) private balances; // valid
mapping(uint256 => sbool) private flags; // valid
mapping(address => saddress) private recipients; // valid
mapping(saddress => uint256) private lookup; // INVALID — shielded keymkdir walnut-app
cd walnut-appmkdir -p packages/contracts packages/cli// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Walnut} from "../src/Walnut.sol";
contract WalnutScript is Script {
Walnut public walnut;
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVKEY");
vm.startBroadcast(deployerPrivateKey);
walnut = new Walnut(3, 0);
vm.stopBroadcast();
}
}sanvilaccount: a viem Account object (e.g. from privateKeyToAccount())curl https://sh.rustup.rs -sSf | shcurl -L \
-H "Accept: application/vnd.github.v3.raw" \
"https://api.github.com/repos/SeismicSystems/seismic-foundry/contents/sfoundryup/install?ref=seismic" | bash
source ~/.zshenv # or ~/.bashrc, depending on your shellsfoundryup
source ~/.zshenv # or ~/.bashrc, depending on your shellsforge clean # run in your project's contract directorybun 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-stdRPC_URL=http://127.0.0.1:8545
PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80source .env
sforge script script/Walnut.s.sol:WalnutScript \
--rpc-url $RPC_URL \
--broadcastimport { createShieldedWalletClient, sanvil } from 'seismic-viem'
import { http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
const walletClient = await createShieldedWalletClient({
chain: sanvil,
transport: http(),
account: privateKeyToAccount('0xabcdef...'),
})const contract = getShieldedContract({
abi: myContractAbi,
address: '0x1234...',
client: shieldedWalletClient,
})// Perform a shielded write
await contract.write.myFunction([arg1, arg2], { gas: 50000n })
// Perform a signed read
const value = await contract.read.getValue()
console.log('Value:', value)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-querysuint256 a = suint256(10);
suint256 b = suint256(3);
// == EXAMPLES
a > b // sbool(true)
a | b // suint256(11)
a << 2 // suint256(40)
a % b // suint256(1)sint256 a = sint256(-10);
sint256 b = sint256(3);
// == EXAMPLES
a < b // sbool(true)
a + b // sint256(-7)
a * b // sint256(-30)sbool a = sbool(true);
sbool b = sbool(false);
// == EXAMPLES
a && b // sbool(false)
!b // sbool(true)saddress a = saddress(0x123);
saddress b = saddress(0x456);
// == VALID EXAMPLES
a == b // sbool(false)
b.code
b.codehash
// == INVALID EXAMPLES
a.balance // must cast to address first
a.call("") // must cast to address firstsbytes32 a = sbytes32(0xabcd);
sbytes1 b = sbytes1(0xff);suint256 a = suint256(42); // explicit cast
suint256 b = 42s; // s suffix — same resultExecute authenticated read calls on shielded contracts
import { 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(),
},
});mapping(address => suint256) balanceOf;
mapping(address => mapping(address => suint256)) allowance;
suint256 totalSupply;
function transfer(address to, suint256 amount) public {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}# Assuming you are in packages/cli/lib
cd ../src
touch app.tsimport { useShieldedWallet } from "seismic-react";contract = w3.seismic.contract(address="0x...", abi=ABI)import {
seismicTestnet,
sanvil,
localSeismicDevnet,
createSeismicDevnet,
} from "seismic-react/rainbowkit";msg.sendereth_callmsg.senderaddress(0)truesuint256uint256"SHELL_INTACT".setUp()npm install seismic-viem viemyarn add seismic-viem viempnpm add seismic-viem viembun add seismic-viem viemsuint256 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);
}function 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://gcp-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],
}); uint256 shellStrength; // The strength of the Walnut's shell.
constructor(uint256 _shellStrength, uint256 _kernel) {
shellStrength = _shellStrength; // Set the initial shell strength.
kernel = suint256(_kernel); // Initialize the kernel (cast to shielded).
} // Event to log hits
event Hit(address indexed hitter, uint256 remainingShellStrength);
// Modifier to ensure the shell is not cracked.
modifier requireIntact() {
require(shellStrength > 0, "SHELL_ALREADY_CRACKED");
_;
}
// Function to hit the walnut shell
function hit() public requireIntact {
shellStrength--; // Decrease the shell strength.
emit Hit(msg.sender, shellStrength); // Log the hit event.
} function shake(suint256 _numShakes) public requireIntact {
kernel += _numShakes; // Increment the kernel value using the shielded parameter.
emit Shake(msg.sender); // Log the shake event.
} // Function to reveal the kernel if the shell is fully cracked.
function look() public view requireCracked returns (uint256) {
return uint256(kernel); // Reveal the kernel as a standard uint256.
}
// Modifier to ensure the shell is fully cracked before revealing the kernel.
modifier requireCracked() {
require(shellStrength == 0, "SHELL_INTACT"); // Ensure the shell is broken before revealing the kernel.
_;
}// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
contract Walnut {
uint256 shellStrength; // The strength of the Walnut's shell.
suint256 kernel; // The hidden kernel (number inside the Walnut).
// Events
event Hit(address indexed hitter, uint256 remainingShellStrength); // Logs when the Walnut is hit.
event Shake(address indexed shaker); // Logs when the Walnut is shaken.
// Constructor to initialize the shell and kernel.
constructor(uint256 _shellStrength, uint256 _kernel) {
shellStrength = _shellStrength; // Set the initial shell strength.
kernel = suint256(_kernel); // Initialize the kernel (cast to shielded).
}
// Function to hit the Walnut and reduce its shell strength.
function hit() public requireIntact {
shellStrength--; // Decrease the shell strength.
emit Hit(msg.sender, shellStrength); // Log the hit action.
}
// Function to shake the Walnut and increment the kernel.
function shake(suint256 _numShakes) public requireIntact {
kernel += _numShakes; // Increment the kernel by the given number of shakes.
emit Shake(msg.sender); // Log the shake action.
}
// Function to reveal the kernel if the shell is fully cracked.
function look() public view requireCracked returns (uint256) {
return uint256(kernel); // Reveal the kernel as a standard uint256.
}
// Modifier to ensure the shell is fully cracked before revealing the kernel.
modifier requireCracked() {
require(shellStrength == 0, "SHELL_INTACT"); // Ensure the shell is broken before revealing the kernel.
_;
}
// Modifier to ensure the shell is not cracked.
modifier requireIntact() {
require(shellStrength > 0, "SHELL_ALREADY_CRACKED");
_;
}
}import {
type ShieldedContract,
type ShieldedWalletClient,
createShieldedWalletClient,
getShieldedContract,
} from 'seismic-viem'
import { Abi, Address, Chain, http } 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 Devnet or Anvil)
rpcUrl: string // RPC URL for blockchain communication
}
contract: {
abi: Abi // The contract's ABI for interaction
address: Address // The contract's deployed address
}
}export class App {
private config: AppConfig // Holds all app configuration
private playerClients: Map<string, ShieldedWalletClient> = new Map() // Maps player names to their wallet clients
private playerContracts: Map<string, ShieldedContract> = new Map() // Maps player names to their contract instances
constructor(config: AppConfig) {
this.config = config
}
}async init() {
for (const player of this.config.players) {
// Create a wallet client for the player
const walletClient = await createShieldedWalletClient({
chain: this.config.wallet.chain,
transport: http(this.config.wallet.rpcUrl),
account: privateKeyToAccount(player.privateKey as `0x${string}`),
})
this.playerClients.set(player.name, walletClient) // Map the client to the player
// Initialize the player's contract instance and ensure the contract is deployed
const contract = await getShieldedContractWithCheck(
walletClient,
this.config.contract.abi,
this.config.contract.address
)
this.playerContracts.set(player.name, contract) // Map the contract to the player
}
}private getWalletClient(playerName: string): ShieldedWalletClient {
const client = this.playerClients.get(playerName)
if (!client) {
throw new Error(`Wallet client for player ${playerName} not found`)
}
return client
}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)
const walletClient = this.getWalletClient(playerName)
await walletClient.waitForTransactionReceipt({
hash: await contract.write.reset([], { gas: 100000n })
})
}async shake(playerName: string, numShakes: number) {
console.log(`- Player ${playerName} writing shake()`)
const contract = this.getPlayerContract(playerName)
await contract.write.shake([numShakes], { gas: 50000n }) // signed write
}async hit(playerName: string) {
console.log(`- Player ${playerName} writing hit()`)
const contract = this.getPlayerContract(playerName)
const walletClient = this.getWalletClient(playerName)
await contract.write.hit([], { gas: 100000n })
}async look(playerName: string) {
console.log(`- Player ${playerName} reading look()`)
const contract = this.getPlayerContract(playerName)
const result = await contract.read.look() // signed read
console.log(`- Player ${playerName} sees number:`, result)
}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>
)
}# 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])SEISMIC_TX_TYPE: int = 0x4A[0x4A] + RLP([chainId, nonce, gasPrice, gas, to, value, data, ...])// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {Walnut} from "../src/Walnut.sol";
contract WalnutTest is Test {
Walnut public walnut;
function setUp() public {
// Initialize a Walnut with shell strength = 2 and kernel = 0
walnut = new Walnut(2, 0);
}
}function test_Hit() public {
walnut.hit(); // Decrease shell strength by 1
walnut.hit(); // Fully crack the shell
assertEq(walnut.look(), 0); // Kernel should still be 0 since no shakes
}function test_Shake() public {
walnut.shake(10s); // Shake the Walnut, increasing the kernel
walnut.hit(); // Decrease shell strength by 1
walnut.hit(); // Fully crack the shell
assertEq(walnut.look(), 10); // Kernel should be 10 after 10 shakes
}function test_Reset() public {
walnut.hit(); // Decrease shell strength by 1
walnut.shake(suint256(2)); // Shake the Walnut
walnut.hit(); // Fully crack the shell
walnut.reset(); // Reset the Walnut
assertEq(walnut.getShellStrength(), 2); // Shell strength should reset to initial value
walnut.hit(); // Start hitting again
walnut.shake(5s); // Shake the Walnut again
walnut.hit(); // Fully crack the shell again
assertEq(walnut.look(), 5); // Kernel should reflect the shakes in the new round
}function test_CannotHitWhenCracked() public {
walnut.hit(); // Decrease shell strength by 1
walnut.hit(); // Fully crack the shell
vm.expectRevert("SHELL_ALREADY_CRACKED"); // Expect revert when hitting an already cracked shell
walnut.hit();
}function test_CannotShakeWhenCracked() public {
walnut.hit(); // Decrease shell strength by 1
walnut.shake(suint256(1)); // Shake the Walnut
walnut.hit(); // Fully crack the shell
vm.expectRevert("SHELL_ALREADY_CRACKED"); // Expect revert when shaking an already cracked shell
walnut.shake(suint256(1));
}function test_CannotLookWhenIntact() public {
walnut.hit(); // Partially crack the shell
walnut.shake(suint256(1)); // Shake the Walnut
vm.expectRevert("SHELL_INTACT"); // Expect revert when trying to look at the kernel with the shell intact
walnut.look();
}function test_CannotResetWhenIntact() public {
walnut.hit(); // Partially crack the shell
walnut.shake(suint256(1)); // Shake the Walnut
vm.expectRevert("SHELL_INTACT"); // Expect revert when trying to reset without cracking the shell
walnut.reset();
}function test_ManyActions() public {
uint256 shakes = 0;
for (uint256 i = 0; i < 50; i++) {
if (walnut.getShellStrength() > 0) {
if (i % 25 == 0) {
walnut.hit(); // Hit the shell every 25 iterations
} else {
uint256 numShakes = (i % 3) + 1; // Random shakes between 1 and 3
walnut.shake(suint256(numShakes));
shakes += numShakes;
}
}
}
assertEq(walnut.look(), shakes); // Kernel should match the total number of shakes
}function test_RevertWhen_NonContributorTriesToLook() public {
address nonContributor = address(0xabcd);
walnut.hit(); // Decrease shell strength by 1
walnut.shake(3s); // Shake the Walnut
walnut.hit(); // Fully crack the shell
vm.prank(nonContributor); // Impersonate a non-contributor
vm.expectRevert("NOT_A_CONTRIBUTOR"); // Expect revert when non-contributor calls `look()`
walnut.look();
}function test_ContributorInRound2() public {
address contributorRound2 = address(0xabcd); // Contributor for round 2
// Round 1: Cracked by address(this)
walnut.hit(); // Hit 1
walnut.hit(); // Hit 2
assertEq(walnut.look(), 0); // Confirm kernel value
walnut.reset(); // Start Round 2
// Round 2: ContributorRound2 cracks the Walnut
vm.prank(contributorRound2);
walnut.hit();
vm.prank(contributorRound2);
walnut.shake(5s); // Shake kernel 5 times
vm.prank(contributorRound2);
walnut.hit();
vm.prank(contributorRound2);
assertEq(walnut.look(), 5); // Kernel value is 5 for contributorRound2
vm.expectRevert("NOT_A_CONTRIBUTOR"); // address(this) cannot look in round 2
walnut.look();
}sforge build
sforge test// ESM (recommended)
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
// CJS
const { createShieldedWalletClient, seismicTestnet } = require("seismic-viem");mkdir my-seismic-app && cd my-seismic-app
npm init -y
npm install seismic-viem viemimport { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
const blockNumber = await client.getBlockNumber();
console.log("Connected! Block:", blockNumber);npx tsx index.ts{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
}
}npm ls viem
# Should show [email protected]node --version
# Should be >= v18.0.0React hooks and providers for Seismic, composing with wagmi to add shielded wallet management, encrypted transactions, and signed reads to React apps.
Network configurations and chain constants
Authenticated read calls that prove caller identity
Send encrypted write transactions with shieldedWriteContract
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 { 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..." });import { useSignedReadContract } from "seismic-react";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>
)
}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://gcp-1.seismictest.net/rpc",
)
# Create read-only contract instance
contract = PublicContract(
w3=w3,
address="0x1234567890123456789012345678901234567890",
abi=CONTRACT_ABI,
)
# Read public contract state (auto-decoded)
total_supply = contract.tread.totalSupply() # int
print(f"Total supply: {total_supply}")
balance = contract.tread.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") # int
print(f"Balance: {balance}")# Single return values are returned directly
number = contract.tread.getNumber() # int
name = contract.tread.getName() # str
is_active = contract.tread.isActive() # bool
# Multiple outputs are returned as a tuple
user_name, user_balance, active = contract.tread.getUserInfo(
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
)# Read array of addresses (auto-decoded to list)
holders = contract.tread.getHolders()
print(f"Found {len(holders)} holders")
for holder in holders:
print(f" - {holder}")try:
value = contract.tread.getNumber()
print(f"Value: {value}")
except ValueError as e:
print(f"RPC error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")import { 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
sfoundryupsanvilnpm 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>
)
}import os
from seismic_web3 import SEISMIC_TESTNET, SANVIL, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Connect to testnet
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Connect to local Sanvil
w3 = SANVIL.wallet_client(pk)localSeismicDevnetcreateSeismicDevnetuseShieldedContractifelse uint256 initialShellStrength; // Store the starting shell strength for resets.
suint256 initialKernel; // Store the starting kernel value for resets.
uint256 round; // The current round number.
// Event to log resets.
event Reset(uint256 indexed newRound, uint256 shellStrength);
function reset() public requireCracked {
shellStrength = initialShellStrength; // Restore the shell strength.
kernel = initialKernel; // Reset the kernel to its original value.
round++; // Increment the round counter.
emit Reset(round, shellStrength); // Log the reset action.
} // Mapping to track contributions: hitsPerRound[round][player] → number of hits.
mapping(uint256 => mapping(address => uint256)) hitsPerRound; function hit() public requireIntact {
shellStrength--; // Decrease the shell strength.
hitsPerRound[round][msg.sender]++; // Record the player's contribution for the current round.
emit Hit(round, msg.sender, shellStrength); // Log the hit event.
} modifier onlyContributor() {
require(hitsPerRound[round][msg.sender] > 0, "NOT_A_CONTRIBUTOR"); // Check if the caller contributed in the current round.
_;
} // Look at the kernel if the shell is cracked and the caller contributed.
function look() public view requireCracked onlyContributor returns (uint256) {
return uint256(kernel); // Return the kernel value.
}// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;
contract Walnut {
uint256 initialShellStrength; // The initial shell strength for resets.
uint256 shellStrength; // The current shell strength.
uint256 round; // The current round number.
suint256 initialKernel; // The initial hidden kernel value for resets.
suint256 kernel; // The current hidden kernel value.
// Tracks the number of hits per player per round.
mapping(uint256 => mapping(address => uint256)) hitsPerRound;
// Events to log hits, shakes, and resets.
// Event to log hits.
event Hit(uint256 indexed round, address indexed hitter, uint256 remaining);
// Event to log shakes.
event Shake(uint256 indexed round, address indexed shaker);
// Event to log resets.
event Reset(uint256 indexed newRound, uint256 shellStrength);
constructor(uint256 _shellStrength, uint256 _kernel) {
initialShellStrength = _shellStrength; // Set the initial shell strength.
shellStrength = _shellStrength; // Initialize the shell strength.
initialKernel = suint256(_kernel); // Set the initial kernel value (cast to shielded).
kernel = suint256(_kernel); // Initialize the kernel value (cast to shielded).
round = 1; // Start with the first round.
}
// Get the current shell strength.
function getShellStrength() public view returns (uint256) {
return shellStrength;
}
// Hit the Walnut to reduce its shell strength.
function hit() public requireIntact {
shellStrength--; // Decrease the shell strength.
hitsPerRound[round][msg.sender]++; // Record the player's hit for the current round.
emit Hit(round, msg.sender, shellStrength); // Log the hit.
}
// Shake the Walnut to increase the kernel value.
function shake(suint256 _numShakes) public requireIntact {
kernel += _numShakes; // Increment the kernel value.
emit Shake(round, msg.sender); // Log the shake.
}
// Reset the Walnut for a new round.
function reset() public requireCracked {
shellStrength = initialShellStrength; // Reset the shell strength.
kernel = initialKernel; // Reset the kernel value.
round++; // Move to the next round.
emit Reset(round, shellStrength); // Log the reset.
}
// Look at the kernel if the shell is cracked and the caller contributed.
function look() public view requireCracked onlyContributor returns (uint256) {
return uint256(kernel); // Return the kernel value.
}
// Modifier to ensure the shell is fully cracked.
modifier requireCracked() {
require(shellStrength == 0, "SHELL_INTACT");
_;
}
// Modifier to ensure the shell is not cracked.
modifier requireIntact() {
require(shellStrength > 0, "SHELL_ALREADY_CRACKED");
_;
}
// Modifier to ensure the caller has contributed in the current round.
modifier onlyContributor() {
require(hitsPerRound[round][msg.sender] > 0, "NOT_A_CONTRIBUTOR");
_;
}
}touch .envCHAIN_ID=31337
RPC_URL=http://127.0.0.1:8545
ALICE_PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
BOB_PRIVKEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690dimport dotenv from 'dotenv'
import { join } from 'path'
import { seismicTestnet, sanvil } 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.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.reset('Alice')
await app.shake('Alice', 2)
await app.hit('Alice')
await app.shake('Alice', 4)
await app.hit('Alice')
await app.shake('Alice', 1)
await app.hit('Alice')
await app.look('Alice') console.log('=== Round 2 ===')
await app.reset('Bob')
await app.hit('Bob')
await app.shake('Bob', 1)
await app.hit('Bob')
await app.shake('Bob', 1)
await app.hit('Bob')
// Bob looks at the number in round 2
await app.look('Bob') // Alice tries to look in round 2, should fail by reverting
console.log('=== Testing Access Control ===')
console.log("Attempting Alice's look() in Bob's round (should revert)")
try {
await app.look('Alice')
console.error('❌ Expected look() to revert but it succeeded')
process.exit(1)
} catch (error) {
console.log('✅ Received expected revert')
}
}
main()bun dev=== Round 1 ===
- Player Alice writing shake()
- Player Alice writing hit()
- Player Alice writing shake()
- Player Alice writing hit()
- Player Alice writing shake()
- Player Alice writing hit()
- Player Alice reading look()
- Player Alice sees number: 7n
=== Round 2 ===
- Player Bob writing reset()
- Player Bob writing hit()
- Player Bob writing shake()
- Player Bob writing hit()
- Player Bob writing shake()
- Player Bob writing hit()
- Player Bob reading look()
- Player Bob sees number: 3n
=== Testing Access Control ===
- Attempting Alice's look() in Bob's round (should revert)
✅ Received expected revertwagmi 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 readsimport os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Wallet client (requires private key)
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Async wallet (auto-selects ws_url when ws=True)
w3 = await SEISMIC_TESTNET.async_wallet_client(pk, ws=True)
# Public client (no private key needed)
public = SEISMIC_TESTNET.public_client()from seismic_web3 import SEISMIC_TESTNET, SANVIL
# Testnet properties
SEISMIC_TESTNET.rpc_url # "https://gcp-1.seismictest.net/rpc"
SEISMIC_TESTNET.ws_url # "wss://gcp-1.seismictest.net/ws"
SEISMIC_TESTNET.chain_id # 5124
SEISMIC_TESTNET.name # "Seismic Testnet (GCP-1)"
# Sanvil properties
SANVIL.rpc_url # "http://127.0.0.1:8545"
SANVIL.ws_url # "ws://127.0.0.1:8545"
SANVIL.chain_id # 31337
SANVIL.name # "Sanvil (local)"from seismic_web3 import make_seismic_testnet
# Connect to different GCP testnet instances
testnet_2 = make_seismic_testnet(2) # gcp-2.seismictest.net
testnet_3 = make_seismic_testnet(3) # gcp-3.seismictest.net
w3 = testnet_2.wallet_client(pk)from seismic_web3 import ChainConfig, PrivateKey
custom = ChainConfig(
chain_id=5124,
rpc_url="https://gcp-1.seismictest.net/rpc",
ws_url="wss://gcp-1.seismictest.net/ws",
name="Seismic Testnet",
)
pk = PrivateKey(...)
w3 = custom.wallet_client(pk)# Sync wallet client
w3 = chain.wallet_client(private_key, encryption_sk=None)
# Async wallet client (HTTP)
w3 = await chain.async_wallet_client(private_key, encryption_sk=None, ws=False)
# Async wallet client (WebSocket)
w3 = await chain.async_wallet_client(private_key, ws=True)# Sync public client
public = chain.public_client()
# Async public client (HTTP)
public = chain.async_public_client(ws=False)
# Async public client (WebSocket)
public = chain.async_public_client(ws=True)from seismic_web3 import SEISMIC_TESTNET_CHAIN_ID, SANVIL_CHAIN_ID
SEISMIC_TESTNET_CHAIN_ID # 5124
SANVIL_CHAIN_ID # 31337def make_seismic_testnet(
n: int = 1,
*,
host: str | None = None,
) -> ChainConfig:import os
from seismic_web3 import make_seismic_testnet, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# GCP node 1 (same as SEISMIC_TESTNET)
testnet_1 = make_seismic_testnet(1)
w3_1 = testnet_1.wallet_client(pk)
# GCP node 2
testnet_2 = make_seismic_testnet(2)
w3_2 = testnet_2.wallet_client(pk)from seismic_web3 import make_seismic_testnet
testnet = make_seismic_testnet(host="my-testnet.example.com")
print(testnet.rpc_url) # "https://my-testnet.example.com/rpc"from seismic_web3 import make_seismic_testnet
# No argument defaults to GCP node 1
testnet = make_seismic_testnet()
print(testnet.rpc_url) # "https://gcp-1.seismictest.net/rpc"SEISMIC_TESTNET: ChainConfig = make_seismic_testnet(1)// 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));// BAD: Leaks the value of `isVIP` via gas difference
sbool isVIP = /* ... */;
if (isVIP) {
discount = 50s;
} else {
// extra work only in the else branch — gas difference reveals which path ran
suint256 tmp = 0s;
for (uint256 i = 0; i < 10; i++) {
tmp = tmp + 1s;
}
discount = tmp;
}// BETTER: Both arms are identical operations (single assignment), same gas either way
discount = isVIP ? 50s : 0s;// Both forms embed `42` in bytecode
suint256 a = suint256(42);
suint256 b = 42s;// BAD: Leaks the value of `shieldedCount` via gas
suint256 shieldedCount = /* ... */;
for (uint256 i = 0; i < uint256(shieldedCount); i++) {
// Each iteration is visible in gas cost
}// BETTER: Fixed-size loop with constant iteration count
uint256 constant MAX_ITERATIONS = 100;
for (uint256 i = 0; i < MAX_ITERATIONS; i++) {
// Use a shielded condition to decide whether to actually do work.
// IMPORTANT: Both the "real work" and "no-op" paths must use
// the same gas -- e.g., write to the same slots, do the same
// number of arithmetic ops, etc.
}// BAD: Anyone can call this and read the shielded value
suint256 private _secretBalance;
function secretBalance() external view returns (uint256) {
return uint256(_secretBalance);
}suint256 private _secretBalance;
function secretBalance() external view returns (uint256) {
require(msg.sender == owner, "Not authorized");
return uint256(_secretBalance);
}// Will NOT compile
suint256 public secretBalance;
// Will NOT compile
function getSecret() external view returns (suint256) {
return secretBalance;
}// This function accepts shielded input, but nothing prevents calling it
// with a regular (non-Seismic) transaction — which would leak `amount`.
function deposit(suint256 amount) external {
balances[msg.sender] += amount;
}// BAD: Gas cost reveals the value of `shieldedExp`
suint256 base = 2s;
suint256 shieldedExp = /* ... */;
suint256 result = base ** shieldedExp; // Gas cost leaks shieldedExpenum Status { Active, Inactive, Suspended }
// Only 3 possible values (0, 1, 2) — shielding provides little protection
suint256 shieldedStatus = suint256(uint256(Status.Active));// Will NOT compile — compiler error
suint256 immutable SECRET = 42s;
// Will NOT compile — compiler error
suint256 constant MY_VALUE = 1s;// In theory, a proposer could simulate this output and decide
// whether to include the transaction based on the result.
function drawWinner() external {
suint256 rand = rng256();
uint256 winnerIndex = uint256(rand) % participants.length;
winner = participants[winnerIndex];
}Emit transfer events with encrypted amounts using AES precompiles
Creating sync and async Seismic clients
w3.seismic)w3.seismic)Pre-configured local development network
Send encrypted write transactions to shielded contracts
import { getShieldedContract } from "seismic-viem";import { getShieldedContract } from "seismic-viem";
const abi = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "account", type: "address" }],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
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: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ name: "", type: "bool" }],
},
] as const;
const contract = getShieldedContract({
abi,
address: "0x1234567890abcdef1234567890abcdef12345678",
client,
});const balance = await contract.read.balanceOf(["0x1234..."]);const hash = await contract.write.transfer(["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..."]);// Signed read -- msg.sender = your address
const myBalance = await contract.read.balanceOf(["0x1234..."]);
// Transparent read -- msg.sender = zero address
const totalSupply = await contract.tread.totalSupply();import { signedReadContract } from "seismic-viem";import { signedReadContract } from "seismic-viem";
const balance = await signedReadContract(client, {
address: "0x1234567890abcdef1234567890abcdef12345678",
abi: myContractAbi,
functionName: "balanceOf",
args: ["0xMyAddress..."],
});import { signedCall } from "seismic-viem";
const result = await signedCall(client, {
to: "0x1234567890abcdef1234567890abcdef12345678",
data: "0x...", // raw calldata
account: client.account,
gas: 30_000_000n,
});// Signed read -- proves identity, encrypted calldata
const myBalance = await contract.read.balanceOf(["0x1234..."]);
// Transparent read -- no identity, plaintext calldata
const totalSupply = await contract.tread.totalSupply();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],
securityParams: {
blocksWindow: 50n, // expires after 50 blocks instead of 100
},
});npm install @rainbow-me/rainbowkitnpm 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"
}
}SEISMIC_TESTNET: ChainConfig = make_seismic_testnet(1)ChainConfig(
chain_id=5124,
rpc_url="https://gcp-1.seismictest.net/rpc",
ws_url="wss://gcp-1.seismictest.net/ws",
name="Seismic Testnet",
)from seismic_web3 import SEISMIC_TESTNET
# Access configuration
print(SEISMIC_TESTNET.rpc_url) # "https://gcp-1.seismictest.net/rpc"
print(SEISMIC_TESTNET.ws_url) # "wss://gcp-1.seismictest.net/ws"
print(SEISMIC_TESTNET.chain_id) # 5124
print(SEISMIC_TESTNET.name) # "Seismic Testnet"import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Now use w3.seismic methods
balance = w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")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 wss://gcp-1.seismictest.net/ws)
w3 = await SEISMIC_TESTNET.async_wallet_client(pk, ws=True)from seismic_web3 import SEISMIC_TESTNET
# Sync
public_sync = SEISMIC_TESTNET.public_client()
block = public_sync.eth.get_block("latest")
# Async
public_async = SEISMIC_TESTNET.async_public_client()
block = await public_async.eth.get_block("latest")import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Get contract
contract = w3.seismic.contract(
address="0x00000000219ab540356cBB839Cbe05303d7705Fa",
abi=[...],
)
# Send shielded write
tx_hash = contract.swrite.your_function(arg1, arg2)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
# Load private key from environment
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Create client
w3 = SEISMIC_TESTNET.wallet_client(pk)from seismic_web3 import SEISMIC_TESTNET
public = SEISMIC_TESTNET.public_client()
# Verify connection
try:
chain_id = public.eth.chain_id
block_number = public.eth.block_number
print(f"Connected to chain {chain_id} at block {block_number}")
except Exception as e:
print(f"Connection failed: {e}")from seismic_web3 import SEISMIC_TESTNET_CHAIN_ID
SEISMIC_TESTNET_CHAIN_ID # 5124event ConfidentialEvent(suint256 confidentialData); // Compilation errornpm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query seismic-react seismic-viempip 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()npm install @privy-io/react-auth @privy-io/wagmi wagmi viem @tanstack/react-query seismic-react seismic-viem0x4A0x66.get_tee_public_key() - Get TEE public keycontract() - Create contract wrappers (.tread only)// 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;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SRC20 is AccessControl {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => suint256) balanceOf;
mapping(address => mapping(address => suint256)) allowance;
mapping(address => bool) public frozen;
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
bytes32 public constant AUDITOR_ROLE = keccak256("AUDITOR_ROLE");
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
event AccountFrozen(address indexed account);
event AccountUnfrozen(address indexed account);
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = suint256(_initialSupply);
// Deployer gets admin role and can grant other roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// --- Standard token functions (with freeze check) ---
function transfer(address to, suint256 amount) public returns (bool) {
require(!frozen[msg.sender], "Account frozen");
require(!frozen[to], "Recipient frozen");
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, uint256(amount));
return true;
}
function approve(address spender, suint256 amount) public returns (bool) {
require(!frozen[msg.sender], "Account frozen");
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, uint256(amount));
return true;
}
function transferFrom(address from, address to, suint256 amount) public returns (bool) {
require(!frozen[from], "Account frozen");
require(!frozen[to], "Recipient frozen");
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
require(balanceOf[from] >= amount, "Insufficient balance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, uint256(amount));
return true;
}
// --- User balance query (signed read) ---
function getBalance(address account) external view returns (uint256) {
require(msg.sender == account, "Only owner can view balance");
return uint256(balanceOf[account]);
}
// --- Compliance functions ---
function complianceBalanceOf(address account) external view returns (uint256) {
require(
hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized: requires COMPLIANCE_ROLE"
);
return uint256(balanceOf[account]);
}
function complianceFreeze(address account) external {
require(
hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized: requires COMPLIANCE_ROLE"
);
frozen[account] = true;
emit AccountFrozen(account);
}
function complianceUnfreeze(address account) external {
require(
hasRole(COMPLIANCE_ROLE, msg.sender),
"Not authorized: requires COMPLIANCE_ROLE"
);
frozen[account] = false;
emit AccountUnfrozen(account);
}
// --- Auditor functions ---
function auditBalanceOf(address account) external view returns (uint256) {
require(
hasRole(AUDITOR_ROLE, msg.sender),
"Not authorized: requires AUDITOR_ROLE"
);
return uint256(balanceOf[account]);
}
}// Grant compliance role to a specific address
token.grantRole(COMPLIANCE_ROLE, complianceOfficerAddress);
// Grant auditor role
token.grantRole(AUDITOR_ROLE, auditorAddress);const token = getShieldedContract({
abi: src20Abi,
address: SRC20_ADDRESS,
client: adminWalletClient,
});
// Grant compliance role
await token.write.grantRole([COMPLIANCE_ROLE, complianceOfficerAddress]);const complianceClient = await createShieldedWalletClient({
chain: seismicDevnet,
transport: http(RPC_URL),
account: privateKeyToAccount(COMPLIANCE_OFFICER_KEY),
});
const complianceToken = getShieldedContract({
abi: src20Abi,
address: SRC20_ADDRESS,
client: complianceClient,
});
// This is a signed read -- response encrypted to the compliance officer's key
const balance = await complianceToken.read.complianceBalanceOf([targetAccount]);
console.log("Account balance:", balance);npm install seismic-viem seismic-react viem wagmi @tanstack/react-queryimport { ShieldedWalletProvider } from "seismic-react";
import { WagmiProvider, createConfig, http } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { seismicTestnet } from "seismic-viem";
const config = createConfig({
chains: [seismicTestnet],
transports: {
[seismicTestnet.id]: http("https://gcp-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>
);
}// 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!
}import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My Seismic App",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [seismicTestnet],
});import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import '@rainbow-me/rainbowkit/styles.css'
const queryClient = new QueryClient()
function App({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}import { ConnectButton } from '@rainbow-me/rainbowkit'
function Header() {
return (
<header>
<ConnectButton />
</header>
)
}import { useShieldedWallet } from 'seismic-react'
function MyComponent() {
const { walletClient, loaded, error } = useShieldedWallet()
if (!loaded) return <div>Initializing shielded wallet...</div>
if (error) return <div>Error: {error}</div>
return <div>Connected!</div>
}// app/providers.tsx
'use client'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
import '@rainbow-me/rainbowkit/styles.css'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet, sanvil } from "seismic-react/rainbowkit";
const config = getDefaultConfig({
appName: "My Seismic App",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [
...(process.env.NODE_ENV === "development" ? [sanvil] : []),
seismicTestnet,
],
});'use client'
import { RainbowKitProvider, ConnectButton, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider, useShieldedWallet } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
import '@rainbow-me/rainbowkit/styles.css'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
function WalletStatus() {
const { walletClient, publicClient, loaded, error } = useShieldedWallet()
if (!loaded) return <p>Initializing shielded wallet...</p>
if (error) return <p>Error: {error}</p>
if (!walletClient) return <p>Connect your wallet to get started.</p>
return (
<div>
<p>Shielded wallet ready</p>
<p>Public client: {publicClient ? 'Available' : 'Loading...'}</p>
</div>
)
}
export default function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
<ConnectButton />
<WalletStatus />
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}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)pip install seismic-web3uv add seismic-web3import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# HTTP
w3 = await SEISMIC_TESTNET.async_wallet_client(pk)
# WebSocket (auto-selects ws_url from chain config)
w3 = await SEISMIC_TESTNET.async_wallet_client(pk, ws=True)from seismic_web3 import SEISMIC_TESTNET
# Sync
public = SEISMIC_TESTNET.public_client()
# Async
public = SEISMIC_TESTNET.async_public_client()import os
from seismic_web3 import SEISMIC_TESTNET, SANVIL, PrivateKey
# Seismic testnet
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Sanvil testnet
w3 = SANVIL.wallet_client(pk)import { 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>
)
}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://gcp-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}")SANVIL: ChainConfig = ChainConfig(
chain_id=31337,
rpc_url="http://127.0.0.1:8545",
ws_url="ws://127.0.0.1:8545",
name="Sanvil (local)",
)from seismic_web3 import SANVIL
# Access configuration
print(SANVIL.rpc_url) # "http://127.0.0.1:8545"
print(SANVIL.ws_url) # "ws://127.0.0.1:8545"
print(SANVIL.chain_id) # 31337
print(SANVIL.name) # "Sanvil (local)"import os
from seismic_web3 import SANVIL, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SANVIL.wallet_client(pk)
# Now use w3.seismic methods
balance = w3.eth.get_balance("0xYourAddress")import os
from seismic_web3 import SANVIL, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# HTTP
w3 = await SANVIL.async_wallet_client(pk)
# WebSocket
w3 = await SANVIL.async_wallet_client(pk, ws=True)from seismic_web3 import SANVIL
# Sync
public = SANVIL.public_client()
# Async
public = SANVIL.async_public_client()
# Read-only operations
block = await public.eth.get_block("latest")from seismic_web3 import SANVIL, PrivateKey
# Use well-known anvil test account
TEST_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
pk = PrivateKey(TEST_PRIVATE_KEY)
w3 = SANVIL.wallet_client(pk)
# Deploy and interact with contracts locally
contract = w3.seismic.contract(
address="0xYourContractAddress",
abi=[...],
)from seismic_web3 import SANVIL, PrivateKey
# Anvil provides 10 test accounts with known private keys
accounts = [
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
]
# Create clients for different accounts
clients = [SANVIL.wallet_client(PrivateKey(pk)) for pk in accounts]from seismic_web3 import SANVIL
public = SANVIL.public_client()
try:
chain_id = public.eth.chain_id
block_number = public.eth.block_number
print(f"Connected to local node (chain {chain_id}) at block {block_number}")
except Exception as e:
print(f"Local node not running: {e}")import pytest
from seismic_web3 import SANVIL, PrivateKey
@pytest.fixture
def w3():
"""Fixture for local Sanvil client."""
pk = PrivateKey.from_hex_str("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
return SANVIL.wallet_client(pk)
def test_shielded_write(w3):
"""Test shielded write on local node."""
contract = w3.seismic.contract(address="0x...", abi=[...])
tx_hash = contract.swrite.my_function(42)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
assert receipt.status == 1from seismic_web3 import SANVIL_CHAIN_ID
SANVIL_CHAIN_ID # 31337# First test account (10000 ETH)
ACCOUNT_0 = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# Second test account (10000 ETH)
ACCOUNT_1 = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
# And 8 more...Derive encryption state from TEE public key using ECDH
Contract interaction namespaces for shielded and transparent operations
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..."),
});
// Shielded write — calldata encrypted automatically
const hash = await client.writeContract({
address: "0x...",
abi: myContractAbi,
functionName: "transfer",
args: ["0x...", 100n],
});
// Signed read — proves caller identity
const balance = await client.readContract({
address: "0x...",
abi: myContractAbi,
functionName: "balanceOf",
args: ["0x..."],
account: client.account,
});seismic-viem
├── Client Layer
│ ├── createShieldedPublicClient — read-only, TEE key, precompiles
│ └── createShieldedWalletClient — full capabilities, encryption pipeline
├── Contract Layer
│ ├── getShieldedContract — .read / .write / .tread / .twrite / .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 generationimport { 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("0xabc123...");
const addrUrl = publicClient.addressExplorerUrl("0x742d35Cc...");
const blockUrl = publicClient.blockExplorerUrl(12345n);
console.log("Transaction:", txUrl);
console.log("Address:", addrUrl);
console.log("Block:", blockUrl);1. Client generates an ephemeral secp256k1 keypair (or uses a provided one)
2. Client fetches the TEE public key from the node via seismic_getTeePublicKey
3. ECDH(client_sk, tee_pk) → shared secret
4. Shared secret → AES-256 key (via key derivation)
5. For each transaction:
a. Generate a random 12-byte nonce
b. Encode TxSeismicMetadata as Additional Authenticated Data (AAD)
c. AES-GCM encrypt(plaintext_calldata, nonce, aad) → ciphertext
d. Include encryptionPubkey + nonce in the transaction's SeismicTxExtras fieldsimport { getEncryption } from "seismic-viem";import { createShieldedPublicClient, getEncryption } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
// Fetch the TEE public key from the node
const publicClient = createShieldedPublicClient({
chain: seismicTestnet,
transport: http(),
});
const teePublicKey = await publicClient.getTeePublicKey();
// Derive encryption keys without constructing a full wallet client
const encryption = getEncryption(teePublicKey);
console.log("AES key:", encryption.aesKey);
console.log("Encryption public key:", encryption.encryptionPublicKey);
console.log("Encryption private key:", encryption.encryptionPrivateKey);
// Or provide your own private key for deterministic key derivation
const deterministicEncryption = getEncryption(
teePublicKey,
"0xYourSecp256k1PrivateKey",
);import { createShieldedWalletClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
// Access the derived AES key
const aesKey = walletClient.getEncryption();
console.log("AES key:", aesKey);
// Access the encryption public key
const encPubKey = walletClient.getEncryptionPublicKey();
console.log("Encryption public key:", encPubKey);import { ShieldedWalletProvider } from "seismic-react";type OnAddressChangeParams = {
publicClient: ShieldedPublicClient;
walletClient: ShieldedWalletClient;
address: Hex;
};interface WalletClientContextType {
publicClient: ShieldedPublicClient | null;
walletClient: ShieldedWalletClient | null;
address: Hex | null;
error: string | null;
loaded: boolean;
}import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
export default function App({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}'use client'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { ShieldedWalletProvider } from 'seismic-react'
import { seismicTestnet } from 'seismic-react/rainbowkit'
const config = getDefaultConfig({
appName: 'My Seismic App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [seismicTestnet],
})
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}WagmiProvider
└─ QueryClientProvider
└─ RainbowKitProvider
└─ ShieldedWalletProvider
└─ Your Appimport { ShieldedWalletProvider } from 'seismic-react'
function App() {
const handleAddressChange = async ({
publicClient,
walletClient,
address,
}) => {
console.log('Connected:', address)
// Example: read user's shielded balance on connect
const balance = await publicClient.readContract({
address: TOKEN_ADDRESS,
abi: tokenAbi,
functionName: 'balanceOf',
args: [address],
})
console.log('Balance:', balance)
}
return (
<ShieldedWalletProvider
config={config}
options={{ onAddressChange: handleAddressChange }}
>
<YourApp />
</ShieldedWalletProvider>
)
}import { useShieldedWallet } from 'seismic-react'
function MyComponent() {
const { error, loaded } = useShieldedWallet()
if (!loaded) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
return <div>Connected</div>
}npm install seismic-react seismic-viem wagmi viem @rainbow-me/rainbowkit @tanstack/react-query// config.ts
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";
export const config = getDefaultConfig({
appName: "Seismic Basic dApp",
projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
chains: [seismicTestnet],
});// providers.tsx
'use client'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ShieldedWalletProvider } from 'seismic-react'
import { config } from './config'
import '@rainbow-me/rainbowkit/styles.css'
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ShieldedWalletProvider config={config}>
{children}
</ShieldedWalletProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}// ShieldedCounter.tsx
'use client'
import { useShieldedWriteContract, useSignedReadContract } from 'seismic-react'
import { useState } from 'react'
const CONTRACT_ADDRESS = '0x...' as const
const ABI = [
{
name: 'increment',
type: 'function',
stateMutability: 'nonpayable',
inputs: [],
outputs: [],
},
{
name: 'getCount',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'uint256' }],
},
] as const
export function ShieldedCounter() {
const [count, setCount] = useState<string | null>(null)
const { writeContract, isLoading: isWriting, hash, error: writeError } = useShieldedWriteContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'increment',
})
const { signedRead, isLoading: isReading, error: readError } = useSignedReadContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'getCount',
})
const handleIncrement = async () => {
try {
await writeContract()
} catch (err) {
console.error('Shielded write failed:', err)
}
}
const handleRead = async () => {
try {
const result = await signedRead()
setCount(result?.toString() ?? 'unknown')
} catch (err) {
console.error('Signed read failed:', err)
}
}
return (
<div>
<h2>Shielded Counter</h2>
<button onClick={handleIncrement} disabled={isWriting}>
{isWriting ? 'Sending...' : 'Increment (Shielded Write)'}
</button>
{hash && <p>Tx hash: {hash}</p>}
{writeError && <p>Write error: {writeError.message}</p>}
<button onClick={handleRead} disabled={isReading}>
{isReading ? 'Reading...' : 'Get Count (Signed Read)'}
</button>
{count !== null && <p>Count: {count}</p>}
{readError && <p>Read error: {readError.message}</p>}
</div>
)
}// App.tsx
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { useShieldedWallet } from 'seismic-react'
import { ShieldedCounter } from './ShieldedCounter'
export function App() {
const { loaded, error } = useShieldedWallet()
return (
<div>
<h1>Seismic Basic dApp</h1>
<ConnectButton />
{error && <p>Wallet error: {error}</p>}
{loaded ? (
<ShieldedCounter />
) : (
<p>Connect your wallet to interact with shielded contracts.</p>
)}
</div>
)
}User connects wallet
|
v
ShieldedWalletProvider creates shielded clients (ECDH with TEE)
|
v
useShieldedWriteContract useSignedReadContract
- encrypts calldata - signs the read request
- sends TxSeismic - node verifies identity
- returns tx hash - returns decrypted resultimport { 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>
)
}contract = w3.seismic.contract(address="0x...", abi=ABI)network_pkclient_pubkey = private_key_to_compressed_public_key(client_sk)return EncryptionState(
aes_key=aes_key,
encryption_pubkey=client_pubkey,
encryption_private_key=client_sk,
)def get_encryption(
network_pk: CompressedPublicKey,
client_sk: PrivateKey | None = None,
) -> EncryptionStatefrom seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
# Get TEE public key (from node)
tee_pk = CompressedPublicKey("0x02abcd...")
# Derive encryption state (random ephemeral key)
encryption = get_encryption(tee_pk)
print(f"AES key: {encryption.aes_key.to_0x_hex()}")
print(f"Client pubkey: {encryption.encryption_pubkey.to_0x_hex()}")import os
from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
tee_pk = CompressedPublicKey("0x02abcd...")
# Use a deterministic client key
client_sk = PrivateKey.from_hex_str(os.environ["CLIENT_KEY"])
encryption = get_encryption(tee_pk, client_sk)import os
from seismic_web3 import get_encryption, get_tee_public_key, PrivateKey
from web3 import Web3
# This is what create_wallet_client() does internally
w3 = Web3(Web3.HTTPProvider("https://gcp-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)// 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 leakedCreate async Web3 instance with public (read-only) Seismic access
w3.eth, w3.net)w3.seismic)Sync contract wrapper with shielded and transparent namespaces
.write - Encrypted Write.read - Encrypted Read.twrite - Transparent Write.tread - Transparent Read.dwrite - Debug Write# 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)import { createShieldedWalletClient } from "seismic-viem";const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});import { createShieldedWalletClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const walletClient = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});
// Shielded write -- calldata is encrypted
const hash = await walletClient.writeContract({
address: "0xContractAddress",
abi: contractAbi,
functionName: "transfer",
args: ["0xRecipient", 1000n],
});
// Signed read -- authenticated eth_call
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,
});// Debug write: inspect the transaction without broadcasting
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 keycontract() - Create contract wrappers (.tread methods are async)deposit() - Requires private keygas_price: int | None - Gas price in wei (default: network suggested)security: [SeismicSecurityParams](../api-reference/transaction-types/seismic-security-params.md) | None - Security parameters for expirydef create_async_public_client(
provider_url: str,
*,
ws: bool = False,
) -> AsyncWeb3from seismic_web3 import create_async_public_client
# Create async public client
w3 = create_async_public_client("https://gcp-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://gcp-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://gcp-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://gcp-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://gcp-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://gcp-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://gcp-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)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://gcp-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://gcp-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)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";balanceOf, transfer, approve, and transferFrom do..twrite.tread.dwriteencrypt() and decrypt() methods that handle AES-GCM encryption with metadata-bound Additional Authenticated Data (AAD).Shielded write namespace for encrypted contract transactions
.write When.write WhenTransparent write namespace for standard contract transactions
.write Instead When.write for these)saddress to Payableclass 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://gcp-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://gcp-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 closed@dataclass
class EncryptionState:
"""Holds the AES key and encryption keypair derived from ECDH.
Created by :func:`get_encryption` during client setup. Pure
computation - works in both sync and async contexts.
Attributes:
aes_key: 32-byte AES-256 key derived from ECDH + HKDF.
encryption_pubkey: Client's compressed secp256k1 public key.
encryption_private_key: Client's secp256k1 private key.
"""
aes_key: Bytes32
encryption_pubkey: CompressedPublicKey
encryption_private_key: PrivateKeydef encrypt(
self,
plaintext: HexBytes,
nonce: EncryptionNonce,
metadata: TxSeismicMetadata,
) -> HexBytesfrom seismic_web3 import get_encryption, EncryptionNonce
from hexbytes import HexBytes
import os
# Setup encryption state
encryption = get_encryption(tee_public_key, client_private_key)
# Encrypt calldata
plaintext = HexBytes("0x1234abcd...")
nonce = EncryptionNonce(os.urandom(12))
ciphertext = encryption.encrypt(
plaintext=plaintext,
nonce=nonce,
metadata=tx_metadata,
)
# Ciphertext is len(plaintext) + 16 bytes (auth tag)
assert len(ciphertext) == len(plaintext) + 16def decrypt(
self,
ciphertext: HexBytes,
nonce: EncryptionNonce,
metadata: TxSeismicMetadata,
) -> HexBytesfrom seismic_web3 import get_encryption
from cryptography.exceptions import InvalidTag
encryption = get_encryption(tee_public_key, client_private_key)
try:
plaintext = encryption.decrypt(
ciphertext=encrypted_data,
nonce=nonce,
metadata=tx_metadata,
)
print(f"Decrypted: {plaintext.to_0x_hex()}")
except InvalidTag:
print("Decryption failed: authentication tag mismatch")import os
from seismic_web3 import create_wallet_client, PrivateKey
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = create_wallet_client("https://gcp-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)@dataclass(frozen=True)
class ChainConfig:
"""Immutable configuration for a Seismic network.
Attributes:
chain_id: Numeric chain identifier.
rpc_url: HTTP(S) JSON-RPC endpoint.
ws_url: WebSocket endpoint (``None`` if not available).
name: Human-readable network name.
"""
chain_id: int
rpc_url: str
ws_url: str | None = None
name: str = ""from seismic_web3 import ChainConfig
config = ChainConfig(
chain_id=5124,
rpc_url="https://gcp-1.seismictest.net/rpc",
ws_url="wss://gcp-1.seismictest.net/ws",
name="Seismic Testnet",
)config = ChainConfig(
chain_id=5124,
rpc_url="https://gcp-1.seismictest.net/rpc",
name="Seismic Testnet",
)from seismic_web3 import SEISMIC_TESTNET, SANVIL
# Pre-configured testnet
SEISMIC_TESTNET
# Pre-configured local Sanvil
SANVILdef wallet_client(
self,
private_key: PrivateKey,
*,
encryption_sk: PrivateKey | None = None,
) -> Web3:import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey
pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)
# Now use w3.seismic methods
balance = w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")async def async_wallet_client(
self,
private_key: PrivateKey,
*,
encryption_sk: PrivateKey | None = None,
ws: bool = False,
) -> AsyncWeb3: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
w3 = await SEISMIC_TESTNET.async_wallet_client(pk, ws=True)
# Use async methods
balance = await w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")def public_client(self) -> Web3:from seismic_web3 import SEISMIC_TESTNET
public = SEISMIC_TESTNET.public_client()
# Read-only operations
block = public.eth.get_block("latest")
tee_key = public.seismic.get_tee_public_key()def async_public_client(
self,
*,
ws: bool = False,
) -> AsyncWeb3:from seismic_web3 import SEISMIC_TESTNET
# HTTP
public = SEISMIC_TESTNET.async_public_client()
# WebSocket
public = SEISMIC_TESTNET.async_public_client(ws=True)
# Read-only operations
block = await public.eth.get_block("latest")# Old (deprecated)
w3 = config.create_client(private_key)
# New
w3 = config.wallet_client(private_key)# Old (deprecated)
w3 = await config.create_async_client(private_key)
# New
w3 = await config.async_wallet_client(private_key)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",
});uint256 publicNumber = 100;
// Implicit casting -- will NOT compile
suint256 shielded = publicNumber; // Error
// Explicit casting -- correct
suint256 shielded = suint256(publicNumber); // OK.write for high-value transactions (extra privacy worth the cost)tx_hash = contract.twrite.functionName(arg1, arg2, ...)# Single argument
tx_hash = contract.twrite.setNumber(42)
# Multiple arguments
tx_hash = contract.twrite.transfer(recipient_address, 1000)
# Complex types
tx_hash = contract.twrite.batchTransfer(
["0x123...", "0x456..."],
[100, 200],
)from seismic_web3 import create_wallet_client
# Create client and contract
w3 = create_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Basic transparent write
tx_hash = contract.twrite.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']}")from seismic_web3 import create_async_wallet_client
# Create async client and contract
w3 = await create_async_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Basic transparent write
tx_hash = await contract.twrite.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']}")# Send 1 ETH with the transaction
tx_hash = contract.twrite.deposit(
value=10**18, # 1 ETH in wei
)# Legacy gas pricing
tx_hash = contract.twrite.transfer(
recipient,
amount,
gas=100_000,
gasPrice=20 * 10**9, # 20 gwei
)# EIP-1559 gas pricing
tx_hash = contract.twrite.transfer(
recipient,
amount,
gas=100_000,
maxFeePerGas=50 * 10**9, # 50 gwei max
maxPriorityFeePerGas=2 * 10**9, # 2 gwei priority
)# Explicitly set nonce
nonce = w3.eth.get_transaction_count(w3.eth.default_account)
tx_hash = contract.twrite.transfer(
recipient,
amount,
nonce=nonce,
)tx_hash = contract.twrite.deposit(
value=10**17, # 0.1 ETH
gas=200_000,
maxFeePerGas=40 * 10**9, # 40 gwei
maxPriorityFeePerGas=2 * 10**9, # 2 gwei
)# Sync
tx_hash = contract.twrite.transfer(recipient, amount)
assert isinstance(tx_hash, HexBytes)
# Async
tx_hash = await contract.twrite.transfer(recipient, amount)
assert isinstance(tx_hash, HexBytes)# This transaction is completely visible
tx_hash = contract.twrite.transfer(
"0x1234...", # Recipient visible
1000, # Amount visible
)# Public token approval (amount is public anyway)
tx_hash = contract.twrite.approve(spender, amount)
# Public state update (no sensitive data)
tx_hash = contract.twrite.setNumber(42)# Private balance transfer — use .write
tx_hash = contract.write.transfer(recipient, secret_amount)
# Private withdrawal — use .write
tx_hash = contract.write.withdraw(amount)from web3.exceptions import TransactionNotFound, TimeExhausted
try:
tx_hash = contract.twrite.transfer(recipient, amount)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
if receipt['status'] == 0:
print("Transaction reverted")
else:
print("Transaction succeeded")
except ValueError as e:
print(f"Transaction failed: {e}")
except TimeExhausted:
print("Transaction not mined within timeout")# Uses default account from web3
w3.eth.default_account = "0x..."
tx_hash = contract.twrite.transfer(recipient, amount)# All middleware is applied (gas estimation, nonce management, etc.)
from web3.middleware import geth_poa_middleware
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
tx_hash = contract.twrite.transfer(recipient, amount)# Gas is auto-estimated if not provided
tx_hash = contract.twrite.setNumber(42) # Gas estimated automatically# Approve + transfer pattern
approve_tx = contract.twrite.approve(spender, amount)
w3.eth.wait_for_transaction_receipt(approve_tx)
transfer_tx = contract.twrite.transferFrom(owner, recipient, amount)
w3.eth.wait_for_transaction_receipt(transfer_tx)tx_hash = w3.eth.send_transaction({
"to": contract_address,
"data": "0x...", # Encoded calldata
"value": 0,
"gas": 100_000,
})bool 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.
}Signed read namespace for encrypted contract queries
.read When.read WhenCreate async Web3 instance with full Seismic wallet capabilities
w3.eth, w3.net)w3.seismic)tx_hash = contract.write.functionName(arg1, arg2, ...)# Single argument
tx_hash = contract.write.setNumber(42)
# Multiple arguments
tx_hash = contract.write.transfer(recipient_address, 1000)
# Complex types
tx_hash = contract.write.batchTransfer(
["0x123...", "0x456..."],
[100, 200],
)from seismic_web3 import create_wallet_client
# Create client and contract
w3 = create_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Basic write
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']}")from seismic_web3 import create_async_wallet_client
# Create async client and contract
w3 = await create_async_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Basic write
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']}")# Send 1 ETH with the transaction
tx_hash = contract.write.deposit(
value=10**18, # 1 ETH in wei
)# Set gas limit and gas price
tx_hash = contract.write.transfer(
recipient,
amount,
gas=100_000,
gas_price=20 * 10**9, # 20 gwei
)from seismic_web3 import SeismicSecurityParams
# Use longer expiry window (200 blocks instead of 100)
security = SeismicSecurityParams(blocks_window=200)
tx_hash = contract.write.transfer(
recipient,
amount,
security=security,
)from seismic_web3 import SeismicSecurityParams
security = SeismicSecurityParams(blocks_window=150)
tx_hash = contract.write.deposit(
value=10**17, # 0.1 ETH
gas=200_000,
gas_price=25 * 10**9, # 25 gwei
security=security,
)# Sync
tx_hash = contract.write.transfer(recipient, amount)
assert isinstance(tx_hash, HexBytes)
# Async
tx_hash = await contract.write.transfer(recipient, amount)
assert isinstance(tx_hash, HexBytes)from web3.exceptions import TransactionNotFound, TimeExhausted
try:
tx_hash = contract.write.transfer(recipient, amount)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
if receipt['status'] == 0:
print("Transaction reverted")
else:
print("Transaction succeeded")
except ValueError as e:
print(f"Transaction failed: {e}")
except TimeExhausted:
print("Transaction not mined within timeout")from hexbytes import HexBytes
tx_hash = w3.seismic.send_shielded_transaction(
to="0x...",
data=HexBytes("0x..."),
value=0,
gas=100_000,
gas_price=10**9,
)npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query seismic-react seismic-viemtuplemsg.sender in any wayencryption_sk is None, a random ephemeral key is created)await deposit() - Deposit ETH/tokensresult = contract.read.functionName(arg1, arg2, ...)# No arguments
result = contract.read.balanceOf()
# Single argument
result = contract.read.balanceOf(owner_address)
# Multiple arguments
result = contract.read.getUserInfo(user_address)from seismic_web3 import create_wallet_client
# Create client and contract
w3 = create_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Results are automatically decoded
balance = contract.read.balanceOf() # int
print(f"Balance: {balance}")
is_active = contract.read.isActive() # bool
name = contract.read.getName() # strfrom seismic_web3 import create_async_wallet_client
# Create async client and contract
w3 = await create_async_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Results are automatically decoded
balance = await contract.read.balanceOf()
print(f"Balance: {balance}")# Single argument — returns decoded value directly
owner_balance = contract.read.balanceOf(owner_address)
# Multiple arguments
allowance = contract.read.allowance(owner_address, spender_address)# Increase gas for complex reads
result = contract.read.getItems(
offset,
limit,
gas=50_000_000,
)from seismic_web3 import SeismicSecurityParams
# Use longer expiry window
security = SeismicSecurityParams(blocks_window=200)
result = contract.read.balanceOf(security=security)# Simulate sending 1 ETH with the read
result = contract.read.deposit(
value=10**18, # 1 ETH in wei
)# Single return value — unwrapped from tuple
balance = contract.read.balanceOf() # int
is_odd = contract.read.isOdd() # bool
name = contract.read.getName() # str
# Multiple return values — tuple
name, balance, active = contract.read.getUserInfo(user_address)
# Array return
holders = contract.read.getHolders() # list// SRC20 balanceOf — uses msg.sender internally
function balanceOf() external view returns (suint256) {
return balances[msg.sender];
}# Proves your identity — msg.sender is your address
balance = contract.read.balanceOf() # Returns your balance (int)# Does NOT prove identity — msg.sender is 0x0
balance = contract.tread.balanceOf() # Returns 0x0's balance (usually 0)try:
balance = contract.read.balanceOf()
print(f"Balance: {balance}")
except ValueError as e:
print(f"Call failed: {e}")from hexbytes import HexBytes
result = w3.seismic.signed_call(
to="0x...",
data=HexBytes("0x..."),
gas=30_000_000,
)import asyncio
# Read multiple values concurrently — each is auto-decoded
balance, name, active = await asyncio.gather(
contract.read.balanceOf(),
contract.read.getName(),
contract.read.isActive(),
)import asyncio
try:
result = await asyncio.wait_for(
contract.read.balanceOf(),
timeout=10.0, # 10 seconds
)
except asyncio.TimeoutError:
print("Read timed out")import { 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>
)
}declare namespace JSX {
interface IntrinsicElements {
"appkit-button": React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement>,
HTMLElement
>;
}
}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>
)
}encryption = get_encryption(network_pk, encryption_sk)w3.seismic = AsyncSeismicNamespace(w3, encryption, private_key)async def create_async_wallet_client(
provider_url: str,
private_key: PrivateKey,
*,
encryption_sk: PrivateKey | None = None,
ws: bool = False,
) -> AsyncWeb3import os
from seismic_web3 import create_async_wallet_client, PrivateKey
# Load private key
private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
# Create async wallet client
w3 = await create_async_wallet_client(
"https://gcp-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://gcp-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://gcp-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://gcp-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://gcp-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))Seismic precompiled contracts for cryptographic operations
Precompile<P, R> TypeCreate sync Web3 instance with public (read-only) Seismic access
w3.eth, w3.net)w3.seismic)result = contract.tread.functionName(arg1, arg2, ...)# No arguments
result = contract.tread.totalSupply()
# Single argument
result = contract.tread.balanceOf(address)
# Multiple arguments
result = contract.tread.allowance(owner, spender)from seismic_web3 import create_public_client
# Create client and contract
w3 = create_public_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Results are automatically decoded
total_supply = contract.tread.totalSupply() # int
print(f"Total supply: {total_supply}")from seismic_web3 import create_async_public_client
# Create async client and contract
w3 = create_async_public_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Results are automatically decoded
total_supply = await contract.tread.totalSupply()
print(f"Total supply: {total_supply}")# Single argument — returns decoded value directly
balance = contract.tread.balanceOf("0x1234...") # int
# Multiple arguments
allowance = contract.tread.allowance("0x1234...", "0x5678...") # int# Function returns multiple values — returned as tuple
name, balance, active = contract.tread.getUserInfo(user_address)
print(f"Name: {name}")
print(f"Balance: {balance}")
print(f"Active: {active}")# Array return — returned as list
holders = contract.tread.getHolders() # list[str]
# Struct-like return (multiple outputs) — returned as tuple
max_deposit, fee_rate, paused = contract.tread.getConfig()# Single return value — unwrapped from tuple
total_supply = contract.tread.totalSupply() # int
name = contract.tread.name() # str
# Multiple return values — tuple
max_deposit, fee_rate, paused = contract.tread.getConfig()
# Array return
holders = contract.tread.getHolders() # list// SRC20 balanceOf — uses msg.sender internally
function balanceOf() external view returns (suint256) {
return balances[msg.sender];
}# msg.sender is 0x0 — returns 0x0's balance (int)
balance = contract.tread.balanceOf() # Almost always 0# msg.sender is your address — returns your balance (int)
balance = contract.read.balanceOf() # Your actual balance// These work fine with .tread
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function name() external view returns (string);
function getConfig() external view returns (uint256, uint256, bool);// These require .read (signed read) — they use msg.sender internally
function balanceOf() external view returns (suint256); // SRC20
function allowance() external view returns (suint256); // SRC20# This call is completely visible
result = contract.tread.balanceOf("0x1234...")# Public token metadata — auto-decoded
name = contract.tread.name() # str
symbol = contract.tread.symbol() # str
decimals = contract.tread.decimals() # int
# Public balances
balance = contract.tread.balanceOf(address) # int
# Public configuration
item_count = contract.tread.getItemCount() # int# SRC20 balanceOf uses msg.sender — use .read
my_balance = contract.read.balanceOf()
# SRC20 allowance uses msg.sender — use .read
my_allowance = contract.read.allowance(spender_address)try:
balance = contract.tread.balanceOf(address)
print(f"Balance: {balance}")
except Exception as e:
print(f"Call failed: {e}")# BAD: SRC20 balanceOf uses msg.sender — returns 0x0's balance
balance = contract.tread.balanceOf()# GOOD: Returns your actual balance
balance = contract.read.balanceOf()import asyncio
# Read multiple values concurrently — each is auto-decoded
total_supply, name, symbol = await asyncio.gather(
contract.tread.totalSupply(),
contract.tread.name(),
contract.tread.symbol(),
)import asyncio
try:
result = await asyncio.wait_for(
contract.tread.totalSupply(),
timeout=5.0, # 5 seconds
)
except asyncio.TimeoutError:
print("Read timed out")# Use low-level eth_call for historical reads
from seismic_web3.contract.abi import encode_shielded_calldata
data = encode_shielded_calldata(abi, "totalSupply", [])
result = w3.eth.call(
{"to": contract_address, "data": data},
block_identifier=12345678, # Historical block
)result = w3.eth.call({
"to": contract_address,
"data": "0x...", # Encoded calldata
})import {
rng,
ecdh,
aesGcmEncrypt,
aesGcmDecrypt,
hdfk,
secp256k1Sig,
} from "seismic-viem";contract() - Create contract wrappers (.tread only)deposit() - Requires private key(value, is_private)suint256CSTORECLOADis_privatetrueimport { 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...");def create_public_client(rpc_url: str) -> Web3from seismic_web3 import create_public_client
# Create public client
w3 = create_public_client("https://gcp-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://gcp-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://gcp-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://gcp-1.seismictest.net/rpc")
print(stats)w3 = Web3(Web3.HTTPProvider(rpc_url))w3.seismic = SeismicPublicNamespace(w3)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 testresult = contract.dwrite.functionName(arg1, arg2, ...)# Single argument
result = contract.dwrite.setNumber(42)
# Multiple arguments
result = contract.dwrite.transfer(recipient_address, 1000)
# Complex types
result = contract.dwrite.batchTransfer(
["0x123...", "0x456..."],
[100, 200],
)@dataclass(frozen=True)
class DebugWriteResult:
plaintext_tx: PlaintextTx # Transaction before encryption
shielded_tx: UnsignedSeismicTx # Transaction with encrypted calldata
tx_hash: HexBytes # Transaction hashfrom seismic_web3 import create_wallet_client
# Create client and contract
w3 = create_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Debug write
result = contract.dwrite.setNumber(42)
# Access all three components
print(f"Transaction hash: {result.tx_hash.to_0x_hex()}")
print(f"Plaintext data: {result.plaintext_tx.data.to_0x_hex()}")
print(f"Encrypted data: {result.shielded_tx.data.to_0x_hex()}")
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(result.tx_hash)
print(f"Status: {receipt['status']}")from seismic_web3 import create_async_wallet_client
# Create async client and contract
w3 = await create_async_wallet_client(...)
contract = w3.seismic.contract(address="0x...", abi=ABI)
# Debug write
result = await contract.dwrite.setNumber(42)
# Access components
print(f"Transaction hash: {result.tx_hash.to_0x_hex()}")
# Wait for confirmation
receipt = await w3.eth.wait_for_transaction_receipt(result.tx_hash)
print(f"Status: {receipt['status']}")result = contract.dwrite.transfer(recipient, 1000)
# Examine what was encoded before encryption
plaintext_data = result.plaintext_tx.data
print(f"Function selector: {plaintext_data[:4].to_0x_hex()}")
print(f"Full calldata: {plaintext_data.to_0x_hex()}")result = contract.dwrite.transfer(recipient, 1000)
# Compare plaintext vs encrypted
plaintext_len = len(result.plaintext_tx.data)
encrypted_len = len(result.shielded_tx.data)
print(f"Plaintext length: {plaintext_len}")
print(f"Encrypted length: {encrypted_len}")
print(f"Difference: {encrypted_len - plaintext_len} bytes")
# AES-GCM adds 16-byte authentication tag
assert encrypted_len == plaintext_len + 16result = contract.dwrite.transfer(
recipient,
amount,
value=10**17,
gas=150_000,
)
# Access plaintext parameters
print(f"To: {result.plaintext_tx.to}")
print(f"Value: {result.plaintext_tx.value} wei")
print(f"Gas: {result.plaintext_tx.gas}")
# Access shielded parameters
shielded = result.shielded_tx
print(f"Nonce: {shielded.nonce}")
print(f"Gas price: {shielded.gas_price}")
print(f"Expires at block: {shielded.seismic.expires_at_block}")result = contract.dwrite.batchTransfer(recipients, amounts)
# Gas limit used for this transaction
gas_limit = result.plaintext_tx.gas
print(f"Gas limit: {gas_limit}")
# Wait for receipt
receipt = w3.eth.wait_for_transaction_receipt(result.tx_hash)
gas_used = receipt['gasUsed']
print(f"Gas used: {gas_used}")
print(f"Unused gas: {gas_limit - gas_used}")# Development: use .dwrite to inspect
result = contract.dwrite.transfer(recipient, amount)
print(f"Plaintext: {result.plaintext_tx.data.to_0x_hex()}")
tx_hash = result.tx_hash
# Production: use .write (same behavior, no debug info)
tx_hash = contract.write.transfer(recipient, amount)# Verify calldata is correctly encoded
result = contract.dwrite.batchTransfer(recipients, amounts)
# Inspect function selector
selector = result.plaintext_tx.data[:4]
print(f"Function selector: {selector.to_0x_hex()}")
# Verify it matches expected selector
from web3 import Web3
expected_selector = Web3.keccak(text="batchTransfer(address[],suint256[])")[:4]
assert selector == expected_selectorresult = contract.dwrite.withdraw(amount)
# Compare plaintext vs encrypted
print("Plaintext calldata:")
print(result.plaintext_tx.data.to_0x_hex())
print("\nEncrypted calldata:")
print(result.shielded_tx.data.to_0x_hex())
# Verify lengths
print(f"\nPlaintext: {len(result.plaintext_tx.data)} bytes")
print(f"Encrypted: {len(result.shielded_tx.data)} bytes")result = contract.dwrite.transfer(recipient, large_amount)
# Audit all parameters
plaintext = result.plaintext_tx
shielded = result.shielded_tx
print(f"From: {w3.eth.default_account}")
print(f"To: {plaintext.to}")
print(f"Value: {plaintext.value} wei ({plaintext.value / 10**18} ETH)")
print(f"Gas limit: {plaintext.gas}")
print(f"Expires at block: {shielded.seismic.expires_at_block}")
print(f"Transaction hash: {result.tx_hash.to_0x_hex()}")from seismic_web3 import SeismicSecurityParams, EncryptionNonce
# Use explicit nonce for testing
nonce = EncryptionNonce(b'\x00' * 12)
security = SeismicSecurityParams(
blocks_window=200,
encryption_nonce=nonce,
)
result = contract.dwrite.transfer(
recipient,
amount,
security=security,
)
# Verify parameters were applied
assert result.shielded_tx.seismic.encryption_nonce == nonce# Development/testing
result = contract.dwrite.transfer(recipient, amount)
tx_hash = result.tx_hash
# Production
tx_hash = contract.write.transfer(recipient, amount)from web3.exceptions import TimeExhausted
try:
result = contract.dwrite.transfer(recipient, amount)
# Transaction broadcast succeeded
print(f"Tx hash: {result.tx_hash.to_0x_hex()}")
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(result.tx_hash, timeout=120)
if receipt['status'] == 0:
print("Transaction reverted")
# Inspect calldata to debug
print(f"Plaintext: {result.plaintext_tx.data.to_0x_hex()}")
else:
print("Transaction succeeded")
except ValueError as e:
print(f"Transaction failed: {e}")
except TimeExhausted:
print("Transaction not mined within timeout")# 1. Test with .dwrite
result = contract.dwrite.transfer(recipient, small_amount)
# 2. Inspect calldata
print(f"Plaintext: {result.plaintext_tx.data.to_0x_hex()}")
# 3. Verify encryption worked
assert result.plaintext_tx.data != result.shielded_tx.data
# 4. Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(result.tx_hash)
# 5. If successful, switch to .write for production
if receipt['status'] == 1:
# Use .write in production
tx_hash = contract.write.transfer(recipient, large_amount)from hexbytes import HexBytes
result = w3.seismic.debug_send_shielded_transaction(
to="0x...",
data=HexBytes("0x..."),
value=0,
gas=100_000,
gas_price=10**9,
)
# Access debug fields
print(result.plaintext_tx.data.to_0x_hex())
print(result.shielded_tx.data.to_0x_hex())
print(result.tx_hash.to_0x_hex())(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);
}
}w3 = Web3(Web3.HTTPProvider(rpc_url))network_pk = get_tee_public_key(w3)encryption_sk = encryption_sk or PrivateKey(os.urandom(32))def 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://gcp-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://gcp-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://gcp-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_idencryption = get_encryption(network_pk, encryption_sk)w3.seismic = SeismicNamespace(w3, encryption, private_key)suint256 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-dbimport {
seismicTestnet,
sanvil,
localSeismicDevnet,
createSeismicDevnet,
} from "seismic-viem";import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: seismicTestnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, sanvil } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: sanvil,
transport: http(),
account: privateKeyToAccount("0x..."),
});import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createShieldedWalletClient, localSeismicDevnet } from "seismic-viem";
const client = await createShieldedWalletClient({
chain: localSeismicDevnet,
transport: http(),
account: privateKeyToAccount("0x..."),
});Are you developing against a live network?
-> Use seismicTestnet
Are you running Sanvil locally for rapid iteration?
-> Use sanvil
Are you running seismic-reth --dev locally?
-> Use localSeismicDevnet
Do you need a custom chain configuration?
-> Use createSeismicDevnet()import { SEISMIC_TX_TYPE } from "seismic-viem";
console.log(SEISMIC_TX_TYPE); // 74 (0x4A)import { createSeismicDevnet } from "seismic-viem";
const myChain = createSeismicDevnet({
nodeHost: "https://my-seismic-node.example.com",
explorerUrl: "https://explorer.example.com",
});import { createSeismicAzTestnet, createSeismicGcpTestnet } from "seismic-viem";
// Azure-hosted testnet instance N
const azChain = createSeismicAzTestnet(1);
// GCP-hosted testnet instance N
const gcpChain = createSeismicGcpTestnet(1);interface SeismicTxExtras {
encryptionPubkey: Hex; // Client's compressed secp256k1 public key
encryptionNonce: Hex; // AES-GCM nonce for calldata encryption
messageVersion: number; // Seismic message format version
recentBlockHash: Hex; // Recent block hash for replay protection
expiresAtBlock: bigint; // Block number at which the transaction expires
signedRead: boolean; // Whether this is a signed read request
}

