arrow-left

Only this pageAll pages
gitbookPowered by GitBook
triangle-exclamation
Couldn't generate the PDF for 233 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

General

Overview

Loading...

Loading...

Loading...

Loading...

Loading...

Getting Started

Loading...

Loading...

Loading...

Tutorials

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Seismic Solidity

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Clients

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...

Quickstart

You're two commands away from running an encrypted protocol

You can play around with shielded types using our starter repositoryarrow-up-right. This assumes you went through everything in Installation.

git clone "https://github.com/SeismicSystems/seismic-starter.git"
cd seismic-starter/packages/contracts
sforge test -vv

Or if you prefer a more detailed walkthrough of building on Seismic, check out our Tutorials section.

Setting Up Your Project

In this chapter, you’ll set up the foundation for the Walnut App using a clean and modular monorepo-style workspace. This structure separates concerns into distinct areas, making your project easier to navigate and maintain. You’ll create a contracts directory to house your Seismic smart contracts and tests, and a cli directory to serve as the command-line interface for interacting with those contracts. By the end of this chapter, you’ll have a fully initialized project, complete with dependencies, formatting tools, and a seamless environment for both contract development and interaction.

Building the CLI

In this section, you will write a CLI to interact with the deployed Walnut smart contract from scratch, simulating a multiplayer, multi-round game consisting of two players, Alice and Bob. At the end of this section, you will be able to see a full game play out on your local Seismic node! Estimated time: ~30 minutes

Welcome

Private by Default. Familiar by Design.

Seismic is an EVM blockchain with native on-chain privacy. Write Solidity. Deploy with Foundry. Interact with Viem. The only difference: your users' data stays private.


hashtag
One letter changes everything

The difference between a public ERC20 and a private SRC20 is one letter:

The s prefix tells the Seismic compiler to shield the underlying value. Observers see 0x00...0 instead of actual balances and amounts. Everything else — the Solidity syntax, the EVM execution model, the deployment flow — stays exactly the same.


hashtag
What you can build

  • Shielded tokens — ERC20s where balances and transfer amounts are hidden from observers

  • Confidential DeFi — AMMs and lending protocols where positions, prices, and liquidation thresholds are shielded

  • Compliant finance — Privacy with built-in access control so regulators can verify without exposing user data


hashtag
3-minute quickstart

Already have Rust and the ?

You just ran shielded contract tests locally. See the for next steps.


hashtag
Find what you need

I want to...
Go to

hashtag
Pre-requisite knowledge

Our documentation assumes some familiarity with blockchain app development. Before getting started, it'll help if you're comfortable with:

If you're new to blockchain app development or need a refresher, we recommend starting out with the tutorial.


hashtag
Work with us

If you might benefit from direct support from the team, please don't hesitate to reach out to [email protected]. We pride ourselves in fast response time.

You can also check out our for the latest updates, or join our community.

Why Seismic

As shown on the , shielding a Solidity contract can be as simple as adding an s prefix to your types. But why does that matter?

hashtag
The transparency problem

Blockchains are public ledgers. Every balance, every transaction, every contract interaction is visible to anyone with a block explorer. This transparency was a design choice -- but it creates real problems:

Verify devtool installation

Before continuing, ensure that you have completed the steps in the section to install all necessary Seismic developer tools:

• sforge: Framework for testing and deploying smart contracts.

• sanvil : Local Seismic node.

• ssolc: The Seismic Solidity compiler.

Run each of the above commands in your terminal with a --version flag to ensure that they're installed correctly.

// 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;
}

Front-running and MEV extraction. Bots watch the mempool, see your trade, and sandwich it for profit. On Ethereum, MEV extraction costs users billions annually.

  • Competitive intelligence leaks. If your protocol holds assets on-chain, competitors can see your treasury, your positions, and your strategy in real time.

  • Privacy violations for end users. When Alice sends tokens to Bob, everyone can see how much she holds, how much she sent, and build a complete transaction graph linking her activity.

  • Business logic exposure. Contract state is fully readable. Pricing algorithms, liquidation thresholds, and internal parameters are all public.

  • Traditional finance operates with confidentiality by default. Public blockchains flip that assumption, and it limits what you can build.

    hashtag
    Why existing privacy solutions fall short

    Several projects have tried to solve this. None of them let you stay in Solidity.

    Approach
    Limitation

    ZK chains (Aztec, Aleo, Mina)

    Require new languages (Noir, Leo, o1js). You leave Solidity, Foundry, and the entire EVM ecosystem behind.

    Mixers (Tornado Cash)

    Only work for transfers. You can obscure the sender, but contract state is still public. No programmable privacy.

    L2 privacy layers

    Sacrifice composability. Contracts on the privacy layer cannot natively interact with contracts on the base chain.

    Homomorphic encryption (fhEVM)

    Computationally expensive. Operations on encrypted data are orders of magnitude slower, limiting what you can practically build.

    Each of these approaches forces a tradeoff: learn a new language, accept limited functionality, or live with performance constraints.

    hashtag
    The Seismic approach

    Seismic solves on-chain privacy with two innovations working together:

    hashtag
    Shielded types in the compiler

    Seismic extends the Solidity compiler with shielded types: suint, sint, sbool, sbytes, and saddress. The s prefix marks a value as shielded. Under the hood, these compile to CLOAD and CSTORE opcodes (instead of the standard SLOAD/SSTORE), which route data through shielded storage.

    No new language. No new programming model. Just a one-letter prefix on the types you already know.

    hashtag
    TEE-based execution

    Seismic nodes run inside Trusted Execution Environments (TEEs) using Intel TDX. The TEE creates a hardware-enforced boundary: even the node operator cannot read the data being processed. Transactions are encrypted before they hit the network and decrypted only inside the TEE for execution.

    Together, these two layers provide privacy at the language level and enforcement at the hardware level.

    hashtag
    What does not change

    Seismic is designed so that everything around the privacy layer stays familiar:

    • Same language. Standard Solidity, with shielded types added. No new syntax beyond the s prefix. Existing Solidity contracts compile and deploy without modification.

    • Same tooling. sforge, sanvil, and ssolc are forks of Foundry's forge, anvil, and solc. Your workflow does not change.

    • Same client libraries. seismic-viem extends Viem. seismic-react extends Wagmi. seismic-alloy extends Alloy for Rust. seismic_web3 extends web3.py for Python.

    • Same standards. ERC20 becomes SRC20. The interface is the same. The deployment flow is the same. The difference is that balances are private.

    • Same deployment flow. Write, test, deploy. The commands are sforge build, sforge test, sforge create. If you have deployed to Ethereum, you can deploy to Seismic.

    welcome page

    This tutorial uses bunarrow-up-right as the JavaScript runtime. Other runtimes like Node.js should also work, but the commands below assume bun. If you don't have it installed, follow the instructions herearrow-up-right.

    Installation

    Private voting — On-chain governance where votes are secret until tallied

    Learn about shielded types

    Integrate a frontend

    Deploy to testnet

    Understand the transaction lifecycle

    Run a node

    Understand why Seismic exists

    Why Seismic

    See how it works under the hood

    How Seismic Works

    Set up my dev environment

    Installation

    Run my first shielded contract

    Quickstart

    Build a complete app step by step

    Walnut App Tutorial

    Build a shielded ERC20 token

    SRC20 Tutorial

    Seismic tools installed
    full quickstart
    Solidityarrow-up-right
    Foundryarrow-up-right
    Viemarrow-up-right
    CryptoZombiesarrow-up-right
    X accountarrow-up-right
    Discordarrow-up-right

    Development Toolkit

    Use sfoundry to write and test smart contract code locally before deployment


    hashtag
    Mappings to foundry

    Seismic's development toolkit closely mirrors Foundryarrow-up-right (it's a forkarrow-up-right!). The mapping is as follows:

    You should use the righthand version of all tools when developing for Seismic to get expected behavior. Our documentation assumes familiarity with foundry.


    hashtag
    Quick actions

    Substitute sforge for forge to execute against Seismic's superset of the EVM. More on this in the next section.


    hashtag
    Local node

    Use sanvil to run a local Seismic node for development and testing:

    This starts a local node at http://localhost:8545 with pre-funded accounts, similar to Foundry's anvil.

    Writing the Contract

    This section dives into the heart of the Walnut App—the shielded smart contract that powers its functionality. You’ll start by building the foundational pieces of the Walnut, including the kernel and the protective shell, before implementing more advanced features like rounds, reset mechanisms, and contributor-based access control. By the end of this section, you’ll have a fully functional, round-based Walnut contract that is secure, fair, and replayable.

    hashtag
    What You'll Learn

    In this section, you’ll:

    • Define and initialize the kernel, the hidden value inside the Walnut.

    • Build the shell, the protective layer that hides the kernel, and implement a hit() function to help crack it, and a shake function to increment the kernel value by an encrypted amount.

    • Add a look() function to conditionally reveal the kernel.

    • Implement a reset mechanism to restart the Walnut for multiple rounds.

    • Track player contributions in each round, ensuring that only contributors can access the kernel.

    hashtag
    Overview of Chapters

    You’ll define the kernel using a shielded state variable (suint256) and implement a shake() function to increment its value. This chapter introduces shielded writes.

    Learn how to build the shell, which protects the kernel from being accessed prematurely. You’ll implement the hit()function to crack the shell and the look() function to reveal the kernel once conditions are met.

    This chapter introduces a reset mechanism to enable multiple rounds of gameplay. You’ll track contributions per round and ensure that only players who helped crack the Walnut in a specific round can reveal the kernel. This chapter introduces shielded reads.

    Ch 1: Making the Kernel

    In this chapter, you’ll learn to create and initialize the kernel, a hidden value inside the Walnut, and increment it by implementing a shake function. Estimated time: ~10 minutes.

    hashtag
    Defining the kernel

    The kernel is the hidden number inside the Walnut. Using Seismic’s suint256 type, the kernel is shielded on-chain. Open up packages/contracts/Walnut.sol and define the kernel as a state variable and initialize it in the constructor:

    Add a shake function

    Next, let’s implement a function to increment the kernel. The shake function takes an suint256 parameter, _numShakes which specifies the amount to increment the kernel by.

    What's happening here?

    Since shake takes a shielded type as a parameter, you should use a Seismic transaction to keep the input private. The value of _numShakes is known only to the function caller and is shielded on-chain.

    The function also updates a shielded state variable (kernel), so you should use a Seismic transaction (a shielded write) when calling it.

    Shielded Literals

    The s suffix lets you write shielded integer constants directly, without explicit casting. The compiler infers the concrete shielded type (suint8, suint256, sint128, etc.) from context.

    This is equivalent to:

    Both forms are valid. The s suffix is syntactic sugar — it produces the same bytecode.

    hashtag
    Supported formats

    The s suffix works with all numeric literal forms:

    hashtag
    Type inference

    The shielded type is inferred from the assignment target or surrounding expression:

    hashtag
    Rules

    No mixed arithmetic. You cannot mix shielded and non-shielded literals in the same expression:

    No implicit conversion to non-shielded types. A shielded literal cannot be assigned to a regular type:

    No immutable or constant. Shielded literals follow the same restriction as all shielded types — they cannot be used in immutable or constant declarations. See .

    hashtag
    Compiler warning

    All shielded literals emit warning 9660. This is intentional — literal values are embedded in the contract bytecode, which is publicly visible. The warning reminds you that the value is leaked at deployment time.

    If the literal value is sensitive, do not hardcode it. Introduce it via encrypted calldata instead. See for more detail.

    TypeScript

    TypeScript client libraries for Seismic

    Seismic provides two TypeScript packages:

    Package
    Purpose
    Use when

    Low-level SDK built on viem 2.x

    Server-side, scripts, non-React apps

    React hooks layer built on seismic-viem + wagmi

    React applications

    seismic-react depends on seismic-viem — install both if you're building a React app, or just seismic-viem for everything else.

    Walnut App

    Imagine you’re holding a walnut, an unassuming object. Inside it lies a number, a secret only revealed when you crack the shell. There are primarily two actions you can take on this walnut: either shaking it or hitting it. Shaking the walnut some n number of times increments the number inside by n, while hitting the walnut brings it one step closer to the shell cracking. You can only see the number inside if you have contributed to the cracking the shell, i.e., you have hit the walnut at least once. This collaborative challenge is the heart of the Walnut App, and it’s all powered by the Walnut smart contract. Let’s dive into the inner workings of the contract and uncover how each part fuels this game.

    The contract can be found in the packages/contracts/Walnut.sol file of the .

    Initialize the CLI

    Now that the contracts subdirectory has been initialized, you should now initialize the cli subdirectory that will be used to interact with the deployed contracts.

    1. Navigate to the contracts subdirectory:

    1. Initialize a new bun project

    Initialize contracts

    1. Navigate to the contracts subdirectory:

    1. Initialize a project with sforge :

    This command will:

    Ch 1: Constants and Utilities

    In this chapter, you will learn about defining and constants and utility functions which we will frequently use throughout the project. Estimated time: ~10 minutes

    First, navigate to the root of the directory/monorepo and run bun install to install all the dependencies.

    Now, create a lib folder inside packages/cli with the files constants.ts and utils.ts:

    constants.ts

    Collections

    Using stype variables in arrays and maps

    hashtag
    Shielded Arrays

    Arrays of shielded types work like standard Solidity arrays. Keys (indices) must be non-shielded — using a shielded type as an array index is a compiler error.

    They come in two forms:

    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 -> scast
    suint256 x = 42s;            // inferred as suint256
    suint8 small = 7s;           // inferred as suint8
    sint256 neg = -1s;           // inferred as sint256
    suint256 x = suint256(42);
    suint8 small = suint8(7);
    sint256 neg = sint256(-1);
    Chapter 1: Making the Kernel
    Chapter 2: Making the Shell and Revealing the Kernel
    Chapter 3: Reset Mechanism, Rounds, and a more conditional Kernel Reveal
    Shielded Types
    Client Libraries
    Migrating from Ethereum
    Seismic Transaction
    Node Operator FAQ
    seismic-viem
    seismic-react
    hashtag
    State variables

    hashtag
    startShell and shell

    Think of the shell as the Walnut’s durability. startShell is the Walnut’s starting strength, and shell tracks how much of it remains as players hit it.

    hashtag
    startNumber and number

    These are the secret numbers at the heart of the Walnut. startNumber initializes the hidden number, while number evolves as players shake the Walnut. Being suint256 (shielded integers), these numbers remain shielded on-chain—visible only to authorized participants.

    hashtag
    round

    A counter that increments with each new round/reset, ensuring every round has a fresh Walnut to crack.

    hashtag
    hitsPerRound

    A mapping that records every player’s contribution to the current round, ensuring only participants can peek at the Walnut’s secret.

    hashtag
    Functions

    hashtag
    hit ( )

    This function allows a player to hit the Walnut, reducing its durability and bringing it one step closer to cracking:

    What happens:

    • Checks if the shell is intact (shell>0 )

    • If it is, decrements shell by 1

    • Increases the player who called hit() 's contribution in the current round (hitsPerRound[round][playerAddress]) by 1

    • Emits the Hit event to update all participants.

    hashtag
    shake (suint256 _numShakes)

    This function allows a player to shake the walnut _numShakes number of times. Since it takes a shielded type as a parameter, you should use a Seismic transaction to keep the input private.

    What happens:

    • Adds _numShakes to number

    • Emits the Shake event.

    hashtag
    look ( )

    This function allows contributors to the current round to view the number inside the walnut. Since it returns a shielded type, you should use a signed read so the contract can identify the caller.

    What happens:

    • Requires the shell to be cracked (requireCracked modifier)

    • Ensures the function caller contributed to the cracking the walnut for this round (onlyContributor modifier)

    • Returns ("reveals") the number inside the walnut.

    hashtag
    Modifiers

    Modifiers enforce the rules of the game:

    hashtag
    requireCracked

    Ensures that look() can only be called if the Walnut’s shell is completely cracked.

    hashtag
    requireIntact

    Ensures that shake() and hit() can only be called if the Walnut’s shell is intact.

    hashtag
    onlyContributor

    Restricts access to look() , and hence the number being revealed, only to players who contributed at least one hit in the current round.

    starter repoarrow-up-right
    # 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);
        }
    }
    Footguns
    Footguns
  • Now, create an src/ folder and move index.ts there.

    1. Now, edit package.json to be the following:

    1. Edit .gitignore to be:

    Your environment is now set!

    # Assuming you are currently in the contracts directory
    cd ../packages/cli
    bun init -y

    Create the contract project structure (e.g., src/ , test/, foundry.toml).

  • Automatically install the Forge standard library (forge-std ) as a submodule.

  • Remove the .github workflow folder (not required)

    1. Edit the .gitignore file to be the following:

    1. Delete the default contract, test and script files (Counter.sol and Counter.t.sol and Counter.s.sol ) and replace them with their Walnut counterparts (Walnut.sol , Walnut.t.sol and Walnut.s.sol ):

    These files are empty for now, but we will add to them as we go along.

    cd packages/contracts
    will contain the constants we use throughout the project with
    utils.ts
    will contain the necessary utility functions.

    Add the following to constants.ts :

    This file centralizes key project constants:

    • CONTRACT_NAME: The Walnut contract name.

    • CONTRACT_DIR: Path to the contracts directory.

    Now, add the following to utils.ts :

    This file contains utility functions to interact with your Walnut contract:

    • getShieldedContractWithCheck: Ensures the contract is deployed and returns a shielded contract instance.

    • readContractAddress: Reads the deployed contract’s address from a broadcast file.

    • readContractABI: Parses the contract’s ABI from an ABI file for use in interactions.

    mkdir -p packages/cli/lib
    touch packages/cli/lib/constants.ts packages/cli/lib/utils.ts
    cd packages/cli/lib
    Dynamic (suint256[], sbool[], saddress[], etc.) — the length is stored as shielded.
  • Fixed-size (suint256[5], sbool[4], saddress[3], etc.) — the length is a compile-time constant and publicly visible.

  • circle-exclamation

    Even with dynamic shielded arrays, an upper bound on the length may be visible to observers monitoring gas costs, since gas usage scales with array operations.

    hashtag
    Shielded Mappings

    Mappings can have shielded values but keys cannot be shielded types. Using a shielded type as a mapping key is a compiler error. The standard mapping syntax applies.

    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
    }
    sanvil
    function 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 minus
    suint8  a = 255s;            // suint8
    suint32 b = 1_000s;          // suint32
    sint128 c = -1s;             // sint128
    
    suint256 x = 10s;
    suint256 y = x + 5s;         // 5s inferred as suint256 from context
    suint256 x = 1s + 1;         // Error — mixed shielded/non-shielded
    suint256 x = 1s + 1s;        // OK
    uint256 x = 42s;             // Error — cannot assign shielded to uint256
    mkdir -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_modules
    sforge 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.sol
    import { 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 key

    Installation

    Setting up your local machine to develop with Seismic


    hashtag
    System requirements

    Before you begin, make sure your machine meets the following requirements:

    • x86_64 or arm64 architecture

    • MacOS, Ubuntu, or Windows (other Linux distros may work but are not officially tested)


    hashtag
    Install the local development suite

    The local development suite uses sforge as the testing framework, sanvil as the local node, and ssolc as the compiler.

    1. Install and on your machine if you don't already have them. Default installation works well.

    1. Download and execute the sfoundryup installation script.

    1. Install sforge, sanvil, ssolc. Expect this to take between 5-20 minutes depending on your machine.

    1. (Optional) Remove old build artifacts in existing projects. You can ignore this step if you aren't working with existing foundry projects.


    hashtag
    Set up the VSCode extension

    We recommend adding syntax highlighting via the (or for Open VSX) extension from the VSCode marketplace. If you already have the solidity extension, you'll have to disable it while writing Seismic code.

    Create project structure

    1. Create the project folder and navigate into it:

    1. Create the packages directory with subdirectories for contracts and cli

    The contracts subdirectory will house the Seismic smart contract(s) and test(s) for the project, while the cli will house the interface to interact with the contracts.

    1. Initialize a bun project in the root directory:

    We remove the default index.ts and tsconfig.json files created by bun init -y to keep the root directory clean and focused on managing the monorepo structure rather than containing code. We also create a .prettierrc file for consistent code formatting and a .gitmodules file to manage contract submodules.

    1. Replace the default package.json with the following content for a monorepo setup:

    1. Add the following to the .prettierrc file for consistent code formatting:

    1. Replace the.gitignore file with:

    1. Add the following to the .gitmodules file to track git submodules (in our case, only the Forge standard library, forge-std):

    Deploying

    In this chapter, you’ll deploy your Walnut contract to a local Seismic node for testing. By the end of this guide, you’ll have a fully deployed contract that you can interact with using your CLI or scripts. Estimated Time: ~15 minutes.

    hashtag
    Writing the deploy script

    Open packages/contracts/script/Walnut.s.sol and add the following:

    and add the following to it:

    This script will deploy a new instance of the Walnut contract with an initial shell strength of 3 and an initial kernel value of 0.

    hashtag
    Deploying the contract

    1. In a separate terminal window, run

    in order to spin up a local Seismic node.

    1. In packages/contracts , create a .env file and add the following to it:

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

    1. Now, from packages/contracts, run

    Your contract should be up and deployed to your local Seismic node!

    Seismic-viem Primer

    Before proceeding to write the CLI, you need to be acquainted with some of the functions and utilities used to enable Seismic primitives (e.g. shielded reads, shielded writes etc.) through our client library, seismic-viem , which we will be using heavily to write the CLI. The detailed docs for seismic-viem can be found here. Estimated time: ~15 minutes

    hashtag
    Shielded wallet client

    The shielded wallet client is the shielded/Seismic counterpart of the wallet client in viem . It is used to enable extended functionality for interacting with shielded blockchain features, wallet operations, and encryption. It can be initialized using the createShieldedWalletClient function as follows:

    createShieldedWalletClient takes in the following parameters:

    1. chain : a well-defined object

    2. transport : the method of transport of interacting with the chain (http /ws along with the corresponding RPC URL)

    Once initialized, it can then be used to perform wallet operations or shielded-specific .

    hashtag
    Shielded contract

    A shielded contract instance provides an interface to interact with a shielded contract onchain. It has extended functionality for performing shielded write operations, signed reads, and contract interaction for a specific contract performed by a specific wallet client that it is initialized with. It can be initialized with the getShieldedContract as follows:

    It takes in the following parameters:

    1. abi : the ABI of the contract. After running sforge build, the ABI is in out/<ContractName>.sol/<ContractName>.json. You can also generate it manually with ssolc --abi <file>.sol. To use it in your code, you can import the JSON file directly, or copy the ABI array into a constant in your source (more tedious, but gives you better type inference).

    2. address : the address of the deployed contract it is interacting with.

    This function extends the base getContract functionality by adding:

    • Shielded write actions for nonpayable and payable functions.

    • Signed read actions for pure and view functions.

    We will extensively use shielded writes (for shake ) and shielded reads (for look()) in our CLI.

    Overview

    Seismic maintains client libraries for three languages.

    hashtag
    TypeScript

    hashtag
    seismic-viem

    composes with to add Seismic transaction support, encrypted calldata, and signed reads. See the .

    hashtag
    seismic-react

    composes with to provide React hooks for shielded reads, writes, and wallet management. See the .

    hashtag
    Python — seismic-web3

    composes with to interact with Seismic nodes from Python. See the .

    hashtag
    Rust — seismic-alloy

    composes with to provide Seismic transaction types and encryption-aware providers. See the .

    circle-exclamation

    The docs for all three of these libraries are pure AI slop. The Python docs have been manually reviewed and cleaned up; we're auditing the TypeScript and Rust docs shortly. Refer to the source code if anything seems off.

    Examples

    Complete runnable examples for Seismic React

    Complete working examples demonstrating common Seismic React patterns. Each example is self-contained and can be copied into a new project.

    hashtag
    Available Examples

    Example
    Description

    hashtag
    Common Setup

    Every example uses the same provider wrapper that combines RainbowKit, wagmi, and Seismic:

    circle-info

    ShieldedWalletProvider must be nested inside WagmiProvider and QueryClientProvider. It automatically creates shielded clients when a wallet connects.

    hashtag
    Prerequisites

    • Node.js 18+

    • A project ID

    • seismic-react and peer dependencies installed

    hashtag
    See Also

    • - Hook API reference

    • - RainbowKit, AppKit, and Privy integration

    • - Full dependency setup

    Shielded Types

    A handle on stype unlocks all shielded computation and storage

    Operations on shielded types return shielded types. For example, comparing two suint256 values produces an sbool, not a bool. Arithmetic on sint256 returns sint256, and so on.

    hashtag

    Seismic Testnet

    Seismic public testnet chain configuration

    The Seismic public testnet is the primary network for development and testing. seismicTestnet is a RainbowKit-compatible chain object that wraps the seismic-viem testnet definition with RainbowKit metadata.

    hashtag
    Configuration

    Property
    Value
    mkdir walnut-app
    cd walnut-app
    mkdir -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();
        }
    }
    seismic-viemarrow-up-right
    viemarrow-up-right
    full documentation
    seismic-reactarrow-up-right
    wagmiarrow-up-right
    full documentation
    seismic-web3arrow-up-right
    web3.pyarrow-up-right
    full documentation
    seismic-alloyarrow-up-right
    alloyarrow-up-right
    full documentation
    rustarrow-up-right
    cargoarrow-up-right
    seismicarrow-up-right
    seismicarrow-up-right
    sanvil
    account: a viem Account object (e.g. from privateKeyToAccount())

    client : the shielded wallet client that the interactions are to be performed by.

    Proxy-based access to dynamically invoke contract methods.
    Chainarrow-up-right
    actions

    Complete minimal dApp: provider setup, connect wallet, shielded write, signed read

    WalletConnectarrow-up-right
    Hooks
    Wallet Guides
    Installation
    Basic dApp
    Shielded Integers

    All comparisons and operators for shielded integers are functionally identical to their unshielded counterparts.

    hashtag
    suint - Shielded Unsigned Integer

    hashtag
    sint - Shielded Signed Integer

    hashtag
    sbool - Shielded Boolean

    All comparisons and operators for sbool function identically to bool.

    We recommend reading the point on conditional execution prior to using sbool since it's easy to accidentally leak information with this type.

    hashtag
    saddress - Shielded Address

    An saddress variable supports code and codehash members only. Members like call, delegatecall, staticcall, balance, and transfer are not available — you must cast to address first.

    hashtag
    sbytes - Shielded Bytes

    hashtag
    Fixed-size: sbytes1 through sbytes32

    Fixed-size shielded bytes mirror the standard bytes1–bytes32 types.

    hashtag
    Dynamic: sbytes

    Dynamic shielded bytes mirror the standard bytes type. The length is stored as shielded — like dynamic shielded arrays, observers cannot read it directly but may infer an upper bound from gas costs.

    hashtag
    Shielded Literals

    You can create shielded integer constants with either an explicit cast or the s suffix:

    The s suffix infers the shielded type from context and works with hex, underscores, scientific notation, and unary minus. See Shielded Literals for the full specification.

    circle-exclamation

    Both forms embed the literal value in the contract bytecode, which is publicly visible — the initial value is leaked at deployment time. The compiler emits warning 9660 to remind you.

    This is fine for values meant to be public initially and then evolve through private state changes. But if the literal itself is sensitive, do not hardcode it. See Footguns for more detail.

    curl https://sh.rustup.rs -sSf | sh
    curl -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 shell
    sfoundryup
    source ~/.zshenv  # or ~/.bashrc, depending on your shell
    sforge clean  # run in your project's contract directory
    bun init -y && rm index.ts && rm tsconfig.json && touch .prettierrc && touch .gitmodules
    {
      "workspaces": [
        "packages/**"
      ],
      "dependencies": {},
      "devDependencies": {
        "@trivago/prettier-plugin-sort-imports": "^5.2.1",
        "prettier": "^3.4.2"
      }
    }
    {
      "semi": false,
      "tabWidth": 2,
      "singleQuote": true,
      "printWidth": 80,
      "trailingComma": "es5",
      "plugins": ["@trivago/prettier-plugin-sort-imports"],
      "importOrder": [
        "<TYPES>^(?!@)([^.].*$)</TYPES>",
        "<TYPES>^@(.*)$</TYPES>",
        "<TYPES>^[./]</TYPES>",
        "^(?!@)([^.].*$)",
        "^@(.*)$",
        "^[./]"
      ],
      "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
      "importOrderSeparation": true,
      "importOrderSortSpecifiers": true
    }
    # Compiler files
    cache/
    out/
    
    # Ignores development broadcast logs
    !/broadcast
    /broadcast/*/31337/
    /broadcast/**/dry-run/
    
    # Docs
    docs/
    
    # Dotenv file
    .env
    
    node_modules/
    [submodule "packages/contracts/lib/forge-std"]
    	path = packages/contracts/lib/forge-std
    	url = https://github.com/foundry-rs/forge-std
    RPC_URL=http://127.0.0.1:8545
    PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    source .env
    sforge script script/Walnut.s.sol:WalnutScript \
        --rpc-url $RPC_URL \
        --broadcast
    import { 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-query
    suint256 a = suint256(10);
    suint256 b = suint256(3);
    
    // == EXAMPLES
    a > b   // sbool(true)
    a | b   // suint256(11)
    a << 2  // suint256(40)
    a % b   // suint256(1)
    sint256 a = sint256(-10);
    sint256 b = sint256(3);
    
    // == EXAMPLES
    a < b   // sbool(true)
    a + b   // sint256(-7)
    a * b   // sint256(-30)
    sbool a = sbool(true);
    sbool b = sbool(false);
    
    // == EXAMPLES
    a && b  // sbool(false)
    !b      // sbool(true)
    saddress a = saddress(0x123);
    saddress b = saddress(0x456);
    
    // == VALID EXAMPLES
    a == b  // sbool(false)
    b.code
    b.codehash
    
    // == INVALID EXAMPLES
    a.balance   // must cast to address first
    a.call("")  // must cast to address first
    sbytes32 a = sbytes32(0xabcd);
    sbytes1 b = sbytes1(0xff);
    suint256 a = suint256(42);   // explicit cast
    suint256 b = 42s;            // s suffix — same result

    Chain ID

    5124

    Name

    Seismic

    RPC (HTTPS)

    https://gcp-1.seismictest.net/rpc

    RPC (WSS)

    wss://gcp-1.seismictest.net/ws

    Explorer

    https://seismic-testnet.socialscan.io

    Native Currency

    ETH (18 decimals)

    hashtag
    Import

    hashtag
    Usage

    hashtag
    With RainbowKit

    hashtag
    With wagmi Config

    hashtag
    Notes

    • Chain ID 5124 is used for EIP-155 replay protection and EIP-712 typed data signing

    • The testnet supports all Seismic protocol features including shielded transactions and signed reads

    • The Seismic icon is included automatically for display in RainbowKit's chain selector

    hashtag
    See Also

    • Chains Overview - All supported chains

    • Sanvil - Local development chains

    • createSeismicDevnet - Custom chain factory

    • - RainbowKit setup guides

    Use Cases

    Seismic's shielded types let you add privacy to any smart contract pattern. Below are five categories with code snippets showing how each looks in Seismic Solidity.

    hashtag
    Private tokens (SRC20)

    An ERC20 with shielded balances and transfer amounts. Users can check their own balance through signed reads, but no one else can see it.

    The only difference from a standard ERC20: uint256 becomes suint256. The compiler routes all balance reads and writes through shielded storage automatically. Observers see 0x00...0 for all balances and amounts.

    For a full walkthrough, see the .

    hashtag
    Confidential DeFi

    An AMM where liquidity positions, reserves, and swap amounts are hidden from observers. This prevents front-running and MEV extraction because bots cannot see the pool state or pending swaps.

    The constant-product formula is standard AMM logic. Shielding the reserves and amounts means no one can compute the price impact of a pending swap or sandwich a user's trade.

    hashtag
    Compliant finance (Intelligence Contracts)

    Contracts can optionally implement access control over shielded data — for example, exposing a view function that only authorized addresses can call. This pattern is sometimes called an "Intelligence Contract": a shielded contract that selectively reveals information to specific parties.

    In this example, balances are stored as suint256 so they are shielded by default. The getBalance function casts the shielded value to a regular uint256 for return, but only if the caller is the account owner or holds the COMPLIANCE_ROLE. Everyone else is rejected.

    hashtag
    Private voting

    Secret ballot governance on-chain. Votes are hidden during the voting period. The tally can be revealed when voting ends, but individual votes remain private.

    During voting, both the individual votes (hasVoted) and the running tallies (yesVotes, noVotes) are shielded. No one can see which way the vote is trending. After the deadline, getResults() casts the shielded tallies to public uint256 values so the outcome can be read.

    hashtag
    Sealed-bid auctions

    Bids are hidden until the auction closes. No bidder can see what others have bid, eliminating bid sniping and strategic underbidding.

    Each bid is stored as a suint256, and the highest bidder's address is stored as an saddress. During the auction, the contract tracks the leading bid internally, but no one outside the TEE can see any bid amounts or the current leader. After the auction ends, getWinner() reveals the result by casting shielded values to their public counterparts.

    Signed Reads

    Let users check their own balance without exposing it to others

    The SRC20 contract stores balances as suint256, which means there is no public getter. This chapter shows how to let users query their own balance securely using signed reads. Estimated time: ~15 minutes.

    hashtag
    The problem

    In a standard ERC20, balanceOf is a public mapping. Anyone can call balanceOf(address) and see any account's balance. In the SRC20, two things prevent this:

    1. Shielded types cannot be public. The suint256 value type means the automatic getter would try to return a shielded type from an external function, which the compiler rejects.

    2. Vanilla eth_call has no sender identity. On Seismic, the from field is zeroed out for unsigned eth_call

    So you need two things: a way for the contract to verify who is asking, and a way to return the value only to that person.

    hashtag
    Signed reads

    A signed read solves both problems. It is a Seismic transaction (type 0x4A) sent to the eth_call RPC endpoint instead of eth_sendRawTransaction. Because it is a valid signed transaction:

    • The from field is cryptographically verified, so the contract can trust msg.sender.

    • The response is encrypted to the sender's encryption public key (included in the Seismic transaction's elements). Even if someone intercepts the response, they cannot decrypt it.

    • The signed_read

    From the contract's perspective, a signed read looks like a normal view function call. The difference is entirely at the transport layer.

    hashtag
    Contract implementation

    Add a balanceOf function that requires the caller to be the account owner:

    circle-info

    Note the naming here. The mapping balanceOf is internal (no public modifier), so there is no collision with the function name. The function acts as the explicit getter that replaces the auto-generated one.

    This function does three things:

    1. Checks msg.sender -- Only the account owner can query their own balance. With a vanilla eth_call, msg.sender would be address(0) and this check would always fail. With a signed read, msg.sender is the actual caller.

    2. Casts to

    hashtag
    Allowance checking

    The same pattern applies to allowances:

    Here, either the owner or the spender can check the allowance. Both parties have a legitimate reason to know the value.

    hashtag
    Client-side code

    On the client side, seismic-viem handles signed reads automatically under the hood. When you use a ShieldedWalletClient or a ShieldedContract, read calls are sent as signed reads. If you need an unsigned read (a vanilla eth_call), use contract.tread instead.

    hashtag
    Using a shielded contract instance

    The token.read.balanceOf() call is sent as a signed read under the hood. The wallet client signs the request, the node verifies the signature, executes the view function with the correct msg.sender, and encrypts the response to the caller's key.

    hashtag
    Using the wallet client directly

    You can also call readContract on the wallet client:

    Both approaches produce the same signed read.

    hashtag
    Security model

    The signed read has several layers of protection:

    Property
    How it works

    The result is that only the account owner can view their balance, and the balance is never exposed in plaintext outside the TEE.

    hashtag
    A note on privacy tradeoffs

    With this implementation, each user can only see their own balance. They cannot see anyone else's balance. This is the strictest privacy model. In the next chapter, , we will add authorized roles (such as compliance officers) who can view specific balances when required -- without compromising privacy for everyone else.

    Ch 2: Making the Shell

    In this chapter, you’ll build the shell, the protective layer that hides the kernel. You’ll initialize the shell’s strength and implement a hit function to decrement it. Additionally, you’ll add a look() function with a requiredCracked modifier to ensure the kernel can only be viewed once the shell is fully broken. Estimated Time: ~10 minutes.

    hashtag
    Defining the shell

    The shell determines the Walnut’s resilience. It has an integer strength (shellStrength), which represents how many hits it can withstand before cracking. Let’s define the shell and initialize it in the constructor:

    hashtag
    Adding the hit function

    Each time the Walnut is hit, the shell strength decreases, simulating damage to the protective shell. This is crucial for revealing the kernel, as the shell must be fully broken for the kernel to be accessed:

    hashtag
    What’s happening here?

    • The requireIntact modifier: Ensures that the function cannot be called if the Walnut’s shell is already broken (shellStrength == 0). This prevents underflow and unnecessary calls after the shell is fully cracked. We also add this modifier to the shake function:

    • Decrementing the shell: Each call to hit decreases the shell’s strength (shellStrength) by one.

    • Logging the action: The Hit event records the hitter’s address (msg.sender) and the remaining shell strength.

    hashtag
    Example call:

    Here’s how calling the hit function works in practice:

    • Initial State: The shell strength is set to 5.

    • First Hit: A player calls hit(). The shell strength decreases to 4.

    • Subsequent Hits: Each additional hit reduces the shell strength by 1 until it reaches 0

    hashtag
    Revealing the Kernel

    Now that we have implemented the shell’s durability and the ability to break it using the hit function, we can introduce a new condition: the kernel should only be revealed once the shell is fully cracked.

    Currently, there is no way to access the kernel’s value. However, now that we have a shell with a decreasing strength, we can apply a condition that restricts when the kernel can be seen. Specifically:

    • The kernel should remain hidden while the shell is intact.

    • The kernel can only be revealed once the shell’s strength reaches zero, i.e. when it is cracked.

    To enforce this, we will create a function called look() , which will return the kernel’s value, but only if the Walnut has been fully cracked.

    Here’s how we define look() with a requireCracked modifier:

    What's happening here?

    • Restricting Access with a Condition: The requireCracked modifier ensures that look() can only be called if shellStrength == 0, meaning the Walnut has been fully cracked.

    • Revealing the Kernel: Once the condition is met, look() returns the unshielded value of the kernel.

    hashtag
    Updated contract with hit, shake and look

    Ch 2: Core App Logic

    In this chapter, you’ll write the core logic to interact with the Walnut contract by creating an App class. This class will initialize player-specific wallet clients and contracts, and provide easy-to-use functions like hit, shake, reset, and look. Estimated time: ~20 minutes

    Create a file called app.ts in packages/cli/src/ which will contain the core logic for the CLI:

    hashtag
    Import required dependencies

    Start by importing all the necessary modules and functions at the top of app.ts:

    hashtag
    Define the app configuration

    The AppConfig interface organizes all settings for the Walnut App, including player info, wallet setup, and contract details. It supports a multiplayer environment, with multiple players having distinct private keys and contract interactions.

    hashtag
    Create the App class

    The App class manages player-specific wallet clients and contract instances, providing an easy-to-use interface for multiplayer gameplay.

    hashtag
    Add initialization logic to App

    The init() method sets up individual wallet clients and contract instances for each player, enabling multiplayer interactions. Each player gets their own wallet client and a direct connection to the contract.

    hashtag
    Add helper methods to App

    These helper methods ensure that the app fetches the correct wallet client or contract instance for a specific player, supporting multiplayer scenarios.

    getWalletClient :

    getPlayerContract :

    hashtag
    Implement Contract Interaction Methods

    reset

    Resets the Walnut for the next round. The reset is player-specific and resets the shell and kernel values.

    shake

    Allows a player to shake the Walnut, incrementing the kernel. This supports multiplayer scenarios where each player’s shakes impact the Walnut. Uses signed writes.

    hit :

    A player can hit the Walnut to reduce the shell’s strength. Each hit is logged for the respective player.

    look :

    Reveals the kernel for a specific player if they contributed to cracking the shell. This ensures fairness in multiplayer gameplay. Uses signed reads.

    useShieldedWallet

    Access shielded public and wallet clients from context

    Hook that consumes the ShieldedWalletProvider context. Returns the shielded public client, wallet client, connected address, error state, and a loaded flag indicating whether initialization is complete.

    hashtag
    Return Type

    Property
    Type
    Description

    hashtag
    Usage

    hashtag
    Basic

    hashtag
    Handling loading state

    The loaded flag is false until the shielded clients have been fully initialized. Use it to avoid rendering components that depend on the wallet client before it is ready.

    hashtag
    Error handling

    If initialization fails (for example, the node is unreachable or the wallet connector is incompatible), the error field contains the error message.

    hashtag
    Accessing clients for direct seismic-viem calls

    The returned publicClient and walletClient are full seismic-viem client instances. You can use them directly for operations not covered by the higher-level hooks.

    circle-exclamation

    This hook must be used within a ShieldedWalletProvider. It will throw if called outside the provider tree.

    hashtag
    See Also

    • -- Context provider that supplies the shielded clients

    • -- Create a contract instance using the wallet client

    • -- Send encrypted write transactions

    Contract

    Instantiating contracts and interacting through shielded and transparent namespaces


    hashtag
    Instantiation

    The ABI works the same as in web3.py. If your contract uses shielded types (suint256, sbool, saddress), the SDK remaps them to their standard counterparts for parameter encoding while keeping the original shielded names for function selector computation.


    hashtag
    Namespaces

    ShieldedContract gives you five namespaces:

    Namespace
    What it does
    On-chain visibility

    Write namespaces accept optional keyword arguments for transaction parameters:


    hashtag
    Example Contract

    All code snippets in the Python SDK docs reference the interface below.

    Token examples use the real / ERC20 specs (balanceOf, transfer, approve, allowance, totalSupply, etc.).


    hashtag
    Encoding calldata manually

    If you need to encode calldata outside of a contract call — for example, to pass it to the — you can use . This computes the function selector using the original shielded type names (like suint256) but encodes the parameters using standard types (like uint256):

    Chains

    RainbowKit-compatible chain configurations for Seismic networks

    seismic-react provides pre-configured chain objects compatible with RainbowKit's getDefaultConfig. Each chain wraps a seismic-viem chain definition with RainbowKit metadata (icon, etc.).

    hashtag
    Import

    hashtag
    Available Chains

    Chain
    Export
    Chain ID
    Description

    hashtag
    Usage with RainbowKit

    hashtag
    Choosing a Chain

    hashtag
    Relationship to seismic-viem Chains

    Each chain export is a thin wrapper around the corresponding seismic-viem chain object. The wrapper adds RainbowKit-specific metadata (such as iconUrl) while preserving all underlying chain properties -- chain ID, RPC URLs, native currency, block explorers, and transaction formatters.

    circle-info

    If you are not using RainbowKit, you can use the seismic-viem chain objects directly with wagmi.

    hashtag
    Pages

    Page
    Description

    hashtag
    See Also

    • - Provider that accepts chain config

    • - RainbowKit and wallet setup guides

    • - Package setup

    SEISMIC_TX_TYPE

    Transaction type byte for Seismic transactions

    Protocol constant representing the transaction type byte for Seismic custom transactions.

    hashtag
    Overview

    SEISMIC_TX_TYPE is the EIP-2718 transaction type identifier used by Seismic's custom transaction format. This byte prefixes all serialized Seismic transactions.

    hashtag
    Value

    Representation
    Value

    hashtag
    Definition

    hashtag
    EIP-2718 Context

    EIP-2718 introduced typed transaction envelopes to Ethereum. Transaction types are identified by a single byte prefix:

    Type
    Description

    hashtag
    Transaction Format

    A serialized Seismic transaction has the format:

    Where 0x4A is the SEISMIC_TX_TYPE prefix.

    hashtag
    Protocol Notes

    • The SDK automatically sets this type for all shielded writes

    • Seismic nodes recognize and process transactions with this type

    • Standard Ethereum nodes will reject transactions with unknown types

    hashtag
    See Also

    • - Seismic transaction structure

    • - Transaction signing

    • - Chain configuration with chain IDs

    Ch 4: Testing

    In this chapter, you’ll write tests to verify that the Walnut contract behaves as expected under various scenarios. Testing ensures the functionality, fairness, and access control mechanisms of your contract work seamlessly, particularly in multi-round gameplay. Estimated Time: ~15 minutes.

    hashtag
    Getting Started

    Open packages/contracts/test/Walnut.t.sol. This is where you’ll write all the test cases for the Walnut contract. Start with the following base code:

    Installation

    Install seismic-viem and configure viem peer dependency

    hashtag
    Prerequisites

    Requirement
    Version
    Notes

    useShieldedContract

    Get a ShieldedContract instance for reads and writes

    Hook that creates a ShieldedContract instance from seismic-viem's getShieldedContract. Provides a contract object with type-safe methods for both shielded writes and signed reads.

    hashtag
    Config

    Parameter
    Type

    useShieldedRead

    Execute authenticated read calls on shielded contracts

    circle-info

    This hook wraps useSignedReadContract from seismic-react. The actual export name is useSignedReadContract.

    Hook for performing signed reads -- authenticated eth_call requests where the caller proves their identity. This allows contracts to return caller-specific shielded data (for example, a balance that depends on msg.sender).

    PublicContract

    Sync contract wrapper with transparent read-only access

    Synchronous contract wrapper for read-only transparent access to Seismic contracts.

    hashtag
    Overview

    PublicContract provides a simplified interface for reading public contract state without requiring encryption or a private key. It exposes only the .tread namespace for standard eth_call operations. Use this class when you only need to read public contract data and don't need shielded operations.

    Sanvil

    Local development chain configurations

    Chain configuration for connecting to a locally-running Seismic Anvil (Sanvil) instance. sanvil is a RainbowKit-compatible chain object pre-configured for the default Sanvil endpoint.

    hashtag
    Configuration

    Property
    Value
    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.ts
    import { useShieldedWallet } from "seismic-react";
    contract = w3.seismic.contract(address="0x...", abi=ABI)
    import {
      seismicTestnet,
      sanvil,
      localSeismicDevnet,
      createSeismicDevnet,
    } from "seismic-react/rainbowkit";
    Wallet Guides
    SRC20 tutorial
    requests. This means a contract cannot verify
    msg.sender
    in a view function called via a normal
    eth_call
    --
    msg.sender
    would just be
    address(0)
    . Without sender verification, anyone could impersonate any address and read their balance.
    flag in SeismicElements should be set to
    true
    , in order to prevent anyone from replaying this payload as a write transaction.
    uint256
    -- The shielded
    suint256
    value is cast to a regular
    uint256
    for the return value. Shielded types cannot be returned from external functions.
  • Returns the balance -- The return value is encrypted to the caller's key by the Seismic node before being sent back. Even though the function returns a uint256, the response is encrypted because the call was made with a signed read.

  • Sender authentication

    The transaction is signed by the user's private key. The node verifies the signature before executing.

    Response encryption

    The response is encrypted to the user's encryption public key, included in the SeismicElements of the transaction.

    Replay protection

    The signed_read flag is set to true. If someone intercepts the signed read payload and submits it to eth_sendRawTransaction, it is rejected.

    No state changes

    A signed read is sent to eth_call, which does not modify state. Even if the replay protection were bypassed, the function is view and cannot alter balances.

    Intelligence Contracts
    Preventing Premature Access: If look() is called before the shell is broken, the function will revert with the error "SHELL_INTACT".
    useSignedReadContract -- Perform signed, encrypted read calls
  • Hooks Overview -- Summary of all hooks

  • publicClient

    ShieldedPublicClient | null

    Shielded public client for reads

    walletClient

    ShieldedWalletClient | null

    Shielded wallet client for writes

    address

    Hex | null

    Connected wallet address

    error

    string | null

    Error message if initialization failed

    loaded

    boolean

    Whether the wallet client has finished initializing

    ShieldedWalletProvider
    useShieldedContract
    useShieldedWriteContract

    .dwrite

    Debug write — like .write but returns plaintext + encrypted views

    Calldata encrypted

    .write

    Encrypted transaction (TxSeismic type 0x4a)

    Calldata encrypted

    .read

    Encrypted signed eth_call

    Calldata + result encrypted

    .twrite

    Standard eth_sendTransaction

    Calldata plaintext

    .tread

    Standard eth_call

    SRC20arrow-up-right
    low-level API
    encode_shielded_calldataarrow-up-right

    Calldata plaintext

    This type is part of the Seismic protocol specification
    Shielded Write Guide - Guide to sending Seismic transactions

    Hexadecimal

    0x4A

    Decimal

    74

    0x00

    Legacy transactions (pre-EIP-2718)

    0x01

    EIP-2930 (Access lists)

    0x02

    EIP-1559 (Dynamic fee)

    0x03

    EIP-4844 (Blob transactions)

    0x04

    EIP-7702 (Set EOA account code)

    0x4A

    Seismic custom transactions

    UnsignedSeismicTx
    sign_seismic_tx_eip712
    ChainConfig
    The
    setUp()
    function initializes the Walnut contract for use in all test cases.

    hashtag
    Writing Test Cases

    Start off with testing the basic functionalities, hit , shake , look and reset

    hashtag
    Core functionalities

    1. Basic hit functionality

    Ensures the Walnut’s shell can be cracked by shellStrength number of hits.

    1. Basic shake functionality

    Validates that shaking the Walnut increments the kernel value.

    1. Reset functionality

    Now, test for the restrictive/conditional nature of these basic functionalities.

    hashtag
    Restricting Actions

    1. Preventing hit when shell is cracked

    Ensures that hitting a cracked shell is not allowed.

    1. Preventing shake when shell is cracked

    Ensures that shaking the Walnut after the shell is cracked is not allowed.

    1. Preventing look when shell is intact

    Ensures that the kernel cannot be revealed unless the shell is fully cracked.

    1. Preventing reset when shell is intact

    Validates that the Walnut cannot be reset unless the shell is fully cracked.

    Now, test for more complex scenarios.

    hashtag
    Complex scenarios

    1. Sequence of Multiple Actions

    Ensures that the Walnut behaves correctly under a sequence of hits and shakes.

    1. Prevent Non-Contributors From Using look()

    Ensures that only contributors in the current round can call look() .

    1. Contributor Tracking Across Rounds

    Validates that contributions are tracked independently for each round. The test has one contributor hit both times and crack the shell in the first round, and a different contributor hit and crack the shell in the second round. We check for the fact the second round contributor cannot see the kernel after the first round and the first round contributor cannot see the kernel after the second.

    You can find the entire test file herearrow-up-right.

    Test out the file by running the following inside the packages/contracts directory:

    The contract has been tested, time to deploy it!

    viem

    2.x

    Peer dependency -- installed alongside seismic-viem

    hashtag
    Install

    Install seismic-viem alongside its viem peer dependency using your preferred package manager:

    npm install seismic-viem viem
    yarn add seismic-viem viem
    pnpm add seismic-viem viem
    bun add seismic-viem viem

    hashtag
    Dependencies

    seismic-viem has a single peer dependency:

    Package
    Version
    Role

    viem

    2.x

    Core Ethereum client library (transports, chains, ABIs)

    The following cryptographic libraries are bundled internally and do not need to be installed separately:

    Package
    Purpose

    @noble/hashes

    SHA-256, HKDF, and other hash functions

    @noble/curves

    Elliptic curve operations (secp256k1, ECDH)

    @noble/ciphers

    AES-GCM encryption and decryption

    circle-info

    The @noble/* packages handle all client-side cryptographic operations including ECDH key exchange with the TEE and AES-GCM calldata encryption. They are bundled as direct dependencies of seismic-viem, so you never need to install or import them yourself.

    hashtag
    Module Format

    seismic-viem ships as both ESM and CJS with a single export entry point. TypeScript type declarations (.d.ts) are included -- no separate @types/ package is needed.

    hashtag
    Minimal Working Example

    Create a new project and verify the installation:

    Create index.ts:

    Run it:

    If you see a block number printed, the installation is working correctly.

    hashtag
    TypeScript

    seismic-viem is written in TypeScript and ships its own type declarations. No additional configuration is required beyond a standard tsconfig.json:

    circle-info

    All exported types, interfaces, and function signatures are fully typed. Your editor will provide autocompletion and type checking for all seismic-viem APIs out of the box.

    hashtag
    Troubleshooting

    hashtag
    Peer Dependency Warning

    If you see a peer dependency warning for viem, ensure you have viem 2.x installed:

    hashtag
    Node.js Version

    seismic-viem requires Node.js 18 or newer for native fetch and crypto support:

    hashtag
    ESM/CJS Interop

    If you encounter module resolution issues, ensure your tsconfig.json uses "moduleResolution": "bundler" or "node16", and that your package.json has "type": "module" if using ESM.

    hashtag
    See Also

    • Chains -- Configure network connections

    • Seismic Viem Overview -- Full SDK overview and architecture

    • seismic-react -- React hooks layer built on seismic-viem

    Node.js

    18+

    LTS recommended; install via nvmarrow-up-right or nodejs.orgarrow-up-right

    suint256 reserve0;
    suint256 reserve1;
    mapping(address => suint256) liquidity;
    
    function swap(address tokenIn, suint256 amountIn) internal returns (suint256 amountOut) {
        if (tokenIn == token0) {
            amountOut = (amountIn * reserve1) / (reserve0 + amountIn);
            reserve0 += amountIn;
            reserve1 -= amountOut;
        } else {
            amountOut = (amountIn * reserve0) / (reserve1 + amountIn);
            reserve1 += amountIn;
            reserve0 -= amountOut;
        }
    }
    import "@openzeppelin/contracts/access/AccessControl.sol";
    
    bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
    
    mapping(address => suint256) balanceOf;
    
    function getBalance(address account) public view returns (uint256) {
        require(
            msg.sender == account || hasRole(COMPLIANCE_ROLE, msg.sender),
            "Not authorized"
        );
        return uint256(balanceOf[account]);
    }
    
    function transfer(address to, suint256 amount) public {
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
    }
    mapping(address => sbool) hasVoted;
    suint256 yesVotes;
    suint256 noVotes;
    uint256 public votingEnd;
    
    function vote(sbool inFavor) public {
        require(block.timestamp < votingEnd, "Voting ended");
        require(!bool(hasVoted[msg.sender]), "Already voted");
    
        hasVoted[msg.sender] = sbool(true);
        if (bool(inFavor)) {
            yesVotes += 1s;
        } else {
            noVotes += 1s;
        }
    }
    
    function getResults() public view returns (uint256 yes, uint256 no) {
        require(block.timestamp >= votingEnd, "Voting still open");
        yes = uint256(yesVotes);
        no = uint256(noVotes);
    }
    mapping(address => suint256) bids;
    suint256 highestBid;
    saddress highestBidder;
    uint256 public auctionEnd;
    
    function bid(suint256 amount) public {
        require(block.timestamp < auctionEnd, "Auction ended");
        bids[msg.sender] = amount;
    
        if (uint256(amount) > uint256(highestBid)) {
            highestBid = amount;
            highestBidder = saddress(msg.sender);
        }
    }
    
    function getWinner() public view returns (address winner, uint256 amount) {
        require(block.timestamp >= auctionEnd, "Auction still open");
        winner = address(highestBidder);
        amount = uint256(highestBid);
    }
    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 hash
    tx_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 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..."),
    });
    
    const blockNumber = await client.getBlockNumber();
    console.log("Connected! Block:", blockNumber);
    npx tsx index.ts
    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true
      }
    }
    npm ls viem
    # Should show [email protected]
    node --version
    # Should be >= v18.0.0

    createSeismicDevnet()

    5124

    Factory for custom chain configs

    Seismic Testnet

    seismicTestnet

    5124

    Public testnet

    Sanvil

    sanvil

    31337

    Local Sanvil dev node

    Local Devnet

    localSeismicDevnet

    5124

    Seismic Testnet

    Public testnet configuration and usage

    Sanvil

    Local development chains (Sanvil + seismic-reth)

    createSeismicDevnet

    Factory for custom chain configurations

    ShieldedWalletProvider
    Wallet Guides
    Installation

    Local seismic-reth --dev

    Required
    Description

    abi

    Abi

    Yes

    Contract ABI

    address

    Address

    Yes

    Contract address

    hashtag
    Return Type

    Property
    Type
    Description

    contract

    ShieldedContract | null

    The shielded contract instance

    abi

    Abi

    The ABI passed in

    address

    Address

    The address passed in

    error

    Error | null

    Error if wallet client not initialized


    hashtag
    Usage

    hashtag
    Basic

    hashtag
    Using the contract for reads and writes

    Once you have a ShieldedContract instance, call its methods directly for both signed reads and shielded writes:

    hashtag
    TypeScript ABI typing

    For full type inference on contract methods, define your ABI with as const:

    circle-info

    useShieldedContract requires a connected wallet via ShieldedWalletProvider. The contract field is null until the wallet client is initialized.

    hashtag
    See Also

    • useShieldedWallet -- Access the underlying wallet and public clients

    • useShieldedWriteContract -- Send encrypted writes without a contract instance

    • useSignedReadContract -- Perform signed reads without a contract instance

    • -- Context provider required by this hook

    • -- Summary of all hooks

    hashtag
    Config
    Parameter
    Type
    Required
    Description

    address

    `0x${string}`

    Yes

    Contract address

    abi

    Abi

    Yes

    Contract ABI

    functionName

    string

    Yes

    hashtag
    Return Type

    Property
    Type
    Description

    signedRead

    () => Promise<any>

    Function to execute the signed read

    read

    () => Promise<any>

    Alias for signedRead

    isLoading

    boolean

    Whether a read is in progress

    error

    Error | null

    Error from the most recent read


    hashtag
    Usage

    hashtag
    Reading a shielded balance

    hashtag
    Loading state handling

    hashtag
    Error handling

    circle-info

    Unlike wagmi's useReadContract which auto-fetches on mount, useSignedReadContract returns a function you call imperatively. This is because signed reads require wallet interaction to prove caller identity.

    hashtag
    See Also

    • useShieldedWriteContract -- Send encrypted write transactions

    • useShieldedContract -- Contract instance with both read and write methods

    • useShieldedWallet -- Access the underlying wallet client

    • -- Context provider required by this hook

    • -- Summary of all hooks

    hashtag
    Definition

    hashtag
    Constructor Parameters

    Parameter
    Type
    Required
    Description

    w3

    Web3

    Yes

    Synchronous Web3 instance connected to RPC endpoint

    address

    ChecksumAddress

    Yes

    Contract address (checksummed Ethereum address)

    abi

    list[dict[str, Any]]

    Yes

    hashtag
    Namespace

    hashtag
    .tread - Transparent Read

    Executes standard eth_call with unencrypted calldata. This is the only namespace available on PublicContract.

    Returns: Any (ABI-decoded Python value)

    Optional Parameters: None (pass positional arguments only)

    hashtag
    Examples

    hashtag
    Basic Read Operations

    hashtag
    Single and Multiple Returns

    hashtag
    Array Results

    hashtag
    Error Handling

    hashtag
    Notes

    • Read-only: No write operations available (no .write, .twrite, or .dwrite namespaces)

    • No encryption required: Does not use EncryptionState or private keys

    • No authentication: Standard unsigned eth_call operations

    • Gas not consumed: eth_call is free (doesn't create transactions)

    • Public data only: Cannot access shielded/encrypted contract state

    hashtag
    See Also

    • AsyncPublicContract - Async version of this class

    • ShieldedContract - Full contract wrapper with write operations

    • create_public_client - Create client without private key

    • - Overview of contract interaction patterns

    Chain ID

    31337

    Name

    Sanvil

    RPC (HTTP)

    http://127.0.0.1:8545

    Native Currency

    ETH (18 decimals)

    hashtag
    Import

    hashtag
    Usage

    hashtag
    With RainbowKit

    hashtag
    With wagmi Config

    hashtag
    localSeismicDevnet

    localSeismicDevnet is a separate chain configuration for connecting to a locally-running seismic-reth node started in --dev mode. Use this instead of sanvil when you are running a full seismic-reth node locally rather than a Sanvil instance.

    circle-info

    Use sanvil for Sanvil (Seismic Anvil) instances. Use localSeismicDevnet for seismic-reth nodes running with the --dev flag.

    hashtag
    Installing Sanvil

    Sanvil is part of the Seismic Foundry toolchain. Install it with sfoundryup:

    Then start a local node:

    By default, Sanvil:

    • Listens on 127.0.0.1:8545

    • Uses chain ID 31337

    • Pre-funds test accounts with ETH

    • Provides instant block mining

    hashtag
    See Also

    • Chains Overview - All supported chains

    • Seismic Testnet - Public testnet configuration

    • createSeismicDevnet - Custom chain factory

    • - RainbowKit setup guides

    Ch 3: Rounds and Access Control

    In this chapter, we’ll implement a reset mechanism that allows the Walnut to be reused in multiple rounds, ensuring each game session starts fresh. We’ll also track contributors per round so that only players who participated in cracking the Walnut can call look(). By the end, we’ll have a fully functional round-based walnut game where the kernel remains shielded until conditions are met! Estimated time: ~15 minutes.

    hashtag
    The need for a Reset mechanism

    Right now, once the Walnut is cracked, there’s no way to reset it. If a game session were to continue, we’d have no way to start fresh—the shell would remain at 0, and the kernel would be permanently revealed.

    To solve this, we need to introduce:

    ✅ A reset function that restores the Walnut to its original state.

    ✅ Round tracking, so each reset creates a new round.

    hashtag
    The need for a contributor check

    While the reset mechanism and round tracking allow us to restart the Walnut for continuous gameplay, they still don’t address who should be allowed to call the look() function.

    Right now, any player can call look() once the shell is cracked, even if they didn’t participate in hitting it during the current round. This creates the following issues:

    • Fairness: Players who didn’t contribute should not be able to reap the benefits of seeing the kernel.

    • Incentivizing Contribution: The game needs to encourage active participation by ensuring that only those who helped crack the Walnut in a specific round are rewarded with access to the kernel.

    The solution to this is implementing a conditional check on look() which allows only those players who contributed in hitting the shell for a particular round (i.e., players whose hit count is >0 for that round) to view the kernel after the walnut is cracked.

    hashtag
    Implementing the Reset Mechanism

    The reset mechanism allows the Walnut to be reused for multiple rounds, with each round starting fresh. It restores the Walnut’s shell and kernel to their original states and increments the round counter to mark the beginning of a new round.

    Here’s how we can implement the reset function:

    These new state variables (initialShellStrength and initialKernel) should be set in the constructor alongside the existing assignments:

    What’s Happening Here?

    • Condition for Reset (requireCracked): The reset function can only be called once the Walnut’s shell is cracked, enforced by the requireCracked modifier.

    • Restoring Initial State: The shell strength and kernel are reset to their original values (initialShellStrength and initialKernel), ensuring the Walnut starts afresh for the next round.

    hashtag
    Modifying hit() to track contributions

    To enforce fair access to the kernel, we’ll track the number of hits each player contributes in a given round. This is achieved using the hitsPerRound mapping:

    Every time a player calls the hit() function, we update their contribution in the current round:

    What’s Happening Here?

    • Tracking Contributions: The hitsPerRound mapping records each player’s hits in the current round. This ensures we can verify who participated when the Walnut was cracked.

    • Replayable Rounds: Because contributions are tracked by round, the game can fairly reset and start fresh without losing player data from previous rounds.

    hashtag
    Restricting look() with a contributor check

    To ensure only contributors can reveal the kernel, we’ll use a modifier called onlyContributor:

    We’ll then apply this modifier to the look() function:

    Congratulations! You made it through to writing the entire shielded smart contract for a multiplayer, multi-round, walnut app!

    Final Walnut contract

    Now, onto testing the contract!

    Ch 3: Bringing It All Together

    Now that we’ve built the core logic for interacting with the Walnut contract, it’s time to tie everything together into a CLI that runs a multiplayer game session with two players - Alice and Bob. In this chapter, you’ll set up the environment variables for multiple players and write index.ts to simulate gameplay. Estimated time: ~20 minutes

    hashtag
    Set Up Environment Variables

    Before running the Walnut game, we need to define environment variables that store important configurations such as the RPC URL, chain ID, and player private keys.

    Create a .env in packages/cli :

    Open .env and paste the following:

    What’s Happening Here?

    • CHAIN_ID=31337 : 31337 is the default chain ID for sanvil (your local Seismic node).

    • RPC_URL=http://127.0.0.1:8545 : This is the RPC URL for interacting with the local Seismic node.

    • ALICE_PRIVKEY and BOB_PRIVKEY : These are Alice and Bob’s private keys, allowing them to play the game. (These are standard test keys provided by sanvil)

    hashtag
    Write index.ts

    Now, we’ll create the main entry point for our game session. This file will simulate gameplay, initializing players and having them interact with the Walnut contract. Open packages/cli/src/index.ts and follow these steps:

    hashtag
    Import Dependencies

    Import the required libraries to read environment variables, define network configurations, and interact with the Walnut contract.

    hashtag
    Define the main() function

    This function initializes the contract and player wallets, then runs the game session.

    hashtag
    Read Contract Details

    The contract’s ABI and deployed address are read from files generated during deployment.

    hashtag
    Select the blockchain network

    Determine whether to use the local sanvil node (31337) or the Seismic devnet.

    hashtag
    Define players

    Assign Alice and Bob as players with private keys stored in .env .

    hashtag
    Initialize the Game App

    Create an App instance to interact with the Walnut contract.

    hashtag
    Simulate the game round by round

    The following logic executes two rounds of gameplay between Alice and Bob.

    Round 1 - Alice Plays

    Round 2 - Bob Plays

    Alice Tries to Look in Round 2 (we expect this to fail since she has contributed in round 1 but not round 2)

    hashtag
    Execute the main() function

    This ensures that the script runs when executed.

    The entire index.ts file can be found

    hashtag
    Running the CLI

    Now, run the CLI from packages/cli by running:

    You should see something like this as the output:

    This output logs the events during two rounds of gameplay in the Walnut contract, showing interactions by Alice and Bob, along with a revert error when Alice attempts to call look() in Round 2.

    Congratulations! You've reached the end of the tutorial. You can find the code for the entire project .

    seismic-react

    React hooks and providers for Seismic, composing with wagmi to add shielded wallet management, encrypted transactions, and signed reads to React apps.

    React SDK (v1.1.1) for Seismicarrow-up-right, built on wagmiarrow-up-right 2.0+ and viemarrow-up-right 2.x. Provides ShieldedWalletProvider context and hooks for encrypted transactions and signed reads in React applications.

    hashtag
    Quick Start

    Minimal setup with RainbowKit:

    hashtag
    Architecture

    The SDK wraps wagmi's connector layer to inject Seismic's shielded wallet and public clients:

    hashtag
    Documentation Navigation

    hashtag
    Getting Started

    Section
    Description

    hashtag
    Hooks Reference

    Section
    Description

    hashtag
    Wallet Guides

    Section
    Description

    hashtag
    Quick Links

    hashtag
    By Task

    • Connect a shielded wallet ->

    • Read shielded state ->

    • Send an encrypted transaction ->

    hashtag
    By Component

    • Provider ->

    • Hooks -> , , ,

    • Chain configs -> seismicTestnet, sanvil

    hashtag
    Features

    • Shielded Transactions -- Encrypt calldata before sending via useShieldedWriteContract

    • Signed Reads -- Prove caller identity in eth_call with useSignedReadContract

    hashtag
    Next Steps

    1. -- Add the package and peer dependencies

    2. -- Wrap your app with the provider

    3. -- Choose a wallet integration

    Chains

    Network configurations and chain constants

    The Seismic SDK provides pre-configured network settings and utilities for connecting to Seismic chains. Chain configurations encapsulate RPC endpoints, WebSocket URLs, chain IDs, and convenience methods for creating clients.

    hashtag
    Overview

    Instead of manually passing RPC URLs and chain IDs when creating clients, you can use pre-defined chain configurations:

    hashtag
    Components

    hashtag
    Chain Configurations

    Component
    Description

    hashtag
    Protocol Constants

    Constant
    Description

    hashtag
    Quick Start

    hashtag
    Using Pre-Defined Chains

    hashtag
    Accessing Chain Properties

    hashtag
    Alternate Testnet Instances

    hashtag
    Custom Chains

    hashtag
    Client Creation Methods

    All chain configurations provide convenience methods for creating clients:

    hashtag
    Wallet Clients (Require Private Key)

    hashtag
    Public Clients (No Private Key)

    hashtag
    Chain IDs

    The SDK defines constants for chain IDs used in transaction signing:

    These are automatically included in the chain configurations and used by the signing infrastructure.

    hashtag
    Notes

    • Chain configurations are immutable (frozen dataclasses)

    • WebSocket support is automatic when ws=True and ws_url is configured

    • HTTP endpoints are used as fallback if WebSocket URL is unavailable

    hashtag
    See Also

    • - Detailed client setup documentation

    • - Full wallet client capabilities

    • - Read-only client features

    make_seismic_testnet

    Factory function for testnet chain configs

    Factory function that creates a ChainConfig for a Seismic testnet node.

    hashtag
    Overview

    make_seismic_testnet generates a chain configuration for a GCP testnet node or a custom host. This is useful when you need to connect to alternate testnet nodes beyond the default SEISMIC_TESTNET (which is GCP node 1).

    hashtag
    Definition

    hashtag
    Parameters

    Parameter
    Type
    Required
    Default
    Description

    Raises ValueError if both n and host are provided.

    hashtag
    Returns

    • ChainConfig - A chain configuration for the specified testnet

    hashtag
    Examples

    hashtag
    Connect to Alternate GCP Nodes

    hashtag
    Custom Host

    hashtag
    Default Parameter

    hashtag
    Relationship to SEISMIC_TESTNET

    The default constant is defined as:

    hashtag
    See Also

    • - Chain configuration dataclass

    • - Pre-defined testnet (GCP node 1)

    • - Private key type

    ERC20 to SRC20: What Changes

    See exactly what changes between a standard ERC20 and a private SRC20

    This chapter puts a standard ERC20 and its SRC20 counterpart side by side so you can see exactly how little changes. To get privacy on Seismic, just change the types. Estimated time: ~10 minutes.

    hashtag
    The standard ERC20

    Here is a minimal but complete ERC20 contract. It covers the full interface -- name, symbol, decimals, totalSupply, balances, allowances, transfer, approve, and transferFrom:

    This is standard Solidity. Every balance, transfer amount, and allowance is a uint256

    Footguns

    Shielded types protect values at rest and in transit, but careless usage patterns can leak information through side channels. This page covers the most common information leak vectors when working with shielded types.

    hashtag
    Conditional Execution

    The problem: Using an sbool in a conditional branch leaks information through the execution trace and gas consumption. Observers can tell which branch was taken by examining the gas used or the operations performed.

    Contract Instance

    Type-safe shielded contract instances with read/write/tread/twrite/dwrite namespaces

    getShieldedContract creates a type-safe contract instance with five namespaces for interacting with shielded contracts. It extends viem's getContract with Seismic-specific read and write patterns, giving you a single object that covers encrypted writes, signed reads, transparent operations, and debug inspection.

    hashtag
    Constructor

    Parameter

    Signed Reads

    Authenticated read calls that prove caller identity

    On standard Ethereum, eth_call can spoof any from address -- there is no signature check. Seismic prevents this: all unsigned eth_call requests have msg.sender set to the zero address. To read shielded data that depends on msg.sender, you need a signed read: a signed transaction submitted to eth_call that proves the caller's identity.

    Shielded Writes

    Send encrypted write transactions with shieldedWriteContract

    Shielded writes encrypt transaction calldata before submission. This prevents calldata from being visible on-chain -- an observer can see that a transaction was sent to a particular contract address, but not what function was called or what arguments were passed.

    seismic-viem provides two approaches:

    • shieldedWriteContract() -- standalone function, same API shape as viem's writeContract

    Installation

    Install seismic-react and configure peer dependencies

    hashtag
    Prerequisites

    Requirement
    Version
    Notes

    SEISMIC_TESTNET

    Pre-configured Seismic public testnet

    Pre-defined for the Seismic public testnet (GCP node 1).

    hashtag
    Overview

    SEISMIC_TESTNET is a ready-to-use chain configuration pointing to the primary Seismic testnet instance. It's the recommended starting point for developers building on Seismic.

    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
    sfoundryup
    sanvil
    npm install seismic-react
    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>
      )
    }
    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)
    ShieldedWalletProvider
    Hooks Overview

    Name of the view/pure function to call

    args

    array

    No

    Arguments to pass to the function

    ShieldedWalletProvider
    Hooks Overview

    Contract ABI (list of function entries)

    Contract Instance Guide
    Wallet Guides
    Custom Devnet

    Round Tracking: The round counter increments each time the Walnut is reset, allowing us to distinguish between rounds.

    herearrow-up-right
    herearrow-up-right
    Get a contract instance -> useShieldedContract
  • Install the package -> Installation

  • Set up RainbowKit -> RainbowKit Guide

  • ,
    localSeismicDevnet
    ,
    createSeismicDevnet
    Contract Abstraction -- ABI-bound contract instances via useShieldedContract
  • wagmi/RainbowKit Integration -- Drop-in provider that composes with the standard wagmi stack

  • TypeScript Support -- Full type inference from contract ABIs

  • Use hooks -- Read and write shielded contract state

    Installation

    Package setup, peer dependencies, and configuration

    ShieldedWalletProvider

    React context provider for shielded clients

    Hooks Overview

    Summary of all available hooks

    useShieldedWallet

    Access shielded wallet and public clients

    useShieldedContract

    Create a contract instance with ABI binding

    useShieldedWriteContract

    Send encrypted write transactions

    useSignedReadContract

    Perform signed, encrypted read calls

    Wallet Guides Overview

    Connecting different wallet providers

    RainbowKit

    Setup with RainbowKit wallet UI

    Privy

    Embedded wallets with Privy

    AppKit

    WalletConnect AppKit integration

    ShieldedWalletProvider
    useSignedReadContract
    useShieldedWriteContract
    ShieldedWalletProvider
    useShieldedWallet
    useShieldedContract
    useShieldedWriteContract
    useSignedReadContract
    Install seismic-react
    Set up ShieldedWalletProvider
    Connect a wallet

    All methods return standard web3.py instances with the w3.seismic namespace attached

    PrivateKey - Private key type documentation

    ChainConfig

    Immutable dataclass for network configuration

    SEISMIC_TESTNET

    Public testnet configuration (GCP-1)

    SANVIL

    Local development network configuration

    make_seismic_testnet

    Factory for alternate testnet instances

    SEISMIC_TX_TYPE

    Transaction type byte for Seismic transactions

    Client Creation
    Wallet Client
    Public Client

    n

    int

    No

    1

    GCP node number

    host

    str | None

    No

    None

    Custom hostname. Mutually exclusive with n

    SEISMIC_TESTNET
    ChainConfig
    SEISMIC_TESTNET
    PrivateKey
    , visible to anyone who queries the contract or reads the chain.

    hashtag
    The SRC20 version

    Here is the same contract converted to an SRC20. Changed lines are marked with comments:

    That is the entire diff. The contract logic is structurally identical.

    hashtag
    Line-by-line diff

    hashtag
    Balances: uint256 to suint256

    Two changes here. First, the value type changes from uint256 to suint256. This tells the Seismic compiler to emit CSTORE/CLOAD instead of SSTORE/SLOAD, which marks these storage slots as private. Second, the public visibility modifier is removed. Shielded types cannot be returned from public or external functions, so the automatic getter that public generates would not compile. We will add an explicit balance-checking function using signed reads in a later chapter.

    hashtag
    Allowances: same pattern

    The same change applies to the allowance mapping. The nested mapping's value type becomes suint256, and the public modifier is removed.

    hashtag
    Function parameters: shielded amounts

    The amount parameter changes to suint256. When a user calls this function through a Seismic transaction (type 0x4A), the amount is encrypted in the calldata before it leaves their machine. During execution inside the TEE, the amount is decrypted and used normally. Observers watching the mempool or block data see 0x00...0 in place of the amount.

    The same change applies to approve and transferFrom.

    hashtag
    Constructor: casting the initial supply

    Since _initialSupply is a regular uint256 (it is the total supply, which is public) and balanceOf now stores suint256 values, an explicit cast is required. Seismic does not allow implicit casting between shielded and unshielded types.

    hashtag
    Events: casting back to uint256

    Events are stored in transaction logs, which are public. Shielded types cannot appear in event parameters. The simplest approach is to cast the amount back to uint256 before emitting. Note that this does reveal the amount in the event log. If you need the event data to also be private, the Encrypted Events chapter shows how to use AES-GCM precompiles to encrypt it.

    hashtag
    What stays the same

    A lot stays the same, which is the point:

    • Transfer logic -- The subtraction, addition, and require checks are identical. Arithmetic operations on suint256 work the same as on uint256.

    • Overflow protection -- Solidity 0.8+ overflow checks work with shielded types.

    • Function signatures -- The function names and return types are unchanged. The contract is still recognizable as an ERC20.

    • Address parameters -- The to, from, and spender parameters remain regular address types. Mapping keys cannot be shielded types, so these must stay as address.

    • totalSupply -- This stays as a regular uint256. The total supply is public information. Individual balances are private, but the aggregate is visible.

    hashtag
    What you lose (and how to get it back)

    There are two capabilities that the basic SRC20 loses compared to a standard ERC20:

    hashtag
    1. Public balance queries

    Since balanceOf cannot be public, there is no automatic getter. Users cannot call balanceOf(address) the way they would with a standard ERC20. The solution is signed reads -- a Seismic-specific mechanism where a user sends a signed eth_call (type 0x4A) to prove their identity, and the contract returns their balance only to them. This is covered in Signed Reads for Balance Checking.

    hashtag
    2. Private event data

    With the simple cast approach above, the amount appears in plaintext in the event log. If you need transfer amounts to be hidden in events as well, you can encrypt the data using Seismic's AES-GCM precompiles before emitting. This is covered in Encrypted Events.

    Both of these are straightforward additions. The next chapters walk through each one.

    Why it leaks:
    The EVM execution trace shows which opcodes were executed. If the
    if
    branch runs different code (or a different amount of code) than the
    else
    branch, observers can determine which branch was taken, revealing the value of the shielded boolean.

    What to do instead: Ensure both branches execute the same operations with the same gas cost. A ternary where both arms are simple assignments of the same type is safe — the EVM does the same work for both paths.

    hashtag
    Literals

    The problem: Assigning literal values to shielded types — whether via explicit cast or the s suffix — embeds those values directly in the contract bytecode, which is publicly visible.

    What to do instead: Be aware that literals are embedded in contract bytecode and are publicly visible. The compiler emits warning 9660 for all shielded literals to remind you. If the initial value is sensitive, introduce it via encrypted calldata instead of hardcoding it.

    hashtag
    Dynamic Loops

    The problem: Using a shielded value as a loop bound leaks the value through gas consumption. Each iteration costs gas, so the total gas used reveals how many times the loop executed.

    Why it leaks: Gas is publicly visible. If a loop runs 5 times vs. 100 times, the gas difference is observable. This reveals the shielded loop bound.

    What to do instead: Use a fixed-size loop with a known maximum, and perform no-op iterations when the actual count is smaller. The no-op path must cost the same gas as the real-work path — otherwise an observer can count how many iterations did real work by comparing per-iteration gas costs.

    hashtag
    Unprotected View Functions

    The problem: If you write a view function that unshields and returns private data without access control, anyone can read it. The shielded storage is meaningless if a public getter exposes the plaintext.

    Why it leaks: The function casts the shielded value to a plain uint256 and returns it with no restriction on who can call it. The value is returned in plaintext to the caller.

    What to do instead: Always add access control to view functions that unshield data. If the getter checks msg.sender, callers must use a signed read — otherwise msg.sender will be the zero address.

    circle-info

    Access control doesn't have to be sender-based. Time-locked reveals, role-based access, or any other gating logic is fine — the important thing is that the function doesn't unconditionally return private data.

    hashtag
    Public Shielded Variables

    The problem: Declaring a shielded variable as public will not compile. Solidity automatically generates a public getter for public state variables, which would return the shielded value -- violating the rule that shielded types cannot be returned from public or external functions.

    What to do instead: Declare shielded variables as private or internal. If you need to expose the value, unshield it explicitly with a cast, and use access control (see above).

    hashtag
    Unencrypted Calldata

    The problem: There is currently nothing enforcing that functions with shielded parameters are called via a Seismic transaction (type 0x4A). You can call a function that accepts suint256 with a regular transaction, and the shielded parameter values will be visible in plaintext in the transaction's input data. Likewise, you can use a Seismic transaction to call a function with no shielded inputs — the encryption is unnecessary but harmless.

    What to do instead: If a function has any shielded inputs, always call it via a Seismic transaction. This is the caller's responsibility — the contract cannot currently enforce it. We plan to tighten this up in the future so the runtime rejects non-Seismic calls to functions with shielded parameters.

    hashtag
    Exponentiation

    The problem: The ** operator has a gas cost that scales with the value of the exponent. If the exponent is a shielded value, the gas cost reveals the exponent.

    Why it leaks: The EVM's modular exponentiation implementation uses more gas for larger exponents. An observer monitoring gas consumption can estimate the exponent value.

    What to do instead: Avoid using shielded values as exponents entirely. If you need exponentiation with a private exponent, consider alternative algorithms and make sure you think carefully about their gas consumption profile.

    hashtag
    Enums

    The problem: Enum types have a small, known range of possible values. If you convert an enum to a shielded type, the limited range makes it easy to guess the shielded value — an observer only needs to try a handful of possibilities.

    What to do instead: Consider whether the limited range of possible values undermines the privacy guarantee. Shielding an enum with 3 members is not meaningfully private.

    hashtag
    immutable and constant Shielded Variables

    Shielded types cannot be declared as immutable or constant. The compiler will reject both with an error. Constants are embedded in bytecode (publicly visible), and immutables are stored in bytecode after construction — neither is compatible with the confidential storage model.

    What to do instead: Use a regular private shielded variable initialized in the constructor or via a setter function called through a Seismic transaction.

    hashtag
    RNG Proposer Bias

    The problem: The synchronous RNG precompile produces randomness that is deterministic given the enclave's secret key, the transaction hash, remaining gas, and personalization bytes. In theory, a block proposer could simulate RNG outputs and selectively include, exclude, or reorder transactions to influence outcomes.

    Why this matters: Seismic's TEE setup largely mitigates this — proposers are restricted in what they can observe and do, and we believe synchronous RNG is safe for most use cases. This is something we've thought about extensively. However, for applications with especially high-stakes randomness requirements, it's worth being aware of the theoretical attack surface.

    What to do instead: For the most sensitive randomness use cases (large-pot lotteries, leader elections), consider using an asynchronous commit-reveal scheme where entropy is committed before the block in which it is consumed. Seismic does not provide this out of the box today. For the vast majority of use cases, the synchronous RNG precompile is appropriate.

        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 .env
    CHAIN_ID=31337
    RPC_URL=http://127.0.0.1:8545
    ALICE_PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    BOB_PRIVKEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
    import 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 revert
    wagmi config
      └─ ShieldedWalletProvider
           ├─ ShieldedPublicClient  (encrypted reads)
           ├─ ShieldedWalletClient  (encrypted writes)
           └─ Hooks
                ├─ useShieldedWallet          Access wallet/public clients
                ├─ useShieldedContract         Contract instance with ABI binding
                ├─ useShieldedWriteContract    Encrypted write transactions
                └─ useSignedReadContract       Signed, encrypted reads
    import 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           # 31337
    def 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 shieldedExp
    enum 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];
    }
    Type
    Required
    Description

    abi

    Abi

    Yes

    Contract ABI (use as const for full type inference)

    address

    Address

    Yes

    Deployed contract address

    client

    ShieldedWalletClient

    Yes

    Wallet client with encryption capabilities

    hashtag
    Namespaces

    The returned ShieldedContract exposes five namespaces. Each namespace provides every function defined in the ABI, but differs in how calldata is handled and who appears as msg.sender on-chain.

    Namespace
    Operation Type
    Calldata
    msg.sender
    Description

    .read

    Signed Read

    Encrypted

    Signer's address

    Authenticated read -- proves identity

    .write

    Shielded Write

    Encrypted

    Signer's address

    Encrypted transaction


    hashtag
    .read -- Signed Read

    Sends an encrypted, signed eth_call that proves your identity to the contract. Use this when the contract checks msg.sender in a view function to gate access to shielded data.

    circle-info

    The .read namespace always sets account to client.account for security. This ensures the signed read is authenticated with the wallet's address, regardless of any account override you pass.

    See Signed Reads for details on how signed reads work under the hood.


    hashtag
    .write -- Shielded Write

    Encrypts calldata before broadcasting the transaction. The calldata is not visible on-chain.

    See Shielded Writes for the encryption lifecycle and security parameters.


    hashtag
    .tread -- Transparent Read

    Standard viem readContract call with plaintext calldata. The from address is set to the zero address, so msg.sender inside the contract will be 0x0000...0000.

    Use .tread for public view functions that do not depend on msg.sender.


    hashtag
    .twrite -- Transparent Write

    Standard viem writeContract call with plaintext (unencrypted) calldata. The transaction is signed by the wallet, so msg.sender is the signer's address.

    Use .twrite when you intentionally want calldata to be visible on-chain (e.g., for transparency or debugging).


    hashtag
    .dwrite -- Debug Write

    Builds the same encrypted transaction as .write, but returns the plaintext transaction, the shielded transaction, and the transaction hash without broadcasting. Useful for inspecting what the SDK produces before sending.

    hashtag
    TypeScript ABI Typing

    For full type inference on function names, argument types, and return types, declare your ABI with as const:

    Without as const, the ABI is widened to readonly AbiItem[] and the contract loses per-function type safety. You can still call functions by name, but arguments and return values will be untyped.

    hashtag
    See Also

    • Shielded Writes -- Encryption lifecycle and shieldedWriteContract

    • Signed Reads -- Authenticated reads and signedReadContract

    • Shielded Wallet Client -- Creating the client passed to getShieldedContract

    • -- ECDH key exchange and AES-GCM calldata encryption

    hashtag
    Why Signed Reads Matter

    Contracts can use msg.sender in view functions to gate access to shielded data. A common example: a token contract with a balanceOf() that takes no arguments and uses msg.sender internally to look up the caller's balance. Without a signed read, the contract sees the zero address and returns that address's balance -- which is almost certainly zero.

    seismic-viem provides two approaches:

    • signedReadContract() -- standalone function, same API shape as viem's readContract

    • contract.read.functionName() -- via a ShieldedContract from getShieldedContract

    Both encrypt the calldata, sign it, and decrypt the response automatically.


    hashtag
    Standalone: signedReadContract

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    address

    Hex

    Yes

    Contract address

    abi

    Abi

    Yes

    Contract ABI

    functionName

    string

    Yes

    hashtag
    Returns

    Promise<ReadContractReturnType> -- the decoded return value, typed according to the ABI.

    hashtag
    Example


    hashtag
    Low-level: signedCall

    For cases where you have raw calldata instead of ABI-encoded parameters -- for example, pre-encoded data or non-ABI interactions:

    signedCall also accepts SeismicSecurityParams as a third argument for overriding the encryption nonce, block hash, or expiry window. See Shielded Writes for the full parameter table.


    hashtag
    How It Works

    When you call signedReadContract (or contract.read.functionName), the SDK performs the following steps:

    1. ABI-encode the function call into plaintext calldata

    2. Build Seismic metadata with signedRead: true

    3. Encrypt calldata with AES-GCM using the shared key derived via ECDH

    4. Sign the transaction:

      • For local accounts (private key): sign as a raw Seismic transaction, send to eth_call

      • For JSON-RPC accounts (MetaMask): sign EIP-712 typed data via eth_signTypedData_v4, send the typed data + signature to eth_call

    5. Decrypt the response returned by the node

    6. Decode the ABI output into the expected return type

    Both the calldata you send and the result you receive are encrypted. An observer watching the network can see that a call was made to a particular contract address, but not what function was called or what was returned.


    hashtag
    Signed Read vs Transparent Read

    Aspect

    .read (Signed)

    .tread (Transparent)

    Calldata

    Encrypted

    Plaintext

    msg.sender

    Signer's address

    Zero address

    Use case

    Shielded data gated by msg.sender

    Public view functions

    Performance

    Slightly slower (sign + encrypt + decrypt)

    Standard eth_call speed

    circle-info

    Signed reads are critical for any contract that checks msg.sender in view functions to gate shielded data access. If you are unsure whether a view function depends on msg.sender, use .read -- it is always safe, just slightly slower than .tread.

    hashtag
    See Also

    • Contract Instance -- getShieldedContract with .read and .tread namespaces

    • Shielded Writes -- Encrypted write transactions using the same pipeline

    • -- ECDH key exchange and AES-GCM details

    • -- Creating the client used for signed reads

    contract.write.functionName() -- via a ShieldedContract from getShieldedContract

    Both produce the same on-chain result: an encrypted type 0x4A Seismic transaction.

    hashtag
    Standalone: shieldedWriteContract

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    address

    Hex

    Yes

    Contract address

    abi

    Abi

    Yes

    Contract ABI

    functionName

    string

    Yes

    hashtag
    Returns

    Promise<Hash> -- the transaction hash.

    hashtag
    Example


    hashtag
    Debug: shieldedWriteContractDebug

    Returns the plaintext transaction, the shielded (encrypted) transaction, and the transaction hash. Useful for inspecting what the SDK encrypts and broadcasts.


    hashtag
    Low-level: sendShieldedTransaction

    For cases where you have raw transaction data instead of ABI-encoded calls -- for example, contract deployments, pre-encoded calldata, or bypassing the contract abstraction entirely:


    hashtag
    How It Works

    When you call shieldedWriteContract (or contract.write.functionName), the SDK performs the following steps:

    1. ABI-encode the function call into plaintext calldata

    2. Build Seismic metadata -- encryption nonce, recent block hash, expiry block

    3. Encrypt calldata with AES-GCM using a shared key derived via ECDH between your ephemeral keypair and the node's TEE public key

    4. Construct a type 0x4A transaction with the encrypted calldata and Seismic-specific fields

    5. Sign and broadcast the transaction

    The encrypted calldata is bound to the transaction context (chain ID, nonce, block hash, expiry) via AES-GCM additional authenticated data, so it cannot be replayed or tampered with.


    hashtag
    Security Parameters

    Every shielded transaction includes a block-hash freshness check and an expiry window. The defaults are sensible for most cases, but you can override them per-call via SeismicSecurityParams:

    Parameter
    Type
    Default
    Description

    blocksWindow

    bigint

    100n

    Number of blocks before the transaction expires

    encryptionNonce

    Hex

    Random

    Override the encryption nonce

    recentBlockHash

    Hex

    Latest

    circle-info

    The default 100-block window, random nonce, and latest block hash are appropriate for nearly all use cases. Override these only if you have a specific reason -- for example, reducing the window for time-sensitive operations or pinning the block hash in tests.

    hashtag
    See Also

    • Contract Instance -- getShieldedContract with .write and .dwrite namespaces

    • Signed Reads -- Authenticated reads that also use the encryption pipeline

    • -- ECDH key exchange and AES-GCM details

    • -- Creating the client used for shielded writes

    React

    ^18

    Peer dependency

    wagmi

    ^2.0.0

    Peer dependency

    viem

    2.x

    Peer dependency

    seismic-viem

    >=1.1.1

    Seismic transport layer

    @rainbow-me/rainbowkit

    ^2.0.0

    Optional, for wallet UI

    hashtag
    Install

    hashtag
    Peer Dependencies

    seismic-react requires several peer dependencies that your project must provide:

    Package
    Purpose

    react

    React runtime

    wagmi

    Ethereum React hooks and wallet connectors

    viem

    TypeScript Ethereum library (used internally by wagmi)

    seismic-viem

    Seismic transport layer providing ShieldedPublicClient and ShieldedWalletClient

    @tanstack/react-query

    Async state management (required by wagmi)

    Install all required peer dependencies:

    circle-info

    If you are using RainbowKit for wallet UI, also install @rainbow-me/rainbowkit:

    hashtag
    Wagmi Config Setup

    Create a wagmi config with Seismic chain definitions:

    circle-exclamation

    Replace YOUR_WALLETCONNECT_PROJECT_ID with a project ID from WalletConnect Cloudarrow-up-right.

    hashtag
    Minimal Working App

    hashtag
    Package Exports

    seismic-react provides two entry points:

    Entry Point
    Contents

    seismic-react

    Main entry -- ShieldedWalletProvider, useShieldedWallet, useShieldedContract, useShieldedWriteContract, useSignedReadContract

    seismic-react/rainbowkit

    Chain configs -- seismicTestnet, sanvil, localSeismicDevnet, createSeismicDevnet

    hashtag
    TypeScript

    TypeScript >= 5.0.4 is an optional peer dependency. seismic-react ships with full type definitions and provides type inference from contract ABIs when using hooks like useShieldedWriteContract and useSignedReadContract.

    No additional @types/* packages are needed.

    hashtag
    Troubleshooting

    hashtag
    Module Not Found: seismic-viem

    Ensure seismic-viem is installed as a peer dependency:

    hashtag
    wagmi Version Mismatch

    seismic-react requires wagmi v2. If you have wagmi v1 installed, upgrade:

    hashtag
    RainbowKit Chain Not Appearing

    Make sure you import chain configs from the seismic-react/rainbowkit entry point, not from the main entry:

    hashtag
    React Version Conflicts

    If you encounter React version conflicts in a monorepo, ensure all packages resolve to the same React version. Add a resolutions field (Yarn) or overrides field (npm) to your root package.json:

    hashtag
    See Also

    • ShieldedWalletProvider -- Provider setup and configuration

    • Hooks Overview -- Available React hooks

    • RainbowKit Guide -- Wallet UI integration

    • -- Embedded wallet setup

    Node.js

    18+

    LTS recommended

    hashtag
    Definition

    Internally, this is equivalent to:

    hashtag
    Configuration

    Property
    Value

    chain_id

    5124

    rpc_url

    "https://gcp-1.seismictest.net/rpc"

    ws_url

    "wss://gcp-1.seismictest.net/ws"

    name

    "Seismic Testnet"

    hashtag
    Usage

    hashtag
    Import and Access Properties

    hashtag
    Create Wallet Client (Sync)

    hashtag
    Create Wallet Client (Async)

    hashtag
    Create Public Client

    hashtag
    Examples

    hashtag
    Basic Shielded Transaction

    hashtag
    Using with Environment Variables

    hashtag
    Checking Connection

    hashtag
    Notes

    • This is the primary public testnet instance

    • Suitable for development and testing

    • WebSocket endpoint is available for subscriptions

    • For alternate testnet instances, use

    hashtag
    Chain ID Constant

    The chain ID is also available as a standalone constant:

    hashtag
    See Also

    • ChainConfig - Chain configuration dataclass

    • make_seismic_testnet - Factory for alternate testnet instances

    • SANVIL - Local development network

    • - Direct client creation

    • - Private key type

    ChainConfig

    Encrypted Events

    Emit transfer events with encrypted amounts using AES precompiles

    In the previous chapter, we cast suint256 amounts to uint256 before emitting events. This works, but it reveals the amount in the public event log. This chapter shows how to encrypt event data so that only the intended recipients can read it. Estimated time: ~20 minutes.

    hashtag
    The problem

    Events in Ethereum (and Seismic) are stored in transaction logs, which are public. You cannot use shielded types directly in event parameters:

    The compiler rejects this because event data is written to public logs, and shielded types are only meaningful in contract storage. If you cast to uint256 and emit, the amount appears in plaintext in the log -- defeating the purpose of shielding it in the first place.

    hashtag
    The solution

    Use Seismic's AES-GCM precompiles to encrypt the sensitive data before emitting it. The event carries opaque bytes that only the intended recipient can decrypt.

    The modified event signature uses bytes instead of uint256 for the amount:

    The from and to addresses remain as indexed parameters. These are public -- observers can see who is transacting with whom. Only the amount is encrypted. If you need to hide the participants as well, you can encrypt those too, but that is less common for a token.

    hashtag
    Step by step

    The encryption flow uses three of Seismic's precompiles:

    hashtag
    Step 1: Derive a shared secret with ECDH

    The ECDH precompile at address 0x65 performs Elliptic Curve Diffie-Hellman key agreement. Given a private key and a public key, it produces a shared secret that both parties can independently derive.

    For event encryption, the contract needs a keypair. The private key must not be passed as a constructor argument -- constructor calldata is not encrypted (CREATE/CREATE2 transactions are standard Ethereum transaction types), so the key would leak in the deployment data.

    Instead, set the key after deployment via a Seismic transaction (type 0x4A), which encrypts calldata:

    Because setContractKey is called via a Seismic transaction, the private key is encrypted in the calldata before it leaves the caller's machine. The key is stored as sbytes32, so the compiler routes it through shielded storage — observers see 0x00...0 instead of the actual key.

    To derive a shared secret with a specific recipient, the contract calls the ECDH precompile with its own private key and the recipient's public key:

    hashtag
    Step 2: Derive an encryption key with HKDF

    The raw ECDH shared secret should not be used directly as an encryption key. The HKDF precompile at address 0x68 derives a proper cryptographic key from the shared secret:

    The second argument is a context string (sometimes called "info" in HKDF terminology). Using a unique context string for each purpose ensures that the same shared secret produces different keys for different uses.

    hashtag
    Step 3: Encrypt with AES-GCM

    The AES-GCM Encrypt precompile at address 0x66 encrypts the data:

    hashtag
    Step 4: Emit the encrypted event

    Putting it all together in an internal helper:

    hashtag
    Full implementation

    Here is the updated transfer function using encrypted events:

    Users register their public key by calling registerPublicKey once. After that, any transfer they receive will emit an event encrypted to their key.

    hashtag
    Decrypting off-chain

    The recipient can decrypt the event data by performing the reverse of the encryption flow:

    1. Take the contract's public key (stored on-chain and readable by anyone).

    2. Combine it with their own private key using ECDH to derive the same shared secret.

    3. Run HKDF with the same context string ("src20-transfer-event") to derive the same encryption key.

    Decrypt events client-side using the ECDH shared secret and AES-GCM decryption. See the for the cryptographic primitives.

    hashtag
    Who can read what

    Here is the visibility breakdown for each piece of data in a Transfer event:

    Data
    Who can see it
    Why

    The sender can also decrypt the amount because they know the plaintext -- they created the transaction. If you need the sender to be able to decrypt from the event log as well (for example, for transaction history), you can emit a second event encrypted to the sender's key, or encrypt to both keys and include both ciphertexts.

    circle-info

    Encrypted events add gas cost for the precompile calls. For applications where the event amount being public is acceptable, the simpler uint256(amount) cast from the previous chapter is more gas-efficient. Choose the approach that matches your privacy requirements.

    Intelligence Contracts

    Add compliance-compatible access control to your private token

    Privacy and compliance are often framed as opposites. Intelligence Contracts show that they are not. This chapter adds role-based access control to the SRC20 so that authorized parties -- auditors, compliance officers, regulators -- can inspect shielded state when required, without compromising privacy for everyone else. Estimated time: ~15 minutes.

    hashtag
    The concept

    An Intelligence Contract is a smart contract that can selectively reveal shielded state to authorized parties. The contract stores data privately by default, but includes gated functions that cast shielded values to their unshielded counterparts -- only for callers who hold the right role.

    The key insight: the data stays shielded in storage at all times. No plaintext balances are ever written to public state. Authorized parties read the data through signed reads, which means the response is encrypted to their key. The balance is revealed only to the specific authorized caller, not to the world.

    hashtag
    Why this matters

    Real-world token issuers need to answer questions like:

    • Can a compliance officer verify that an account's balance is below a threshold?

    • Can an auditor check aggregate balances across a set of accounts?

    • Can the token issuer freeze a specific account if required by law?

    Without Intelligence Contracts, privacy is all-or-nothing: either everyone can see everything, or no one can. With Intelligence Contracts, you get selective disclosure -- the right people see the right data, and no one else does.

    hashtag
    Implementation with AccessControl

    We will use OpenZeppelin's AccessControl to manage roles. This is a battle-tested pattern used across thousands of Ethereum contracts.

    hashtag
    Access tiers

    The contract implements three levels of access:

    Role
    Can do
    How they access

    hashtag
    Granting roles

    The deployer holds DEFAULT_ADMIN_ROLE and can grant roles to other addresses:

    In TypeScript:

    hashtag
    Compliance officer reading a balance

    The compliance officer uses a signed read, just like a regular user. The only difference is the function they call:

    The balance is returned encrypted to the compliance officer's key. No one else -- not even other compliance officers -- can see this specific response.

    hashtag
    The privacy guarantee

    Even with compliance roles in place, the privacy model is strong:

    1. Data stays shielded in storage. The balanceOf mapping always stores suint256. No plaintext balances are ever written to public state, regardless of who has what role.

    2. Reads go through signed reads. Whether it is a user checking their own balance or a compliance officer auditing an account, the query is a signed read. The response is encrypted to the caller's key.

    circle-info

    The role structure shown here is a starting point. In production, you might add time-limited roles, multi-sig requirements for granting compliance access, or on-chain audit logs that record when a compliance officer accessed a balance (without revealing the balance itself).

    Building the Frontend

    Connect your SRC20 contract to a React frontend with seismic-react

    This chapter connects the SRC20 contract to a React frontend using seismic-react, which composes with wagmiarrow-up-right to provide shielded reads, shielded writes, and encrypted communication out of the box. Estimated time: ~25 minutes.

    hashtag
    Overview

    By the end of this chapter you will have a React application that can:

    • Connect a wallet through a ShieldedWalletProvider

    • Display the user's shielded balance (via signed reads)

    • Transfer tokens (via shielded writes)

    • Listen for Transfer events and decrypt the encrypted amounts

    The patterns here mirror standard wagmi usage. If you have built a dApp with wagmi before, the seismic-react equivalents will feel familiar.

    hashtag
    Setup

    Install the required packages:

    hashtag
    Configure the ShieldedWalletProvider

    The ShieldedWalletProvider wraps your application and provides the shielded wallet context to all child components. It works alongside wagmi's standard provider:

    The ShieldedWalletProvider handles the cryptographic setup needed for Seismic transactions -- deriving encryption keys, managing the TEE public key, and signing shielded requests.

    hashtag
    Connecting to the contract

    Use the useShieldedContract hook to get a contract instance that supports shielded reads and writes:

    This hook returns a contract instance bound to the currently connected shielded wallet. All reads are signed reads and all writes are shielded writes.

    hashtag
    Reading balance (signed read)

    Use the useSignedReadContract hook to query the user's balance. This sends a signed read under the hood, so the contract can verify msg.sender and the response is encrypted:

    The useSignedReadContract hook handles the full signed-read flow: signing the request with the user's key, sending it to eth_call, and decrypting the encrypted response. It returns a signedRead function that you call imperatively to perform the read.

    hashtag
    Transferring tokens (shielded write)

    Use the useShieldedWriteContract hook to send a shielded transfer. The calldata is encrypted before leaving the user's machine:

    When transfer() is called, the seismic-react library:

    1. Fetches the TEE public key from the node.

    2. Derives a shared secret via ECDH.

    3. Encrypts the calldata (including the recipient and amount) with AEAD.

    The user sees a standard wallet confirmation prompt. The privacy happens automatically under the hood.

    hashtag
    Decrypting events

    If your contract uses encrypted events (from the chapter), you can listen for them and decrypt the amounts off-chain:

    Decrypt events client-side using the ECDH shared secret and AES-GCM decryption. See the for the cryptographic primitives.

    hashtag
    Complete example

    Here is the full dashboard component that ties everything together:

    circle-info

    In a production application, you should not hardcode private keys. Use a wallet provider (such as RainbowKit, Privy, or AppKit) to manage keys securely. See the for integration details.

    hashtag
    Next steps

    You now have a complete SRC20 token: a private ERC20 with shielded balances, encrypted events, signed reads, compliance access control, and a React frontend.

    From here, you can:

    • Explore the client library docs -- The has detailed API references for seismic-viem and seismic-react, including all available hooks, wallet client methods, and precompile utilities.

    • Add wallet integration -- See the for step-by-step instructions on integrating RainbowKit, Privy, or AppKit with seismic-react.

    Events

    hashtag
    The Limitation

    Shielded types cannot be emitted directly in events. The following will not compile:

    This restriction exists because events are stored in transaction logs, which are publicly accessible on-chain. Emitting a shielded value in an event would defeat the purpose of shielding it in the first place -- the value would be visible to anyone inspecting the logs.

    This applies to all shielded types: suint, sint, sbool, saddress, and sbytes.

    hashtag
    The Workaround: Encrypted Events via Precompiles

    Although native encrypted events are not yet supported, you can achieve private event data today using the AES-GCM and ECDH precompiles. The approach is to encrypt the sensitive data before emitting it in a regular (unshielded) event, so that only the intended recipient can decrypt it.

    Here is the general flow:

    1. Generate a shared secret between the sender and the intended recipient using the at address 0x65.

    2. Derive an encryption key from the shared secret using the at address 0x68.

    3. Encrypt the event data

    hashtag
    Precompile Reference

    Precompile
    Address
    Purpose

    hashtag
    Code Example

    Below is a minimal private token showing how to emit encrypted transfer events. The contract holds a keypair; users register their public keys so the contract can encrypt event data that only the recipient can read.

    For a full implementation, see the tutorial.

    The built-in helpers ecdh(), hkdf(), and aes_gcm_encrypt() are compiler-provided globals — no imports needed. See the for details on each.

    hashtag
    Decryption (Off-Chain)

    The recipient reconstructs the shared secret off-chain using their own private key and the contract's public key (ECDH is symmetric). They then derive the same encryption key via HKDF and decrypt the event data using AES-GCM Decrypt.

    The client libraries provide built-in helpers for this:

    • — watch_src20_events_with_key and SRC20EventWatcher

    • — watchSRC20EventsWithKey() action

    hashtag
    What Not to Do

    Do not attempt to work around the restriction by casting a shielded value to its unshielded counterpart and then emitting it:

    Casting from a shielded type to an unshielded type makes the value visible in the execution trace. Emitting it in an event then permanently records it in publicly accessible logs.

    hashtag
    Future Improvements

    Native encrypted events are planned for a future version of Seismic. This will allow shielded types to be emitted directly in events without requiring manual encryption via precompiles. The compiler and runtime will handle encryption transparently, making private events as simple to use as regular events.

    hashtag
    Key Takeaway

    Privacy in events is achievable today -- it just requires explicit encryption via the aforementioned precompiles. Encrypt sensitive data before emitting it, and ensure only the intended recipient has the keys to decrypt. When native encrypted events ship, migrating will be straightforward: replace the manual encryption logic with direct shielded type emission.

    RainbowKit

    Set up RainbowKit with Seismic for wallet connection

    RainbowKit is the recommended wallet connection library for Seismic React apps. It provides a polished connect modal, chain switching, and account management out of the box.

    hashtag
    Prerequisites

    hashtag
    Step 1: Configure wagmi

    Create a wagmi config using RainbowKit's getDefaultConfig with Seismic chains:

    circle-exclamation

    Replace YOUR_WALLETCONNECT_PROJECT_ID with a project ID from .

    hashtag
    Step 2: Set Up Providers

    Wrap your app with the provider stack. The nesting order matters:

    circle-info

    ShieldedWalletProvider must be inside RainbowKitProvider so it can access the connected wallet from wagmi's context.

    hashtag
    Step 3: Add the Connect Button

    RainbowKit provides a pre-built connect button component:

    hashtag
    Step 4: Use Shielded Hooks

    Once the providers are in place, use seismic-react hooks to interact with shielded contracts:

    hashtag
    Next.js Setup

    Next.js App Router requires client components for providers. Create a separate providers file:

    Then wrap your root layout:

    circle-info

    The 'use client' directive is required because providers use React context, which is only available in client components.

    hashtag
    Local Development with Sanvil

    For local development, use the sanvil chain (Seismic's local development node) instead of seismicTestnet:

    hashtag
    Complete Example

    A full working setup in a single file:

    hashtag
    See Also

    • -- Comparison of wallet libraries

    • -- Provider reference and options

    • -- Access shielded wallet context

    Python — seismic-web3

    Python SDK for Seismic, built on web3.py

    Python SDK for Seismicarrow-up-right, built on web3.pyarrow-up-right. Requires Python 3.10+.

    Or with uvarrow-up-right:

    hashtag
    Quick Example

    hashtag
    Documentation Navigation

    hashtag
    Getting Started

    Section
    Description

    hashtag
    Guides

    Section
    Description

    hashtag
    API Reference

    Section
    Description

    hashtag
    Advanced Features

    Section
    Description

    hashtag
    Quick Links

    hashtag
    By Task

    • Send a shielded transaction →

    • Execute a signed read →

    • Work with SRC20 tokens →

    hashtag
    By Component

    • Client factories → ,

    • Contract wrappers → ,

    • Encryption → ,

    hashtag
    Features

    • 🔒 Shielded Transactions - Encrypt calldata with TEE public key

    • 📝 Signed Reads - Prove identity in eth_call

    • 🪙 SRC20 Support - Built-in support for private tokens

    hashtag
    Architecture

    The SDK extends web3.py with a custom w3.seismic namespace:

    hashtag
    Next Steps

    1. - Get connected to Seismic

    2. - Step-by-step guide

    3. - Deep dive into all types and functions

    Client

    Creating sync and async Seismic clients

    The Seismic Python SDK provides two client types for interacting with Seismic nodes:

    • Wallet client — Full capabilities (shielded writes, signed reads, deposits). Requires a private key.

    • Public client — Read-only (transparent reads, TEE public key, deposit queries). No private key needed.

    Both clients are available in sync and async variants.

    hashtag
    Quick Start

    hashtag
    Installation

    Or with :

    hashtag
    Wallet Client (Sync)

    This gives you a standard Web3 instance with an extra w3.seismic namespace. Everything from web3.py works as usual — w3.eth.get_block(...), w3.eth.wait_for_transaction_receipt(...), etc.

    hashtag
    Wallet Client (Async)

    Every method on w3.seismic and on ShieldedContract is await-able when using the async client.

    hashtag
    Public Client

    The public client's w3.seismic namespace has limited methods: get_tee_public_key(), get_deposit_root(), get_deposit_count(), and contract() (with .tread only).

    hashtag
    Client Factory Functions

    hashtag
    Wallet Clients (Require Private Key)

    Function
    Type
    Description

    hashtag
    Public Clients (No Private Key)

    Function
    Type
    Description

    hashtag
    Chain-Based Creation

    The recommended approach is to use chain configuration objects:

    See for more details.

    hashtag
    Encryption

    The wallet client automatically handles encryption setup:

    1. Fetches the network's TEE public key

    2. Derives a shared key via

    3. Uses this key to encrypt calldata for all shielded transactions and signed reads

    You don't need to manage this manually, but the encryption state is accessible at w3.seismic.encryption if needed.

    hashtag
    Encryption Components

    Component
    Description

    hashtag
    Client Capabilities

    hashtag
    Wallet Client (w3.seismic)

    • - Send shielded transactions

    • - Debug shielded transactions

    • - Execute signed reads

    hashtag
    Public Client (w3.seismic)

    • - Get TEE public key

    • - Query deposit merkle root

    • - Query deposit count

    hashtag
    See Also

    • - Working with shielded and public contracts

    • - Detailed w3.seismic namespace documentation

    • - Chain configs and constants

    Privy

    Set up Privy with Seismic for email and social login

    Privy provides email, social, and embedded wallet authentication. This guide shows how to integrate Privy with Seismic React for apps that need flexible onboarding beyond browser extension wallets.

    hashtag
    Prerequisites

    circle-info

    You need a Privy App ID from the .

    hashtag
    Step 1: Configure Privy with Seismic Chain

    Define the Seismic chain configuration for Privy:

    hashtag
    Step 2: Set Up wagmi Config

    Create a wagmi config using Privy's wagmi integration:

    hashtag
    Step 3: Set Up Providers

    Nest the providers in the correct order -- Privy wraps wagmi, and ShieldedWalletProvider goes inside:

    circle-info

    Privy's embedded wallets work seamlessly with Seismic -- users don't need a browser extension. Privy creates a wallet automatically when users sign in with email or social accounts.

    hashtag
    Step 4: Add Login Button

    Use Privy's hooks to trigger the login modal:

    hashtag
    Step 5: Use Shielded Hooks

    Once authenticated, use seismic-react hooks as normal:

    hashtag
    Complete Example

    hashtag
    See Also

    • -- Comparison of wallet libraries

    • -- Provider reference and options

    • -- Access shielded wallet context

    AsyncPublicContract

    Async contract wrapper with transparent read-only access

    Asynchronous contract wrapper for read-only transparent access to Seismic contracts.

    hashtag
    Overview

    AsyncPublicContract is the async version of PublicContract, providing non-blocking read-only access to contract state. It exposes only the .tread namespace for standard async eth_call operations. All methods return coroutines that must be awaited. Use this class in async applications when you only need to read public contract data.

    hashtag
    Definition

    hashtag
    Constructor Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Namespace

    hashtag
    .tread - Transparent Read

    Executes standard async eth_call with unencrypted calldata. This is the only namespace available on AsyncPublicContract.

    Returns: Coroutine[Any] (ABI-decoded Python value)

    Optional Parameters: None (pass positional arguments only)

    hashtag
    Examples

    hashtag
    Basic Read Operations

    hashtag
    Concurrent Reads

    hashtag
    Single and Multiple Returns

    hashtag
    Array Results

    hashtag
    Error Handling

    hashtag
    Notes

    • All methods return coroutines: Must use await with every .tread call

    • Read-only: No write operations available

    • No encryption required: Does not use EncryptionState

    hashtag
    See Also

    • - Synchronous version of this class

    • - Full async contract wrapper with write operations

    • - Create async client without private key

    SANVIL

    Pre-configured local development network

    Pre-defined ChainConfig for the local Sanvil (Seismic Anvil) development network.

    hashtag
    Overview

    SANVIL is a ready-to-use chain configuration pointing to a local Seismic node running on 127.0.0.1:8545. It's designed for local development and testing workflows.

    hashtag
    Definition

    hashtag
    Configuration

    Property
    Value

    hashtag
    Usage

    hashtag
    Import and Access Properties

    hashtag
    Create Wallet Client (Sync)

    hashtag
    Create Wallet Client (Async)

    hashtag
    Create Public Client

    hashtag
    Examples

    hashtag
    Local Development Workflow

    hashtag
    Testing with Multiple Accounts

    hashtag
    Checking Local Node Connection

    hashtag
    Integration Testing

    hashtag
    Prerequisites

    To use SANVIL, you need a local Seismic node running. This is typically:

    1. Sanvil (Seismic Anvil) - Local development node

    2. Running on 127.0.0.1:8545

    3. Compatible with Seismic protocol extensions

    hashtag
    Notes

    • Chain ID 31337 matches the default Anvil chain ID

    • Suitable for local development and testing only

    • Fast block times and instant finality

    hashtag
    Chain ID Constant

    The chain ID is also available as a standalone constant:

    hashtag
    Well-Known Test Accounts

    When running Sanvil/Anvil, the following test accounts are pre-funded:

    These are unsafe for production and should only be used for local testing.

    hashtag
    See Also

    • - Chain configuration dataclass

    • - Public testnet configuration

    • - Direct client creation

    seismic-viem

    TypeScript client library for Seismic, composing with viem to add shielded transactions, encrypted calldata, and signed reads.

    Low-level TypeScript SDK for , built on 2.x. Provides shielded public and wallet clients, encrypted contract interactions, signed reads, chain configs, and precompile access. This is the foundation that builds upon.

    hashtag
    Quick Example

    Shielded Public Client

    Read-only client with TEE key access and precompile support

    Read-only client for interacting with a Seismic node. Extends viem's PublicClient with TEE public key retrieval, precompile access, block explorer URL helpers, SRC20 event watching, and deposit contract queries. Does not require a private key.

    hashtag
    Import

    Encryption

    ECDH key exchange, AES-GCM calldata encryption, and the Seismic encryption pipeline

    seismic-viem handles encryption transparently when you call writeContract() or signedCall() on a ShieldedWalletClient. The client derives a shared AES key during construction and encrypts calldata on every shielded operation automatically. This page documents the underlying encryption pipeline for advanced use cases where you need direct access to the cryptographic primitives.

    hashtag
    Encryption Flow

    Shielded Wallet Provider

    React context provider for shielded wallet and public client

    React context component that wraps wagmi's connector client to provide ShieldedPublicClient and ShieldedWalletClient from . This is the core provider that all Seismic React hooks depend on.

    hashtag
    Import

    Basic dApp

    Complete minimal dApp with shielded writes and signed reads

    This example builds a complete minimal dApp that connects a wallet, sends a shielded write transaction, and performs a signed read -- all using seismic-react hooks.

    hashtag
    What You'll Build

    1. Provider setup with RainbowKit + Seismic

    useShieldedWrite

    Send encrypted write transactions to shielded contracts

    circle-info

    This hook wraps useShieldedWriteContract from seismic-react. The actual export name is useShieldedWriteContract.

    Hook for sending shielded write transactions. Encrypts calldata before submission so transaction data is not visible on-chain.

    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/rainbowkit
    npm install seismic-react
    yarn add seismic-react
    pnpm add seismic-react
    bun add seismic-react
    npm install react wagmi viem seismic-viem @tanstack/react-query
    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 { 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-viem
    npm 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  # 5124
    event ConfidentialEvent(suint256 confidentialData); // Compilation error
    npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query seismic-react seismic-viem
    pip install seismic-web3
    uv add seismic-web3
    import 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-viem

    .tread

    Transparent Read

    Plaintext

    Zero address

    Standard viem read

    .twrite

    Transparent Write

    Plaintext

    Signer's address

    Standard viem write

    .dwrite

    Debug Write

    Encrypted

    Signer's address

    Returns plaintext + encrypted tx + hash

    Encryption

    Name of the view/pure function

    args

    array

    No

    Function arguments

    nonce

    number

    No

    Override the nonce

    Encryption
    Shielded Wallet Client

    Function to call

    args

    array

    No

    Function arguments

    gas

    bigint

    No

    Gas limit

    gasPrice

    bigint

    No

    Gas price

    value

    bigint

    No

    ETH value to send

    Override the recent block hash

    expiresAtBlock

    bigint

    Calculated

    Override the expiry block directly

    Encryption
    Shielded Wallet Client
    Privy Guide
    make_seismic_testnet(n)
    create_wallet_client
    PrivateKey

    Decrypt the encryptedAmount from the event log using AES-GCM Decrypt.

    from address

    Everyone

    Indexed parameter, stored in public log topics

    to address

    Everyone

    Indexed parameter, stored in public log topics

    Transfer amount

    Recipient only

    Encrypted to recipient's public key

    A transfer happened

    Everyone

    The event emission itself is visible

    seismic-viem precompiles documentation
    No broadcast disclosure. When a compliance officer reads a balance, only they learn the value. It is not published on-chain or visible to other observers.
  • Roles are on-chain and auditable. The AccessControl roles are standard Solidity state. Anyone can verify who holds what role by reading the contract. The role assignments themselves are transparent -- only the shielded data they gate is private.

  • Freeze is public. The frozen mapping uses bool, not sbool. This is a deliberate design choice: if an account is frozen, that fact should be publicly verifiable so that counterparties know not to send tokens to it.

  • Regular user

    View their own balance

    getBalance(myAddress) via signed read

    Auditor (AUDITOR_ROLE)

    View any account's balance (read-only)

    auditBalanceOf(account) via signed read

    Compliance officer (COMPLIANCE_ROLE)

    View any balance, freeze/unfreeze accounts

    complianceBalanceOf(account) via signed read; complianceFreeze(account) via transaction

    Wraps everything in a Seismic transaction (type
    0x4A
    ).
  • Broadcasts the encrypted transaction.

  • Deploy to testnet -- See the deploy section for deploying your SRC20 to a live Seismic network.

  • Extend the contract -- Consider adding features like burn functions or governance mechanisms.

  • Encrypted Events
    seismic-viem precompiles documentation
    Wallet Guides
    Client Libraries section
    Wallet Guides
    using the
    at address 0x66.
  • Emit a regular event containing the encrypted bytes. Since the event parameter is bytes (not a shielded type), this compiles and works normally.

  • Recipient decrypts the data using the AES-GCM Decrypt precompile at address 0x67, either off-chain or on-chain if needed.

  • ECDH

    0x65

    Shared secret generation

    AES-GCM Encrypt

    0x66

    Encrypt data with AES-GCM

    AES-GCM Decrypt

    0x67

    Decrypt data with AES-GCM

    HKDF

    0x68

    Key derivation from shared secret

    ECDH precompile
    HKDF precompile
    SRC20: Private Token
    Precompiles reference
    Python
    TypeScript (viem)
    AES-GCM Encrypt precompile
    Privy Guide -- Email/social login alternative
  • AppKit Guide -- WalletConnect modal alternative

  • WalletConnect Cloudarrow-up-right
    Wallet Guides Overview
    ShieldedWalletProvider
    useShieldedWallet
    Use async client → Client Documentation
  • Configure custom chain → Chains Documentation

  • Transaction types → UnsignedSeismicTx, SeismicElements

    ⚡ Async/Await - Full async support with AsyncWeb3
  • 🔑 EIP-712 - Structured typed data signing

  • 🛠️ Precompiles - Access Mercury EVM cryptographic precompiles

  • 🌐 Web3.py Compatible - Standard Web3 instance with Seismic extensions

  • Client

    Create sync/async wallet and public clients

    Chains

    Chain configuration (SEISMIC_TESTNET, SANVIL)

    Contract

    Interact with shielded and public contracts

    Guides

    Step-by-step tutorials and runnable examples

    API Reference

    Complete API documentation for all types and functions

    Typesarrow-up-right

    Primitive types (Bytes32, PrivateKey, etc.)

    Transaction Typesarrow-up-right

    Seismic transaction dataclasses

    EIP-712arrow-up-right

    EIP-712 typed data signing functions

    Namespaces

    w3.seismic namespace methods

    Precompiles

    Privacy-preserving cryptographic functions

    SRC20

    SRC20 token standard support

    ABIs

    Built-in contract ABIs and helpers

    Shielded Write Guide
    Signed Reads Guide
    SRC20 Documentation
    create_wallet_client
    create_public_client
    ShieldedContract
    PublicContract
    EncryptionState
    Precompiles
    Install and setup a client
    Send your first shielded transaction
    Explore the API reference
    get_tee_public_key() - Get TEE public key
  • deposit() - Deposit ETH/tokens

  • get_deposit_root() - Query deposit merkle root

  • get_deposit_count() - Query deposit count

  • contract() - Create contract wrappers

  • contract() - Create contract wrappers (.tread only)

    Shielded Write Guide - Step-by-step shielded transaction guide

    create_wallet_client

    Sync

    Create sync wallet client from RPC URL

    create_async_wallet_client

    Async

    Create async wallet client from RPC URL

    create_public_client

    Sync

    Create sync public client from RPC URL

    create_async_public_client

    Async

    Create async public client from RPC URL

    EncryptionState

    Holds AES key and encryption keypair

    get_encryption

    Derives encryption state from TEE public key

    uvarrow-up-right
    Chains Configuration
    AES-GCMarrow-up-right
    ECDHarrow-up-right
    send_shielded_transaction()
    debug_send_shielded_transaction()
    signed_call()
    get_tee_public_key()
    get_deposit_root()
    get_deposit_count()
    Contract Instances
    Namespaces
    Chains Configuration
    RainbowKit Guide -- Traditional wallet modal alternative
  • AppKit Guide -- WalletConnect modal alternative

  • Privy Dashboardarrow-up-right
    Wallet Guides Overview
    ShieldedWalletProvider
    useShieldedWallet
    or private keys
  • No authentication: Standard unsigned async eth_call operations

  • Gas not consumed: eth_call is free (doesn't create transactions)

  • Contract Instance Guide - Overview of contract interaction patterns

    w3

    AsyncWeb3

    Yes

    Asynchronous AsyncWeb3 instance connected to RPC endpoint

    address

    ChecksumAddress

    Yes

    Contract address (checksummed Ethereum address)

    abi

    list[dict[str, Any]]

    Yes

    PublicContract
    AsyncShieldedContract
    create_async_public_client

    Contract ABI (list of function entries)

    No real value on chain
  • WebSocket endpoint available for subscriptions

  • Typically used with well-known test private keys

  • PrivateKey - Private key type

    chain_id

    31337

    rpc_url

    "http://127.0.0.1:8545"

    ws_url

    "ws://127.0.0.1:8545"

    name

    "Sanvil (local)"

    ChainConfig
    SEISMIC_TESTNET
    create_wallet_client
    // 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-query
    import { 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-web3
    uv add seismic-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)
    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 == 1
    from 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...
    hashtag
    Architecture

    hashtag
    Documentation Navigation

    hashtag
    Getting Started

    Section
    Description

    Install seismic-viem, configure viem peer dependency

    Pre-configured chain definitions for Seismic networks

    hashtag
    Client Reference

    Section
    Description

    Shielded Public Client

    Read-only client with TEE key exchange and precompile access

    Shielded Wallet Client

    Full-featured client with encryption pipeline and transaction signing

    hashtag
    Contract Interaction

    Section
    Description

    Contract Instance

    getShieldedContract for .read, .write, .tread, .twrite, .dwrite

    Shielded Writes

    Encrypt calldata and send Seismic transactions (type 0x4A)

    Signed Reads

    Prove caller identity in eth_call via seismic_call

    hashtag
    Infrastructure

    Section
    Description

    seismicTestnet, sanvil, createSeismicDevnet chain configs

    Encryption

    ECDH key exchange, AES-GCM calldata encryption

    Precompiles

    RNG, ECDH, AES-GCM, HKDF, secp256k1 signing precompile bindings

    hashtag
    Features

    • Shielded Transactions -- Encrypt calldata with TEE public key via AES-GCM before sending

    • Signed Reads -- Prove identity in eth_call with signed read requests

    • Two Client Types -- createShieldedPublicClient (read-only) and createShieldedWalletClient (full capabilities)

    • Contract Abstraction -- getShieldedContract provides .read, .write, .tread, .twrite, and .dwrite methods

    • Automatic Encryption Pipeline -- Calldata encryption handled transparently in the client layer

    • Type 0x4A Transactions -- Native support for Seismic transaction type with custom chain formatters

    • Precompile Bindings -- TypeScript wrappers for all Seismic precompiles (RNG, ECDH, AES-GCM, HKDF, secp256k1)

    • Chain Configs -- Pre-configured chain definitions for testnet, local dev, and custom devnets

    • Full viem Compatibility -- All standard viem client methods work unchanged

    hashtag
    Quick Links

    hashtag
    By Task

    • Create a wallet client -> Shielded Wallet Client

    • Create a read-only client -> Shielded Public Client

    • Interact with a contract -> Contract Instance

    • Send an encrypted transaction -> Shielded Writes

    • Read with identity proof -> Signed Reads

    • Connect to a network ->

    • Understand calldata encryption -> Encryption

    • Install the package ->

    hashtag
    By Component

    • Client types -> Shielded Public Client, Shielded Wallet Client

    • Contract interaction -> Contract Instance, Shielded Writes, Signed Reads

    • Chains -> Chains (seismicTestnet, sanvil, createSeismicDevnet)

    • Encryption -> Encryption (getEncryption, AesGcmCrypto)

    • Precompiles -> Precompiles (rng, ecdh, aesGcmEncrypt, hkdf, secp256k1Sig)

    hashtag
    Comparison with seismic-react

    Aspect
    seismic-viem
    seismic-react

    Level

    Low-level SDK

    React hooks layer

    Framework

    Framework-agnostic TypeScript

    React 18+

    Foundation

    Built directly on viem 2.x

    Built on seismic-viem + wagmi

    Client creation

    Manual (createShieldedWalletClient)

    circle-info

    If you are building a React application, consider using seismic-react which provides React hooks on top of seismic-viem. For Node.js scripts, server-side code, or non-React frontends, use seismic-viem directly.

    hashtag
    Next Steps

    1. Install seismic-viem -- Add the package to your project

    2. Configure a chain -- Connect to testnet or local dev

    3. Create a shielded wallet client -- Connect with full capabilities

    4. Understand encryption -- Learn how calldata encryption works

    5. Interact with contracts -- Use getShieldedContract for reads and writes

    6. Explore precompiles -- Access on-chain RNG, ECDH, and AES-GCM

    Seismicarrow-up-right
    viemarrow-up-right
    seismic-react
    hashtag
    Constructor

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    chain

    Chain

    Yes

    Chain configuration (e.g., seismicTestnet)

    transport

    Transport

    Yes

    viem transport (e.g., http())

    hashtag
    Return Type

    ShieldedPublicClient

    A viem PublicClient extended with ShieldedPublicActions, DepositContractPublicActions, and SRC20PublicActions.

    hashtag
    Usage

    hashtag
    Actions

    hashtag
    Shielded Public Actions

    Action
    Return Type
    Description

    getTeePublicKey()

    Promise<Hex>

    Fetch the TEE's secp256k1 public key via seismic_getTeePublicKey RPC

    getStorageAt()

    --

    Throws error (not supported on Seismic)

    publicRequest()

    Promise<any>

    Raw RPC request to the node

    explorerUrl(opts)

    string | null

    hashtag
    Precompile Actions

    Action
    Return Type
    Description

    rng(params)

    Promise<bigint>

    Generate random numbers via the RNG precompile

    ecdh(params)

    Promise<Hex>

    ECDH key exchange via precompile

    aesGcmEncryption(params)

    Promise<Hex>

    AES-GCM encrypt via precompile

    aesGcmDecryption(params)

    Promise<string>

    hashtag
    SRC20 Actions

    Action
    Return Type
    Description

    watchSRC20Events()

    --

    Watch for SRC20 token events

    watchSRC20EventsWithKey()

    --

    Watch for SRC20 events with a decryption key

    hashtag
    Deposit Contract Actions

    Action
    Return Type
    Description

    getDepositRoot()

    Promise<Hex>

    Query the deposit merkle root

    getDepositCount()

    Promise<bigint>

    Query the deposit count

    hashtag
    Standard viem Public Actions

    All standard viem PublicActions are available directly on the client:

    • getBlockNumber(), getBlock(), getTransaction()

    • getBalance(), getCode(), call()

    • estimateGas(), waitForTransactionReceipt()

    • All other viem public client methods

    hashtag
    getStorageAt Disabled

    circle-exclamation

    Seismic does not support eth_getStorageAt. Calling publicClient.getStorageAt() will throw an error. This is by design -- storage slots on Seismic may contain shielded data and cannot be read directly via RPC.

    hashtag
    Examples

    hashtag
    Fetching the TEE Public Key

    hashtag
    Using Precompiles

    hashtag
    Explorer URL Helpers

    hashtag
    See Also

    • Shielded Wallet Client -- Full-featured client with encryption and shielded writes

    • Chains -- Chain configurations for Seismic networks

    • Precompiles -- Precompile details and parameters

    • -- Encryption utilities and getEncryption()

    Every shielded transaction follows this pipeline:
    circle-info

    You don't need to call getEncryption() manually -- createShieldedWalletClient handles key exchange automatically. The functions on this page are for advanced use cases like offline encryption, custom key management, or debugging.

    hashtag
    getEncryption(networkPk, clientSk?)

    Standalone function that performs ECDH key exchange and derives an AES-256 key from a TEE public key and an optional client secret key.

    hashtag
    Import

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    networkPk

    string

    Yes

    TEE's secp256k1 public key (fetched via client.getTeePublicKey())

    clientSk

    Hex

    No

    Client's encryption private key. If omitted, generates a random keypair

    hashtag
    Returns

    Property
    Type
    Description

    aesKey

    Hex

    Derived AES-256 key for encrypting calldata

    encryptionPrivateKey

    Hex

    Client's ephemeral secp256k1 private key

    encryptionPublicKey

    Hex

    Client's compressed secp256k1 public key

    hashtag
    Example

    hashtag
    Encryption Actions

    The ShieldedWalletClient exposes encryption operations as client methods. These use the AES key that was derived during client construction.

    Action
    Return Type
    Description

    getEncryption()

    Hex

    Returns the AES-256 key used for shielded operations

    getEncryptionPublicKey()

    Hex

    Returns the client's compressed secp256k1 encryption public key

    encrypt(plaintext, metadata)

    Promise<Hex>

    AES-GCM encrypt plaintext calldata with metadata as AAD

    decrypt(ciphertext, metadata)

    Promise<Hex>

    hashtag
    Example

    hashtag
    SeismicTxExtras

    Every Seismic transaction includes additional fields that carry encryption metadata alongside the standard Ethereum transaction fields.

    Field
    Type
    Description

    encryptionPubkey

    Hex

    Client's compressed secp256k1 public key

    encryptionNonce

    Hex

    Random 12-byte AES-GCM nonce

    messageVersion

    number

    0 for normal transactions, 2 for EIP-712 signed

    recentBlockHash

    Hex

    Recent block hash used for replay protection

    These fields are populated automatically by writeContract() and signedCall(). The node uses the encryptionPubkey to reconstruct the shared secret and decrypt the calldata inside the TEE.

    hashtag
    AES-GCM with AEAD

    Seismic uses AES-GCM with Additional Authenticated Data (AEAD) to bind encrypted calldata to the transaction context. The TxSeismicMetadata is encoded and passed as AAD during encryption, which means:

    • The ciphertext can only be decrypted when presented alongside the correct metadata.

    • Any modification to the transaction's metadata (account, nonce, recipient, value, or SeismicTxExtras) causes decryption to fail.

    • This prevents replay attacks and ensures calldata cannot be reattached to a different transaction context.

    TxSeismicMetadata includes the following fields as AAD:

    • account -- sender address

    • nonce -- transaction nonce

    • to -- recipient address

    • value -- ETH value

    • seismicElements -- the encryptionPubkey, encryptionNonce, messageVersion, recentBlockHash, expiresAtBlock, and signedRead fields

    hashtag
    Crypto Dependencies

    seismic-viem uses the following @noble libraries for client-side cryptography. These are bundled as direct dependencies and do not need to be installed separately.

    Package
    Purpose

    @noble/curves

    secp256k1 keypair generation and ECDH

    @noble/ciphers

    AES-GCM encryption and decryption

    @noble/hashes

    SHA-256, HKDF, and other hash functions

    circle-info

    All cryptographic operations happen client-side before the transaction is submitted. The plaintext calldata never leaves the client -- only the AES-GCM ciphertext is sent to the node, where it is decrypted inside the TEE for execution.

    hashtag
    See Also

    • Shielded Wallet Client -- Client that performs encryption automatically

    • Shielded Writes -- How encrypted transactions are built and sent

    • Signed Reads -- Authenticated reads that prove caller identity

    • -- On-chain cryptographic operations via precompiled contracts

    • -- Bundled crypto dependencies

    hashtag
    Props
    Prop
    Type
    Required
    Description

    children

    React.ReactNode

    Yes

    Child components that can access shielded wallet context

    config

    Config (from wagmi)

    Yes

    wagmi configuration object

    options

    object

    No

    hashtag
    OnAddressChangeParams

    hashtag
    Context Value

    The provider exposes a WalletClientContextType to all descendant components via React context:

    Field
    Description

    publicClient

    Shielded public client for encrypted reads. null until initialization completes.

    walletClient

    Shielded wallet client for encrypted writes. null until a wallet is connected.

    address

    Connected wallet address. null when no wallet is connected.

    error

    Error message if client creation fails. null when healthy.

    loaded

    true once the provider has finished its initial setup, regardless of whether a wallet is connected.

    circle-info

    Access these values through the useShieldedWallet hook rather than consuming the context directly.

    hashtag
    Setup

    hashtag
    Basic Setup with RainbowKit

    hashtag
    Next.js Setup

    For Next.js, create the provider in a client component:

    Then wrap your layout:

    hashtag
    Provider Nesting Order

    The providers must be nested in this order:

    circle-exclamation

    ShieldedWalletProvider must be inside WagmiProvider because it reads the connected wallet from wagmi's context. Placing it outside will cause a runtime error.

    hashtag
    Lifecycle

    The provider follows this initialization sequence:

    1. Public client creation -- On mount, creates a ShieldedPublicClient using the chain and transport from the wagmi config (or from options.publicChain / options.publicTransport if provided). This client is available even when no wallet is connected.

    2. Wallet client creation -- When a wallet connects through wagmi, the provider creates a ShieldedWalletClient from the connector's client. This enables encrypted write transactions.

    3. onAddressChange callback -- If provided, called after both clients are ready with the new address. Use this to load user-specific data or initialize contract state.

    4. Reconnection handling -- When the user switches accounts or reconnects, the provider recreates the wallet client and fires onAddressChange again.

    hashtag
    Usage with onAddressChange

    The onAddressChange callback is useful for initializing state when a wallet connects:

    hashtag
    Error Handling

    The provider captures errors during client creation and exposes them through the context:

    Error State
    Cause
    Resolution

    Public client creation fails

    Invalid chain config or transport

    Check wagmi config and chain definitions

    Wallet client creation fails

    Connector incompatibility or network mismatch

    Ensure the wallet supports the target chain

    onAddressChange throws

    Error in your callback

    The error is caught and set on context.error

    Access the error state through useShieldedWallet:

    hashtag
    See Also

    • Installation -- Package setup and peer dependencies

    • useShieldedWallet -- Hook to consume the provider context

    • Hooks Overview -- All available hooks

    • -- Wallet UI integration

    • -- Embedded wallet setup

    seismic-viemarrow-up-right

    Wallet connection via RainbowKit's ConnectButton

  • Shielded write to a contract (encrypted calldata)

  • Signed read from a contract (authenticated query)

  • hashtag
    Prerequisites

    • Node.js 18+

    • A WalletConnectarrow-up-right project ID

    Install dependencies:

    hashtag
    Step 1: wagmi Config

    Create the wagmi configuration with the Seismic testnet chain:

    circle-exclamation

    Replace YOUR_WALLETCONNECT_PROJECT_ID with your actual project ID from WalletConnect Cloudarrow-up-right.

    hashtag
    Step 2: Provider Wrapper

    Wrap your app with the required providers. ShieldedWalletProvider must be nested inside the wagmi and React Query providers:

    hashtag
    Step 3: Contract Interaction Component

    Create a component that uses useShieldedWriteContract and useSignedReadContract to interact with a shielded counter contract:

    circle-info

    Replace CONTRACT_ADDRESS with the address of a deployed shielded contract on Seismic testnet. The ABI above is for a simple counter -- adapt it to match your contract.

    hashtag
    Step 4: App Component

    Combine the wallet connection button with the counter component. The counter only renders once the shielded wallet is ready:

    hashtag
    What's Happening

    1. RainbowKit handles wallet connection and chain switching

    2. ShieldedWalletProvider automatically creates shielded clients when a wallet connects, performing the ECDH key exchange with the TEE

    3. useShieldedWriteContract encrypts calldata before sending the transaction, ensuring on-chain privacy

    4. useSignedReadContract attaches a signature proving caller identity, allowing the contract to return private data only to authorized readers

    hashtag
    Next Steps

    • Hooks Reference - Full API for all hooks

    • Wallet Guides - Use AppKit or Privy instead of RainbowKit

    • Chains - Configure Seismic testnet or local Sanvil

    hashtag
    See Also

    • ShieldedWalletProvider - Provider configuration options

    • useShieldedWriteContract - Shielded write hook API

    • useSignedReadContract - Signed read hook API

    • - Full dependency setup

    hashtag
    Config
    Parameter
    Type
    Required
    Description

    address

    Hex

    Yes

    Contract address

    abi

    Abi

    Yes

    Contract ABI

    functionName

    string

    Yes

    hashtag
    Return Type

    Property
    Type
    Description

    writeContract

    () => Promise<Hex>

    Function to execute the shielded write

    write

    () => Promise<Hex>

    Alias for writeContract

    isLoading

    boolean

    Whether a write is in progress

    error

    Error | null

    Error from the most recent write


    hashtag
    Usage

    hashtag
    Shielded token transfer

    hashtag
    Transaction hash tracking

    The hash field updates after each successful write. Use it to link to a block explorer or track confirmation.

    hashtag
    Loading and error state handling

    hashtag
    With gas override

    Pass gas or gasPrice to override the automatic estimates:

    circle-info

    Like useSignedReadContract, the write function is imperative -- you call writeContract() or write() explicitly. The hook does not auto-execute on mount or when arguments change.

    hashtag
    See Also

    • useSignedReadContract -- Perform signed, encrypted read calls

    • useShieldedContract -- Contract instance with both read and write methods

    • useShieldedWallet -- Access the underlying wallet client

    • -- Context provider required by this hook

    • -- Summary of all hooks

    get_encryption

    Derive encryption state from TEE public key using ECDH

    Derive encryption state from a TEE public key using ECDH key exchange.

    hashtag
    Overview

    get_encryption() performs ECDH key exchange between a client private key and the TEE's public key to derive a shared AES-GCM key. It returns an EncryptionState object containing the AES key, client public key, and client private key.

    This is a pure computation function with no I/O - it works in both sync and async contexts.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Type
    Description

    hashtag
    Examples

    hashtag
    Basic Usage

    hashtag
    With Custom Client Key

    hashtag
    In Client Factory

    hashtag
    Random Ephemeral Key

    hashtag
    Verify Key Derivation

    hashtag
    How It Works

    The function performs three steps:

    1. Generate client key if needed

    2. Derive AES key via ECDH +

      This performs:

      • ECDH: Compute shared secret from client_sk

    hashtag
    ECDH Key Exchange

    The ECDH key exchange works as follows:

    The client sends client_pk in the transaction's SeismicElements, allowing the TEE to derive the same AES key and decrypt the calldata.

    hashtag
    Random vs Deterministic Keys

    Key Type
    Pros
    Cons

    The SDK defaults to random ephemeral keys for simplicity. Use deterministic keys only if you need to recreate the same encryption state across sessions.

    hashtag
    Notes

    • Pure computation - no RPC calls or I/O

    • Works in both sync and async contexts

    • If client_sk is None, generates a cryptographically secure random key via os.urandom(32)

    hashtag
    Security Considerations

    • Random key generation - Uses os.urandom() which is cryptographically secure on all platforms

    • Forward secrecy - Each encryption session can use a different ephemeral key

    • Key storage - If using deterministic keys, store client_sk

    hashtag
    Common Patterns

    hashtag
    Ephemeral Session Keys

    hashtag
    Persistent Keys

    hashtag
    Rotate Keys

    hashtag
    See Also

    • - Returned encryption state class

    • - Sync client factory (calls get_encryption)

    • - Async client factory

    Namespaces

    Contract interaction namespaces for shielded and transparent operations

    Seismic contracts expose five namespaces for different types of operations. Each namespace provides a different combination of encryption, authentication, and transaction behavior.


    hashtag
    Overview

    When you instantiate a contract:

    You get access to five namespaces:


    hashtag
    Quick Comparison

    hashtag
    Encrypted vs Transparent

    Encrypted (.write, .read, .dwrite):

    • Calldata is encrypted using AES-GCM

    • Only you and the TEE can see plaintext

    • Requires ECDH key exchange with node

    Transparent (.twrite, .tread):

    • Calldata is visible on-chain

    • Standard Ethereum transactions/calls

    • No encryption or security metadata

    hashtag
    Write vs Read

    Write (.write, .twrite, .dwrite):

    • Broadcasts a transaction

    • Consumes gas

    • Modifies contract state

    • Returns transaction hash

    Read (.read, .tread):

    • Executes an eth_call

    • Free (no gas cost)

    • Does not modify state


    hashtag
    Usage Patterns

    hashtag
    Encrypted Write (.write)

    When to use:

    • Amounts, addresses, or arguments should be private

    • Privacy compliance required

    • Trading, voting, auctions, confidential transfers

    hashtag
    Signed Read (.read)

    When to use:

    • Contract checks msg.sender for access control

    • Caller-specific data (e.g., "my balance")

    • Privacy required for queries

    hashtag
    Transparent Write (.twrite)

    When to use:

    • Data is public anyway

    • Lower gas cost matters

    • No privacy requirements

    • Standard Ethereum behavior

    hashtag
    Transparent Read (.tread)

    When to use:

    • Function is public (doesn't check msg.sender)

    • No authentication needed

    • Data is public

    hashtag
    Debug Write (.dwrite)

    When to use:

    • Development and testing

    • Debugging encryption issues

    • Verifying calldata encoding

    • Auditing transaction parameters


    hashtag
    Common Pitfalls

    hashtag
    Using .tread for Access-Controlled Functions

    Problem:

    Solution:

    hashtag
    Using .write for Public Data

    Unnecessary overhead:

    Better:


    hashtag
    See Also

    • — Contract wrapper reference

    • — Full encryption workflow

    • — Authentication and privacy for reads

    Best Practices

    These are guidelines for writing secure Seismic contracts. Following them will help you avoid the most common privacy mistakes.

    hashtag
    Always Use Seismic Transactions for Shielded Parameters

    Any function that accepts shielded types as parameters should be called using a Seismic transaction (type 0x4A), which encrypts the calldata. If calldata is not encrypted, the shielded parameter values are visible in plaintext in the transaction's input data, defeating the purpose of using shielded types. This applies to all function calls that pass shielded values as parameters.

    Your client library (e.g., seismic-viem) handles Seismic transaction construction automatically when you use shielded write functions.

    hashtag
    Be Mindful of Gas-Based Information Leakage

    Gas consumption is publicly visible. Any operation whose gas cost depends on a shielded value is a potential leak. The main offenders are:

    • Conditional branches on shielded booleans (different branches use different gas).

    • Loops with shielded bounds (iteration count is visible via gas).

    • Exponentiation with shielded exponents (gas scales with exponent value).

    Use constant-time patterns: fixed-size loops, branchless logic, and avoid shielded exponents.

    CLOAD and CSTORE themselves have constant gas cost by design -- the risk comes from higher-level patterns.

    hashtag
    Use Casting Carefully

    Every cast between shielded and unshielded types is a potential information leak. See for details.

    • Unshielded to shielded: The input value is visible in the trace.

    • Shielded to unshielded: The output value is visible in the trace.

    Minimize casts. During security review, identify every cast and confirm the exposure is intentional.

    hashtag
    Review Compiler Warnings

    The Seismic Solidity compiler generates a ton of warnings specific to shielded types. This is designed to be annoying because of how easy it is to make a mistake. Please review each warning carefully. These warnings flag potential privacy issues such as:

    • Shielded values used in contexts that may leak information.

    • Casts that expose confidential data.

    • Patterns known to be risky.

    Do not ignore these warnings. Treat them as seriously as you would treat security audit findings.

    hashtag
    Test with sforge test

    Use sforge test (the Seismic fork of Foundry's forge test) to run your test suite. It supports shielded types natively and can catch issues specific to confidential computation that standard forge test would miss.

    Write tests that specifically verify:

    • Shielded values remain confidential through the expected code paths.

    • Access control prevents unauthorized reads of unshielded data.

    hashtag
    Keep Shielded Data in the Shielded Domain

    The longer a value stays shielded, the more private it is. Avoid unnecessary round-trips between shielded and unshielded types. Perform as much computation as possible in the shielded domain before unshielding a final result (if unshielding is even needed at all).

    Shielded Wallet Client

    Full-featured client with encryption, shielded writes, and signed reads

    Full-featured client for Seismic that handles encryption, shielded writes, and signed reads. Extends viem's wallet client with an ECDH-derived AES encryption pipeline. On construction, it fetches the TEE public key from the node and derives a shared AES key for encrypting calldata.

    hashtag
    Import

    hashtag

    npm install seismic-viem 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..."),
    });
    
    // 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 generation
    import { createShieldedPublicClient } from "seismic-viem";
    const publicClient = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    import { createShieldedPublicClient } from "seismic-viem";
    import { seismicTestnet } from "seismic-viem";
    import { http } from "viem";
    
    const publicClient = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    
    // Standard viem public actions work as usual
    const blockNumber = await publicClient.getBlockNumber();
    const balance = await publicClient.getBalance({
      address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    });
    import { createShieldedPublicClient } from "seismic-viem";
    import { seismicTestnet } from "seismic-viem";
    import { http } from "viem";
    
    const publicClient = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    
    const teePublicKey = await publicClient.getTeePublicKey();
    console.log("TEE public key:", teePublicKey);
    import { createShieldedPublicClient } from "seismic-viem";
    import { seismicTestnet } from "seismic-viem";
    import { http } from "viem";
    
    const publicClient = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    
    // Generate a random number
    const randomValue = await publicClient.rng({ numBytes: 1 });
    console.log("Random value:", randomValue);
    
    // ECDH key exchange
    const sharedSecret = await publicClient.ecdh({
      sk: "0x...",
      pk: "0x...",
    });
    
    // AES-GCM encryption
    const ciphertext = await publicClient.aesGcmEncryption({
      aesKey: "0x...",
      plaintext: "0x...",
      nonce: "0x...",
    });
    
    // HKDF key derivation
    const derivedKey = await publicClient.hdfk("0x...");
    const publicClient = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    
    const txUrl = publicClient.txExplorerUrl("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 fields
    import { 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 App
    import { 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 result
    import { useShieldedWriteContract } from "seismic-react";
    import { useShieldedWriteContract } from 'seismic-react'
    
    const abi = [
      {
        name: 'transfer',
        type: 'function',
        stateMutability: 'nonpayable',
        inputs: [
          { name: 'to', type: 'address' },
          { name: 'amount', type: 'uint256' },
        ],
        outputs: [],
      },
    ] as const
    
    function TransferToken() {
      const { writeContract, isLoading, error, hash } = useShieldedWriteContract({
        address: '0x1234567890abcdef1234567890abcdef12345678',
        abi,
        functionName: 'transfer',
        args: ['0xRecipientAddress...', 1000n],
      })
    
      return (
        <div>
          <button onClick={writeContract} disabled={isLoading}>
            {isLoading ? 'Sending...' : 'Transfer'}
          </button>
          {hash && <p>Transaction: {hash}</p>}
          {error && <p>Error: {error.message}</p>}
        </div>
      )
    }
    import { useShieldedWriteContract } from 'seismic-react'
    import { useEffect } from 'react'
    
    function WriteWithTracking() {
      const { writeContract, hash, isLoading } = useShieldedWriteContract({
        address: CONTRACT_ADDRESS,
        abi,
        functionName: 'increment',
      })
    
      useEffect(() => {
        if (hash) {
          console.log('Transaction confirmed:', hash)
        }
      }, [hash])
    
      return (
        <div>
          <button onClick={writeContract} disabled={isLoading}>
            Increment
          </button>
          {hash && (
            <a href={`https://seismic-testnet.socialscan.io/tx/${hash}`} target="_blank" rel="noreferrer">
              View on explorer
            </a>
          )}
        </div>
      )
    }
    import { useShieldedWriteContract } from 'seismic-react'
    
    function WriteWithStates() {
      const { writeContract, isLoading, error, hash } = useShieldedWriteContract({
        address: CONTRACT_ADDRESS,
        abi,
        functionName: 'setNumber',
        args: [42n],
      })
    
      return (
        <div>
          <button onClick={writeContract} disabled={isLoading}>
            {isLoading ? 'Encrypting & sending...' : 'Set Number'}
          </button>
          {isLoading && <p>Transaction in progress...</p>}
          {error && <p style={{ color: 'red' }}>Failed: {error.message}</p>}
          {hash && <p style={{ color: 'green' }}>Success: {hash}</p>}
        </div>
      )
    }
    import { useShieldedWriteContract } from 'seismic-react'
    
    function WriteWithGasOverride() {
      const { writeContract, isLoading } = useShieldedWriteContract({
        address: CONTRACT_ADDRESS,
        abi,
        functionName: 'expensiveOperation',
        gas: 500_000n,
        gasPrice: 20_000_000_000n,
      })
    
      return (
        <button onClick={writeContract} disabled={isLoading}>
          Execute
        </button>
      )
    }
    contract = w3.seismic.contract(address="0x...", abi=ABI)

    Generate a block explorer URL

    addressExplorerUrl(address)

    string | null

    Explorer URL for an address

    blockExplorerUrl(block)

    string | null

    Explorer URL for a block

    txExplorerUrl(hash)

    string | null

    Explorer URL for a transaction

    tokenExplorerUrl(address)

    string | null

    Explorer URL for a token

    AES-GCM decrypt via precompile

    hdfk(ikm)

    Promise<Hex>

    HKDF key derivation via precompile

    secp256k1Signature(params)

    Promise<Signature>

    secp256k1 signing via precompile

    Encryption

    AES-GCM decrypt ciphertext using metadata as AAD

    expiresAtBlock

    bigint

    Block number after which the transaction expires

    signedRead

    boolean

    true for signed reads, false for standard writes

    Precompiles
    Installation

    Additional configuration options

    options.publicTransport

    Transport

    No

    Custom transport for the public client

    options.publicChain

    Chain

    No

    Custom chain for the public client

    options.onAddressChange

    (params: OnAddressChangeParams) => Promise<void>

    No

    Callback fired when the connected wallet address changes

    RainbowKit Guide
    Privy Guide
    Installation

    Name of the nonpayable/payable function

    args

    array

    No

    Arguments to pass to the function

    gas

    bigint

    No

    Gas limit override

    gasPrice

    bigint

    No

    Gas price override

    hash

    `0x${string}` | null

    Transaction hash from last successful write

    ShieldedWalletProvider
    Hooks Overview

    Automatic via ShieldedWalletProvider

    State management

    Manual

    React hooks (useShieldedWallet, etc.)

    Use when

    Server-side, scripts, non-React apps

    React applications

    Chains
    Installation
    Installation
    Chains
    Chains
    and
    network_pk
  • HKDF: Derive 32-byte AES key from shared secret

  • Derive client public key

  • Return EncryptionState

  • The AES key is derived using ECDH + HKDF (NIST SP 800-56C)

  • Called automatically by create_wallet_client() and create_async_wallet_client()

  • You rarely need to call this directly unless implementing custom client logic

  • securely (encrypted at rest, never logged)
  • ECDH security - Based on secp256k1 elliptic curve discrete logarithm problem

  • HKDF - Uses SHA-256 for key derivation

  • PrivateKey - Client private key type
  • CompressedPublicKey - TEE public key type

  • Shielded Write Guide - How encryption is used in transactions

  • network_pk

    CompressedPublicKey

    Yes

    The TEE's 33-byte compressed secp256k1 public key

    client_sk

    PrivateKey

    No

    Optional 32-byte client private key. If None, a random ephemeral key is generated

    EncryptionState

    Fully initialized encryption state with AES key and keypair

    Random ephemeral

    No key management needed, fresh key per session

    Cannot recreate encryption state, no key persistence

    Deterministic

    Can recreate same state, key persistence, backup via mnemonic

    Requires secure key storage, key management complexity

    HKDFarrow-up-right
    EncryptionState
    create_wallet_client
    create_async_wallet_client
    Casting
    client_pubkey = private_key_to_compressed_public_key(client_sk)
    return EncryptionState(
        aes_key=aes_key,
        encryption_pubkey=client_pubkey,
        encryption_private_key=client_sk,
    )
    def get_encryption(
        network_pk: CompressedPublicKey,
        client_sk: PrivateKey | None = None,
    ) -> EncryptionState
    from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
    
    # Get TEE public key (from node)
    tee_pk = CompressedPublicKey("0x02abcd...")
    
    # Derive encryption state (random ephemeral key)
    encryption = get_encryption(tee_pk)
    
    print(f"AES key: {encryption.aes_key.to_0x_hex()}")
    print(f"Client pubkey: {encryption.encryption_pubkey.to_0x_hex()}")
    import os
    from seismic_web3 import get_encryption, PrivateKey, CompressedPublicKey
    
    tee_pk = CompressedPublicKey("0x02abcd...")
    
    # Use a deterministic client key
    client_sk = PrivateKey.from_hex_str(os.environ["CLIENT_KEY"])
    
    encryption = get_encryption(tee_pk, client_sk)
    import os
    from seismic_web3 import get_encryption, get_tee_public_key, PrivateKey
    from web3 import Web3
    
    # This is what create_wallet_client() does internally
    w3 = Web3(Web3.HTTPProvider("https://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_key
    import 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_sk
    if 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 key
    from 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 leaked

    Write

    No

    Your address

    Yes

    Public writes

    Read

    No

    0x0

    No

    Public reads

    Write

    Yes

    Your address

    Yes

    Debug/testing

    Includes security metadata (nonce, block hash, expiry)
  • Higher gas cost (encryption overhead)

  • Lower gas cost (no overhead)
  • Faster (no encryption computation)

  • Must wait for confirmation

  • Returns result immediately
  • No confirmation needed

  • Result should be encrypted
    SeismicSecurityParams — Security parameters
  • DebugWriteResult — Debug write return type

  • Namespace

    Operation

    Encryption

    msg.sender

    Broadcasts

    Use Case

    .write

    Write

    Yes

    Your address

    Yes

    Privacy-sensitive writes

    .read

    Read

    Yes

    Your address

    No

    Contract Instance
    Shielded Write Guide
    Signed Read Guide

    Access-controlled reads

    Constructor
    circle-info

    createShieldedWalletClient is async -- it fetches the TEE public key from the node during construction and derives the AES encryption key. Always await the result.

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    chain

    Chain

    Yes

    Chain configuration (e.g., seismicTestnet)

    transport

    Transport

    Yes

    viem transport (e.g., http())

    account

    Account

    Yes

    hashtag
    Return Type

    Promise<ShieldedWalletClient>

    A viem Client extended with PublicActions, WalletActions, EncryptionActions, ShieldedPublicActions, ShieldedWalletActions, DepositContractPublicActions, DepositContractWalletActions, and SRC20WalletActions.

    hashtag
    Usage

    hashtag
    Initialization Lifecycle

    When you call createShieldedWalletClient(), the following steps happen:

    1. Creates a ShieldedPublicClient (or reuses the one provided via publicClient)

    2. Fetches the TEE public key from the node via seismic_getTeePublicKey RPC

    3. Generates an ephemeral secp256k1 keypair (or uses the provided encryptionSk)

    4. Derives an AES-256 key via ECDH between the client's private key and the TEE's public key

    5. Composes all action layers (PublicActions, WalletActions, EncryptionActions, ShieldedPublicActions, ShieldedWalletActions, DepositContractPublicActions, DepositContractWalletActions, SRC20WalletActions) onto a single viem client

    hashtag
    Actions

    hashtag
    Shielded Wallet Actions

    Action
    Description

    writeContract(params)

    Shielded write -- encrypts calldata with the AES key before sending

    twriteContract(params)

    Transparent write -- standard viem writeContract (unencrypted calldata)

    dwriteContract(params)

    Debug write -- returns the plaintext tx, encrypted tx, and tx hash without broadcasting

    readContract(params)

    Signed read -- authenticated eth_call that proves the caller's identity

    treadContract(params)

    Transparent read -- standard viem readContract (unsigned call)

    signedCall(params)

    Low-level signed eth_call

    hashtag
    Encryption Actions

    Action
    Description

    getEncryption()

    Returns the AES encryption key used for shielded operations

    getEncryptionPublicKey()

    Returns the client's encryption public key (the ephemeral secp256k1 public key)

    encrypt(plaintext, metadata)

    Manual AES-GCM encryption using the derived key

    decrypt(ciphertext, metadata)

    Manual AES-GCM decryption using the derived key

    hashtag
    Inherited Actions

    The wallet client also includes all actions from ShieldedPublicClient:

    • Shielded public actions -- getTeePublicKey(), explorerUrl(), etc.

    • Precompile actions -- rng(), ecdh(), aesGcmEncryption(), aesGcmDecryption(), hdfk(), secp256k1Signature()

    • SRC20 actions -- watchSRC20Events(), watchSRC20EventsWithKey()

    • Deposit contract actions -- getDepositRoot(), getDepositCount()

    • Standard viem public actions -- getBlockNumber(), getBalance(), getBlock(), etc.

    • Standard viem wallet actions -- sendTransaction(), signMessage(), signTypedData(), etc.

    circle-info

    The wallet client automatically includes all public client actions -- you can call getBlockNumber(), getBalance(), etc. directly on it.

    hashtag
    Examples

    hashtag
    Shielded Write

    hashtag
    Signed Read

    hashtag
    Reusing a Public Client

    hashtag
    Debug Write

    hashtag
    Custom Encryption Key

    hashtag
    getEncryption() Standalone Function

    The encryption derivation logic is also available as a standalone function, separate from any client:

    Parameter
    Type
    Required
    Description

    networkPk

    Hex

    Yes

    The TEE's secp256k1 public key

    clientSk

    Hex

    No

    Client's encryption private key. If not provided, generates a random one

    Returns { aesKey: Hex, encryptionPrivateKey: Hex, encryptionPublicKey: Hex }.

    This is useful when you need the encryption key material without constructing a full wallet client -- for example, to manually encrypt or decrypt data outside of the client's lifecycle.

    hashtag
    See Also

    • Shielded Public Client -- Read-only client without private key

    • Encryption -- Encryption utilities and getEncryption()

    • Chains -- Chain configurations for Seismic networks

    • -- Precompile details and parameters

    • -- Working with contract wrappers

    create_async_public_client

    Create async Web3 instance with public (read-only) Seismic access

    Create an asynchronous AsyncWeb3 instance with public (read-only) Seismic access.

    hashtag
    Overview

    create_async_public_client() creates an async client for read-only operations on the Seismic network. No private key is required. The w3.seismic namespace provides only public read operations: get_tee_public_key(), get_deposit_root(), get_deposit_count(), and contract() (with .tread only).

    Supports both HTTP and WebSocket connections for efficient async queries and real-time monitoring.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Type
    Description

    hashtag
    Examples

    hashtag
    Basic Usage (HTTP)

    hashtag
    WebSocket Connection

    hashtag
    Using Chain Configuration

    hashtag
    Async Application

    hashtag
    Read-Only Contract Access

    hashtag
    Monitoring Pattern

    hashtag
    Parallel Queries

    hashtag
    Context Manager Pattern

    hashtag
    How It Works

    The function performs three steps:

    1. Create provider

    2. Create AsyncWeb3 instance

    3. Attach public Seismic namespace

    No TEE public key fetching or encryption setup is performed since the client cannot perform shielded operations.

    hashtag
    Client Capabilities

    hashtag
    Standard AsyncWeb3 Methods (e.g. w3.eth, w3.net)

    • await get_block(), await get_transaction(), await get_balance()

    • await call(), await estimate_gas()

    hashtag
    Public Seismic Methods (w3.seismic)

    • - Get TEE public key

    • - Query deposit merkle root

    • - Query deposit count

    hashtag
    NOT Available

    • - Requires private key

    • - Requires private key

    • - Requires private key and encryption

    hashtag
    HTTP vs WebSocket

    hashtag
    Notes

    • The function is synchronous (no await needed) but returns an AsyncWeb3 instance whose methods are async

    • No private key required or accepted

    • No encryption setup performed

    hashtag
    Use Cases

    • Async block explorers and chain analytics

    • Real-time monitoring dashboards with WebSocket subscriptions

    • High-throughput read-only services

    hashtag
    Warnings

    • Connection cleanup - Close WebSocket connections properly to avoid resource leaks

    • Error handling - WebSocket connections can drop; implement reconnection logic for production

    • HTTPS/WSS recommended - Use secure protocols in production to prevent MITM attacks

    hashtag
    See Also

    • - Sync variant (HTTP only)

    • - Async client with private key

    • - The async public w3.seismic namespace

    ShieldedContract

    Sync contract wrapper with shielded and transparent namespaces

    Synchronous contract wrapper providing encrypted and transparent interaction with Seismic contracts.

    hashtag
    Overview

    ShieldedContract is the primary sync interface for interacting with Seismic smart contracts. It provides five namespaces for different interaction modes: encrypted writes (.write), encrypted reads (.read), transparent writes (.twrite), transparent reads (.tread), and debug writes (.dwrite). Each namespace dynamically exposes contract methods based on the provided ABI.

    hashtag
    Definition

    hashtag
    Constructor Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Namespaces

    hashtag
    .write - Encrypted Write

    Sends encrypted transactions using TxSeismic (type 0x4a). Calldata is encrypted before broadcast.

    Returns: HexBytes (transaction hash)

    Positional Arguments: *args - ABI function arguments (e.g. contract.write.transfer(to, amount))

    Optional Parameters:

    • value: int - Wei to send (default: 0)

    • gas: int | None - Gas limit (default: 30_000_000 when omitted)

    hashtag
    .read - Encrypted Read

    Executes encrypted signed eth_call with encrypted calldata. Result is decrypted and ABI-decoded by the SDK. Single-output functions return the value directly (e.g. int, bool); multi-output functions return a tuple.

    Returns: Any (ABI-decoded Python value)

    Positional Arguments: *args - ABI function arguments (e.g. contract.read.balanceOf(owner))

    Optional Parameters:

    • value: int - Wei for call context (default: 0)

    • gas: int - Gas limit (default: 30_000_000)

    hashtag
    .twrite - Transparent Write

    Sends standard eth_sendTransaction with unencrypted calldata.

    Returns: HexBytes (transaction hash)

    Positional Arguments: *args - ABI function arguments

    Optional Parameters:

    • value: int - Wei to send (default: 0)

    • **tx_params: Any - Additional transaction parameters (gas, gasPrice, etc.)

    hashtag
    .tread - Transparent Read

    Executes standard eth_call with unencrypted calldata. Result is ABI-decoded by the SDK. Single-output functions return the value directly; multi-output functions return a tuple.

    Returns: Any (ABI-decoded Python value)

    Positional Arguments: *args - ABI function arguments

    hashtag
    .dwrite - Debug Write

    Like .write but returns debug information including plaintext and encrypted views. Transaction is actually broadcast.

    Returns:

    Positional Arguments: *args - ABI function arguments

    Optional Parameters: Same as .write

    hashtag
    Examples

    hashtag
    Basic Encrypted Write

    hashtag
    Encrypted Read

    hashtag
    Transparent Operations

    hashtag
    Debug Write

    hashtag
    With Transaction Parameters

    hashtag
    Using EIP-712 Signing

    hashtag
    Instantiation via Client

    hashtag
    Notes

    • Dynamic method access: Contract methods are accessed via __getattr__, so contract.write.setNumber() dynamically resolves to the ABI function

    • ABI remapping: Shielded types (suint256, sbool, saddress) are remapped to standard types for encoding while preserving original names for selector computation

    hashtag
    See Also

    • - Async version of this class

    • - Read-only contract wrapper (no encryption)

    • - Manages encryption keys

    Hooks

    React hooks for shielded transactions and signed reads

    seismic-react provides four hooks that mirror wagmi's hook API but route through Seismic's shielded transport layer. Each hook wraps the corresponding seismic-viem client or contract method, handling encryption, signing, and decryption automatically.

    hashtag
    Hook Comparison

    seismic-react Hook
    wagmi Equivalent
    Purpose
    circle-info

    All hooks require ShieldedWalletProvider context. Calling any hook outside the provider tree will throw an error.

    hashtag
    Common Patterns

    hashtag
    Wallet check

    Always verify the wallet is loaded before calling contract methods:

    hashtag
    Loading states

    The write and read hooks expose isLoading so you can disable buttons or show spinners:

    hashtag
    Error handling

    Every hook returns an error field. Check it after operations complete:

    hashtag
    Pages

    Page
    Description

    hashtag
    See Also

    • -- Context provider required by all hooks

    • -- Package setup and peer dependencies

    • -- SDK architecture and quick start

    SRC20: Private Token

    \Build a private ERC20 token where balances and transfers are hidden from observers

    In this tutorial, you will build a fully functional private ERC20 token -- an SRC20 -- where balances, transfer amounts, and allowances are all shielded from external observers. Anyone watching the chain sees 0x00...0 instead of actual values, yet the token behaves like a standard ERC20 from the user's perspective.

    hashtag
    What you'll build

    By the end of this tutorial you will have:

    • An SRC20 smart contract with shielded balances, transfers, and allowances

    • Encrypted transfer events that only the sender and recipient can decrypt

    • A signed-read pattern that lets users check their own balance without anyone else knowing it

    • Compliance-ready access control through Intelligence Contracts

    • A React frontend that connects everything end-to-end

    hashtag
    What makes this special

    The contract changes from a standard ERC20 to an SRC20 are remarkably small. The core of it is changing uint256 to suint256 for balances, amounts, and allowances. The transfer logic, require checks, and overall structure stay almost identical. Seismic's compiler handles the rest -- routing reads and writes through shielded storage automatically.

    This is the power of Seismic's approach: privacy is a type-level annotation, not a protocol-level rewrite.

    hashtag
    Prerequisites

    Before starting, make sure you have:

    • Seismic development tools installed -- sforge, sanvil, and ssolc. See the if you have not set these up yet.

    • Solidity familiarity -- You should be comfortable writing and reading Solidity contracts.

    hashtag
    What you'll learn

    Chapter
    Topic
    Key concept

    hashtag
    Tutorial structure

    Chapter 1 starts with a side-by-side comparison of a standard ERC20 and the SRC20 version, walking through every changed line. Chapter 2 dives into the implementation of shielded transfers, allowances, and minting, including how to test with sforge. Chapter 3 tackles encrypted events -- since shielded types cannot appear in event parameters, you will use AES-GCM precompiles to encrypt sensitive data before emitting. Chapter 4 introduces signed reads, the mechanism that lets users query their own balance without exposing it. Chapter 5 adds compliance through Intelligence Contracts, showing how authorized roles can inspect shielded state. Chapter 6 brings it all together with a React frontend using seismic-react.

    Each chapter builds on the previous one. By the end, you will have a complete, deployable private token with a working frontend.

    Wallet Guides

    Integrate Seismic with popular wallet connection libraries

    seismic-react integrates with any wallet library that provides wagmi configuration. This section covers setup for the most popular options.

    hashtag
    Supported Libraries

    Library
    Best For
    Key Feature

    hashtag
    Common Integration Pattern

    Regardless of which wallet library you choose, the integration follows the same steps:

    1. Install the wallet library alongside seismic-react

    2. Configure wagmi with Seismic chain definitions

    3. Nest providers in the correct order

    hashtag
    Provider Nesting Order

    All wallet integrations require the same provider hierarchy:

    circle-exclamation

    ShieldedWalletProvider must be nested inside both WagmiProvider and your wallet provider. It reads the connected wallet from wagmi's context, so placing it outside will cause a runtime error.

    hashtag
    Chain Configuration

    All wallet libraries use the same chain imports from seismic-react/rainbowkit:

    hashtag
    Choosing a Library

    • Want the easiest setup with a beautiful wallet modal? Use

    • Need email/social login or embedded wallets? Use

    • Want WalletConnect with maximum wallet compatibility? Use

    hashtag
    Pages

    Page
    Description

    hashtag
    See Also

    • -- Package setup and peer dependencies

    • -- Provider reference and configuration

    • -- All available hooks

    AsyncShieldedContract

    Async contract wrapper with shielded and transparent namespaces

    Asynchronous contract wrapper providing encrypted and transparent interaction with Seismic contracts.

    hashtag
    Overview

    AsyncShieldedContract is the async version of ShieldedContract, providing the same five namespaces (.write, .read

    EncryptionState

    Holds AES key and encryption keypair derived from ECDH

    Holds the -GCM key and encryption keypair derived from key exchange.

    hashtag
    Overview

    EncryptionState encapsulates all cryptographic material needed for shielded transactions and signed reads. It's created by during wallet client setup and attached to .encryption.

    ChainConfig

    Immutable network configuration dataclass

    Immutable configuration for a Seismic network, including RPC endpoints, chain ID, and client factory methods.

    hashtag
    Overview

    ChainConfig is a frozen dataclass that encapsulates all information needed to connect to a Seismic network. Instead of passing RPC URLs and chain IDs separately, you create a ChainConfig once and use its convenience methods to create clients.

    Create Seismic Devnet

    Factory function for custom Seismic chain configurations

    Factory function that creates a RainbowKit-compatible chain configuration for any Seismic node. Use this when the pre-configured chains (seismicTestnet, sanvil, localSeismicDevnet) do not match your node's host.

    hashtag
    Import

    Differences from Ethereum

    hashtag
    Overview

    The is approximately a superset of the EVM

    hashtag
    What's the same

    # 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 key

    viem account (from privateKeyToAccount or custom)

    encryptionSk

    Hex

    No

    Custom encryption private key. If not provided, generates a random secp256k1 key

    publicClient

    ShieldedPublicClient

    No

    Reuse an existing ShieldedPublicClient instead of creating a new one

    sendShieldedTransaction(params)

    Low-level shielded transaction send

    Precompiles
    Contract Instance
    .twrite
    .tread
    .dwrite

    All other standard read-only async web3.py functionality

    contract() - Create contract wrappers (.tread methods are async)
    deposit() - Requires private key
  • Contract .swrite and .sread methods - Require private key

  • One-off queries

    Real-time monitoring, subscriptions

    No RPC calls during client creation (lightweight)

  • Cannot perform any write operations or shielded reads

  • Contract wrappers only expose .tread (transparent read, async)

  • All w3.seismic methods are async and must be await-ed

  • WebSocket connections should be properly closed when done

  • For write operations, use create_async_wallet_client()

  • For sync operations, use create_public_client()

  • Async data aggregation pipelines
  • Event monitoring and alerting systems

  • Price oracles with low-latency requirements

  • Chains Configuration - Pre-configured chain constants

  • Contract Instances - Working with contract wrappers

  • provider_url

    str

    Yes

    HTTP(S) or WS(S) URL of the Seismic node

    ws

    bool

    No

    If True, uses WebSocketProvider (persistent connection, supports subscriptions). Otherwise uses AsyncHTTPProvider. Default: False. WebSocket is only available on async clients — sync clients are HTTP-only

    AsyncWeb3

    An AsyncWeb3 instance with w3.seismic namespace attached (AsyncSeismicPublicNamespace)

    Aspect

    AsyncHTTPProvider (ws=False)

    WebSocketProvider (ws=True)

    Connection

    New connection per request

    Persistent connection

    Latency

    Higher per-request overhead

    Lower latency

    Subscriptions

    Not supported

    Supported (eth.subscribe)

    Resource usage

    Lower idle usage

    Keeps connection open

    await get_tee_public_key()
    await get_deposit_root()
    await get_deposit_count()
    send_shielded_transaction()
    debug_send_shielded_transaction()
    signed_call()
    create_public_client
    create_async_wallet_client
    AsyncSeismicPublicNamespace

    Use case

    address

    ChecksumAddress

    Yes

    Contract address (checksummed Ethereum address)

    abi

    list[dict[str, Any]]

    Yes

    Contract ABI (list of function entries)

    eip712

    bool

    No

    Use EIP-712 typed data signing (default: False)

    gas_price: int | None - Gas price in wei (default: network suggested)
  • security: [SeismicSecurityParams](../api-reference/transaction-types/seismic-security-params.md) | None - Security parameters for expiry

  • security: [SeismicSecurityParams](../api-reference/transaction-types/seismic-security-params.md) | None - Security parameters for expiry

    Gas defaults: .write uses 30_000_000 when gas is omitted; .twrite follows normal web3.py/provider transaction behavior

  • Encryption overhead: Encrypted operations add ~16 bytes (AES-GCM auth tag) to calldata

  • EIP-712 vs raw: EIP-712 signing enables integration with browser extension wallets like Metamask; raw signing is faster for automation and likely what you want to use in this Python SDK

  • Use .write in production: .dwrite is for debugging only

  • DebugWriteResult - Debug write return type
  • SeismicSecurityParams - Transaction expiry parameters

  • Contract Namespaces - Detailed namespace documentation

  • Shielded Write Guide - Complete workflow guide

  • w3

    Web3

    Yes

    Synchronous Web3 instance connected to Seismic RPC

    encryption

    EncryptionState

    Yes

    Encryption state for shielded operations

    private_key

    PrivateKey

    Yes

    DebugWriteResult
    AsyncShieldedContract
    PublicContract
    EncryptionState

    32-byte secp256k1 private key for signing transactions

    useShieldedWallet

    useAccount + useWalletClient

    Access shielded public and wallet clients

    useShieldedContract

    useContract

    Get a ShieldedContract instance

    useShieldedWriteContract

    useWriteContract

    Send encrypted write transactions

    useSignedReadContract

    useReadContract

    Execute authenticated read calls

    useShieldedWallet

    Access shielded wallet and public clients from context

    useShieldedContract

    Get a ShieldedContract instance for reads and writes

    useShieldedWriteContract

    Send encrypted write transactions

    useSignedReadContract

    Execute authenticated read calls

    ShieldedWalletProvider
    Installation
    Seismic React Overview

    Use hooks from seismic-react in your components

    RainbowKit

    dApps wanting polished wallet UI

    Built-in modal, chain switching, account display

    Privy

    Apps needing email/social login

    Embedded wallets, onboarding flow

    AppKit

    WalletConnect ecosystem apps

    WalletConnect modal, broad wallet support

    RainbowKit

    Setup with RainbowKit wallet UI

    Privy

    Embedded wallets with Privy

    AppKit

    WalletConnect AppKit integration

    RainbowKit
    Privy
    AppKit
    Installation
    ShieldedWalletProvider
    Hooks Overview
    def create_async_public_client(
        provider_url: str,
        *,
        ws: bool = False,
    ) -> AsyncWeb3
    from 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 App
    import { seismicTestnet } from "seismic-react/rainbowkit";
    Basic understanding of ERC20 -- You should know what balanceOf, transfer, approve, and transferFrom do.

    5

    Compliance-compatible access control

    6

    React integration with seismic-react

    1

    ERC20 to SRC20

    Shielded types and the minimal diff

    2

    Shielded Balances and Transfers

    suint256 in practice, testing with sforge

    3

    Encrypted Events

    AES-GCM precompiles for private event data

    4

    Signed Reads

    Letting users view their own balance securely

    Installation guide
    ,
    .twrite
    ,
    .tread
    ,
    .dwrite
    ) but with coroutine-based methods. All namespace methods return coroutines that must be awaited. Use this class in async/await applications for non-blocking contract interactions.

    hashtag
    Definition

    hashtag
    Constructor Parameters

    Parameter
    Type
    Required
    Description

    w3

    AsyncWeb3

    Yes

    Asynchronous AsyncWeb3 instance connected to Seismic RPC

    encryption

    Yes

    Encryption state for shielded operations

    private_key

    Yes

    hashtag
    Namespaces

    hashtag
    .write - Encrypted Write

    Sends encrypted transactions using TxSeismic (type 0x4a). Calldata is encrypted before broadcast.

    Returns: Coroutine[HexBytes] (transaction hash)

    Positional Arguments: *args - ABI function arguments (e.g. await contract.write.transfer(to, amount))

    Optional Parameters:

    • value: int - Wei to send (default: 0)

    • gas: int | None - Gas limit (default: 30_000_000 when omitted)

    • gas_price: int | None - Gas price in wei (default: network suggested)

    • security: [SeismicSecurityParams](../api-reference/transaction-types/seismic-security-params.md) | None - Security parameters for expiry

    hashtag
    .read - Encrypted Read

    Executes encrypted signed eth_call with encrypted calldata. Result is decrypted and ABI-decoded by the SDK. Single-output functions return the value directly (e.g. int, bool); multi-output functions return a tuple.

    Returns: Coroutine[Any] (ABI-decoded Python value)

    Positional Arguments: *args - ABI function arguments (e.g. await contract.read.balanceOf(owner))

    Optional Parameters:

    • value: int - Wei for call context (default: 0)

    • gas: int - Gas limit (default: 30_000_000)

    • security: [SeismicSecurityParams](../api-reference/transaction-types/seismic-security-params.md) | None - Security parameters for expiry

    hashtag
    .twrite - Transparent Write

    Sends standard async eth_sendTransaction with unencrypted calldata.

    Returns: Coroutine[HexBytes] (transaction hash)

    Positional Arguments: *args - ABI function arguments

    Optional Parameters:

    • value: int - Wei to send (default: 0)

    • **tx_params: Any - Additional transaction parameters (gas, gasPrice, etc.)

    hashtag
    .tread - Transparent Read

    Executes standard async eth_call with unencrypted calldata. Result is ABI-decoded by the SDK. Single-output functions return the value directly; multi-output functions return a tuple.

    Returns: Coroutine[Any] (ABI-decoded Python value)

    Positional Arguments: *args - ABI function arguments

    hashtag
    .dwrite - Debug Write

    Like .write but returns debug information including plaintext and encrypted views. Transaction is actually broadcast.

    Returns: Coroutine[DebugWriteResult] (DebugWriteResult)

    Positional Arguments: *args - ABI function arguments

    Optional Parameters: Same as .write

    hashtag
    Examples

    hashtag
    Basic Encrypted Write

    hashtag
    Encrypted Read

    hashtag
    Transparent Operations

    hashtag
    Debug Write

    hashtag
    Concurrent Operations

    hashtag
    With Transaction Parameters

    hashtag
    Batch Processing

    hashtag
    Using EIP-712 Signing

    hashtag
    Instantiation via Async Client

    hashtag
    Error Handling

    hashtag
    Context Manager Pattern

    hashtag
    Notes

    • All methods return coroutines: Must use await with every namespace call

    • Concurrent operations: Use asyncio.gather() for parallel reads/writes

    • Dynamic method access: Contract methods resolved via __getattr__ at runtime

    • ABI remapping: Shielded types automatically remapped (same as sync version)

    • Connection pooling: AsyncWeb3 can reuse connections for better performance

    • Gas defaults: .write uses 30_000_000 when gas is omitted; .twrite follows normal web3.py/provider transaction behavior

    • EIP-712 vs raw: Same signing options as sync version

    • Error handling: Use try/except around await calls for RPC errors

    hashtag
    See Also

    • ShieldedContract - Synchronous version of this class

    • AsyncPublicContract - Async read-only contract wrapper

    • create_async_wallet_client - Create async client

    • - Manages encryption keys

    • - Debug write return type

    • - Transaction expiry parameters

    • - Detailed namespace documentation

    The class provides encrypt() and decrypt() methods that handle AES-GCM encryption with metadata-bound Additional Authenticated Data (AAD).

    hashtag
    Definition

    hashtag
    Attributes

    Attribute
    Type
    Description

    aes_key

    32-byte AES-256 key derived from ECDH +

    encryption_pubkey

    Client's 33-byte compressed secp256k1 public key

    encryption_private_key

    Client's 32-byte secp256k1 private key

    hashtag
    Methods

    hashtag
    encrypt()

    Encrypt plaintext calldata with metadata-bound AAD.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Description

    plaintext

    HexBytes

    Raw calldata to encrypt

    nonce

    12-byte AES-GCM nonce

    metadata

    Transaction metadata (used to build AAD)

    hashtag
    Returns

    Type
    Description

    HexBytes

    Ciphertext with 16-byte authentication tag appended

    hashtag
    Example

    hashtag
    decrypt()

    Decrypt ciphertext with metadata-bound AAD.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Description

    ciphertext

    HexBytes

    Encrypted data (includes 16-byte auth tag)

    nonce

    12-byte AES-GCM nonce

    metadata

    Transaction metadata (used to build AAD)

    hashtag
    Returns

    Type
    Description

    HexBytes

    Decrypted plaintext

    hashtag
    Raises

    • cryptography.exceptions.InvalidTag - If authentication fails (wrong key, tampered data, or mismatched metadata)

    hashtag
    Example

    hashtag
    Examples

    hashtag
    Access from Client

    hashtag
    Manual Encryption Workflow

    hashtag
    Custom Encryption Key

    hashtag
    Verify Encryption/Decryption

    hashtag
    How It Works

    hashtag
    Initialization

    When created, EncryptionState automatically initializes an internal AesGcmCrypto instance:

    hashtag
    Encryption

    1. Encode metadata as AAD using encode_metadata_as_aad()

    2. Call AesGcmCrypto.encrypt(plaintext, nonce, aad)

    3. Return ciphertext with 16-byte authentication tag

    hashtag
    Decryption

    1. Encode metadata as AAD

    2. Call AesGcmCrypto.decrypt(ciphertext, nonce, aad)

    3. Verify authentication tag (raises InvalidTag if fails)

    4. Return plaintext

    hashtag
    AAD Binding

    The Additional Authenticated Data (AAD) ensures that ciphertext is cryptographically bound to transaction metadata:

    • message_version

    • chain_id

    • client_pubkey

    • nonce_seed

    • recent_block_hash

    • expires_at_block

    If any metadata field changes, decryption will fail even with the correct key and nonce.

    hashtag
    Notes

    • Pure computation - no I/O operations

    • Works in both sync and async contexts

    • Created automatically by create_wallet_client() and create_async_wallet_client()

    • You rarely need to call or directly - the SDK handles this

    • The internal _crypto field is excluded from repr() and comparison

    • Authentication tag is always 16 bytes (AES-GCM standard)

    hashtag
    Security Considerations

    • Key derivation - AESarrow-up-right key is derived from ECDHarrow-up-right + HKDFarrow-up-right, ensuring forward secrecy

    • AAD binding - Metadata binding prevents ciphertext reuse or manipulation

    • Nonce uniqueness - Nonces must be unique per encryption; SDK generates fresh nonces automatically

    • Key storage - encryption_private_key should be stored securely if deterministic keys are used

    hashtag
    See Also

    • get_encryption - Derive encryption state from TEE public key

    • create_wallet_client - Sync client factory (creates EncryptionState)

    • create_async_wallet_client - Async client factory

    • - 12-byte nonce type

    • - Metadata structure

    • - How shielded transactions work

    AESarrow-up-right
    ECDHarrow-up-right
    get_encryption()
    w3.seismic
    hashtag
    Definition

    hashtag
    Attributes

    Attribute
    Type
    Required
    Description

    chain_id

    int

    Yes

    Numeric chain identifier (e.g., 5124 for testnet)

    rpc_url

    str

    Yes

    HTTP(S) JSON-RPC endpoint

    ws_url

    str | None

    No

    hashtag
    Construction

    hashtag
    Basic Usage

    hashtag
    Without WebSocket

    hashtag
    Using Pre-Defined Configs

    hashtag
    Methods

    hashtag
    wallet_client()

    Create a synchronous Web3 instance with wallet capabilities.

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    private_key

    PrivateKey

    Yes

    32-byte signing key for transactions

    encryption_sk

    PrivateKey | None

    No

    Optional 32-byte key for ECDH

    hashtag
    Returns

    • Web3 - A synchronous Web3 instance with w3.seismic namespace attached

    hashtag
    Example


    hashtag
    async_wallet_client()

    Create an asynchronous Web3 instance with wallet capabilities.

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    private_key

    PrivateKey

    Yes

    32-byte signing key for transactions

    encryption_sk

    PrivateKey | None

    No

    Optional 32-byte key for ECDH

    ws

    bool

    No

    hashtag
    Returns

    • AsyncWeb3 - An asynchronous Web3 instance with w3.seismic namespace attached

    hashtag
    Behavior

    • When ws=True and ws_url is set, the WebSocket URL is used automatically

    • When ws=False, the HTTP rpc_url is used

    • Raises ValueError if ws=True but ws_url is None

    hashtag
    Example


    hashtag
    public_client()

    Create a synchronous Web3 instance with public (read-only) access.

    hashtag
    Parameters

    None. (public clients don't handle private keys)

    hashtag
    Returns

    • Web3 - A synchronous Web3 instance with w3.seismic namespace attached (read-only)

    hashtag
    Example


    hashtag
    async_public_client()

    Create an asynchronous Web3 instance with public (read-only) access.

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    ws

    bool

    No

    If True, uses WebSocketProvider (default: False)

    hashtag
    Returns

    • AsyncWeb3 - An asynchronous Web3 instance with w3.seismic namespace attached (read-only)

    hashtag
    Example

    hashtag
    Deprecated Methods

    hashtag
    create_client()

    Deprecated: use wallet_client() instead.

    hashtag
    create_async_client()

    Deprecated: use async_wallet_client() instead.

    hashtag
    Notes

    • All client creation methods return standard web3.py instances

    • The w3.seismic namespace is automatically attached to all clients

    • WebSocket connections provide better performance for subscription-based workflows

    • HTTP connections are suitable for simple request-response patterns

    hashtag
    See Also

    • SEISMIC_TESTNET - Pre-defined testnet configuration

    • SANVIL - Pre-defined local development configuration

    • make_seismic_testnet - Factory for testnet instances

    • - Direct wallet client creation

    • - Direct public client creation

    • - Private key type

    hashtag
    Parameters
    Parameter
    Type
    Required
    Description

    nodeHost

    string

    Yes

    Hostname for the node (e.g. gcp-1.seismictest.net)

    explorerUrl

    string

    No

    Block explorer URL

    hashtag
    Return Type

    RainbowKitChain -- a chain object compatible with RainbowKit's getDefaultConfig and wagmi's createConfig.

    The returned chain has:

    • Chain ID: 5124

    • Name: Seismic

    • Native Currency: ETH (18 decimals)

    • RPC (HTTPS): https://<nodeHost>/rpc

    • RPC (WSS): wss://<nodeHost>/ws

    • Seismic transaction formatters

    hashtag
    Usage

    hashtag
    Basic

    hashtag
    With RainbowKit

    hashtag
    With wagmi Config

    hashtag
    With Explorer URL

    hashtag
    Notes

    • The nodeHost parameter should be the bare hostname without a protocol prefix or path. HTTPS and WSS URLs are constructed automatically.

    • The Seismic icon is included automatically for display in RainbowKit's chain selector.

    • The underlying implementation delegates to createSeismicDevnet from seismic-viem and wraps the result with RainbowKit metadata.

    hashtag
    See Also

    • Chains Overview - All supported chains

    • Seismic Testnet - Public testnet configuration

    • Sanvil - Local development chains

    • - RainbowKit setup guides

  • Transaction construction and serialization identical to Ethereum (with one new transaction type)

  • Address generation, gas estimation, and signing work the same as Ethereum

  • Almost all RPC methods are identical to reth

  • Standard Solidity bytecode will behave identically on Seismic, with (e.g., SSTORE reverts on shielded slots)

  • Seismic supports all of Ethereum's opcodes & precompiles

  • EIP-1559 transactions follow standard EIP-1559 fee rules. Seismic transactions (type 0x4A) use legacy fee pricing

  • Seismic will produce empty blocks when there are no pending transactions

  • hashtag
    Key differences

    • Shielded storage: Solidity contracts can store private data on-chain

    • Runs in a TEE: Seismic nodes must run in Trusted Execution Environments

    • Seismic transaction: We added a new transaction type that allows you to encrypt your calldata

    hashtag
    EVM Compatibility

    hashtag
    Opcodes

    • CLOAD – load shielded data from storage

    • CSTORE – write shielded data to storage

    • TIMESTAMP_MS – get the block timestamp in milliseconds. Note that block.timestamp still returns seconds, matching standard Solidity. Use block.timestamp_ms for millisecond precision

    hashtag
    Seismic transaction

    The transaction with type 0x4a allows users to encrypt their calldata. These otherwise work just like legacy transactions. We also support the other standard Ethereum transaction types (Legacy, EIP-1559, EIP-2930, EIP-4844, EIP-7702)

    hashtag
    Precompiles

    All standard Ethereum precompiles are still available. Seismic added 6 new precompiles to our EVM:

    • RNG 0x64 — securely generate a random number

    • ECDH 0x65 — Elliptic Curve Diffie-Hellman, for generating a shared secret given a public key and secret key

    • AES-GCM Encrypt 0x66 — encrypt data with AES-GCM

    • — decrypt data with AES-GCM

    • — derive cryptographic keys from a parent key

    • — sign a message given a secret key

    hashtag
    Staking

    Seismic uses the same staking contract as Ethereum, which is hardcoded into our Genesis block at address 0x00000000219ab540356cbb839cbe05303d7705fa

    hashtag
    Block times

    We will often produce multiple blocks in the same second, yet Ethereum's block timestamps are expressed in terms of unix seconds. Our solution to this:

    • Block headers and the EVM use timestamps in milliseconds internally

    • In Seismic Solidity, block.timestamp returns unix seconds, just like in standard Solidity. We added block.timestamp_ms which returns unix milliseconds. block.timestamp_seconds is an alias for block.timestamp

    hashtag
    RPC compatibility

    We support almost every RPC endpoint in Reth, and have added a few more of our own. See the full RPC Methods reference for details.

    Seismic-specific methods:

    • seismic_getTeePublicKey — returns the TEE's encryption public key for ECDH key exchange

    Modified Ethereum methods:

    • eth_call — zeroes the from field on unsigned calls; supports "signed reads" via type 0x4A

    • eth_sendRawTransaction — accepts Seismic transaction type 0x4A with encrypted calldata

    • — returns zero for shielded slots, making them indistinguishable from uninitialized storage

    • Tracing endpoints are currently disabled. We plan to re-enable them with private data redacted from traces

    Seismic EVMarrow-up-right

    .write

    Shielded write namespace for encrypted contract transactions

    The .write namespace provides encrypted contract write operations using Seismic's TxSeismic (type 0x4a) transaction format. Calldata is encrypted end-to-end from your client to the node's TEE, ensuring on-chain privacy.


    hashtag
    Overview

    When you call contract.write.functionName(...), the SDK:

    1. Encodes your function call using the contract ABI

    2. Encrypts the calldata using AES-GCM with a shared key derived via ECDH

    3. Constructs a TxSeismic with encryption metadata (nonce, block hash, expiry)

    The encrypted calldata is bound to the transaction context (chain ID, nonce, block hash, expiry) via AES-GCM additional authenticated data, preventing replay attacks and tampering.


    hashtag
    Usage Pattern

    • Sync: Returns HexBytes transaction hash immediately

    • Async: Returns HexBytes transaction hash (must await)


    hashtag
    Parameters

    hashtag
    Function Arguments

    Pass function arguments as positional parameters:

    hashtag
    Transaction Options (Keyword Arguments)

    All transaction options are optional keyword arguments:

    Parameter
    Type
    Default
    Description

    hashtag
    Examples

    hashtag
    Sync Usage

    hashtag
    Async Usage

    hashtag
    Sending ETH

    hashtag
    Custom Gas Parameters

    hashtag
    Custom Security Parameters

    hashtag
    Combining All Parameters


    hashtag
    Return Value

    Returns HexBytes containing the transaction hash.

    You can:

    • Convert to hex string: tx_hash.to_0x_hex()

    • Convert to bytes: bytes(tx_hash)

    • Wait for receipt: w3.eth.wait_for_transaction_receipt(tx_hash)


    hashtag
    Privacy Guarantees

    hashtag
    What Gets Encrypted

    • Function selector (4 bytes)

    • All function arguments

    • Encoding metadata

    An observer watching the network can see:

    • Your address (transaction sender)

    • Contract address (transaction recipient)

    • Value transferred (if non-zero)

    But cannot see:

    • Which function you called

    • What arguments you passed

    • Any data in the calldata

    hashtag
    What Remains Visible

    These fields are not encrypted:

    • from — Your wallet address

    • to — Contract address

    • value — ETH amount sent


    hashtag
    Security Considerations

    hashtag
    Block Hash Freshness

    Every shielded transaction includes a recent block hash as a freshness proof. The node validates that:

    1. The block hash corresponds to a real block

    2. The block is recent (within the chain's freshness window)

    This prevents:

    • Replay attacks across chains

    • Stale transaction submissions

    hashtag
    Transaction Expiry

    Transactions include an expiry block number. After this block:

    • The node will reject the transaction

    • You must create a new transaction with updated parameters

    Default expiry: 100 blocks (~20 minutes on most chains)

    hashtag
    Nonce Uniqueness

    Each transaction uses a cryptographically random 12-byte encryption nonce. Never reuse nonces — this breaks AES-GCM security.

    The SDK generates random nonces automatically. Only override if you know what you're doing (e.g., testing).


    hashtag
    Error Handling


    hashtag
    Comparison with Other Namespaces

    Namespace
    Encryption
    Transaction Type
    Gas Cost
    Use Case

    hashtag
    Best Practices

    hashtag
    Use .write When

    • Function arguments contain sensitive data (amounts, addresses, private state)

    • Privacy is required (trading, voting, auctions)

    • Compliance requires on-chain confidentiality

    hashtag
    Don't Use .write When

    • Function is view-only (use .read instead)

    • No privacy needed and gas optimization is priority (use .twrite)

    • Data is already public (e.g., reading public constants)

    hashtag
    Production Checklist

    • Use default security parameters (don't override unless necessary)

    • Handle transaction failures gracefully

    • Wait for transaction confirmation before assuming success


    hashtag
    Low-Level Alternative

    If you need more control (e.g., contract deployment, pre-encoded calldata):

    See for details.


    hashtag
    See Also

    • — Full encryption workflow

    • — Security parameter reference

    • — Encrypted signed reads

    .twrite

    Transparent write namespace for standard contract transactions

    The .twrite namespace provides standard (non-encrypted) contract write operations using eth_sendTransaction. Use this when privacy is not required and you want to minimize gas costs.


    hashtag
    Overview

    When you call contract.twrite.functionName(...), the SDK:

    1. Encodes your function call using the contract ABI

    2. Constructs a standard Ethereum transaction

    3. Sends the transaction using eth_sendTransaction

    No encryption is applied — calldata is visible on-chain to anyone.


    hashtag
    Usage Pattern

    • Sync: Returns HexBytes transaction hash immediately

    • Async: Returns HexBytes transaction hash (must await)


    hashtag
    Parameters

    hashtag
    Function Arguments

    Pass function arguments as positional parameters:

    hashtag
    Transaction Options (Keyword Arguments)

    Transaction options are passed as keyword arguments:

    Parameter
    Type
    Default
    Description

    Common additional parameters:

    • gas — Gas limit

    • gasPrice — Gas price (legacy)

    • maxFeePerGas — Max fee per gas (EIP-1559)


    hashtag
    Examples

    hashtag
    Sync Usage

    hashtag
    Async Usage

    hashtag
    Sending ETH

    hashtag
    Custom Gas (Legacy)

    hashtag
    EIP-1559 Gas Parameters

    hashtag
    Custom Nonce

    hashtag
    Combining Parameters


    hashtag
    Return Value

    Returns HexBytes containing the transaction hash.

    You can:

    • Convert to hex string: tx_hash.to_0x_hex()

    • Convert to bytes: bytes(tx_hash)

    • Wait for receipt: w3.eth.wait_for_transaction_receipt(tx_hash)


    hashtag
    Privacy Implications

    hashtag
    What's Visible

    Everything is visible on-chain:

    • Your address (transaction sender)

    • Contract address (transaction recipient)

    • Function selector (first 4 bytes of calldata)

    hashtag
    Example

    Anyone can:

    • See you called transfer

    • See the recipient address

    • See the amount transferred


    hashtag
    When to Use .twrite

    hashtag
    Good Use Cases

    • Public operations — No sensitive data in calldata

    • Cost optimization — Lower gas costs (no encryption overhead)

    • Public protocols — DEX trades, public votes, open registrations

    hashtag
    Examples


    hashtag
    When NOT to Use .twrite

    hashtag
    Use .write Instead When

    • Function arguments contain sensitive data

    • Privacy is required (trading, private voting, auctions)

    • Amounts or addresses should be hidden

    hashtag
    Examples (Use .write for these)


    hashtag
    Comparison with Other Namespaces

    Namespace
    Encryption
    Transaction Type
    Gas Cost
    Use Case

    hashtag
    Error Handling


    hashtag
    Standard Web3.py Behavior

    The .twrite namespace uses standard eth_sendTransaction under the hood. All web3.py transaction features work:

    hashtag
    Account Management

    hashtag
    Transaction Middleware

    hashtag
    Gas Estimation


    hashtag
    Best Practices

    hashtag
    Security Checklist

    • Verify calldata is not sensitive — Anyone can see it

    • Check if privacy is required — Use .write if in doubt

    • Validate recipient addresses — Mistakes are public and permanent

    hashtag
    Optimization Tips

    • Let web3.py estimate gas (don't hardcode unless necessary)

    • Use EIP-1559 pricing on supported networks

    • Batch multiple operations if possible

    hashtag
    Common Patterns


    hashtag
    Low-Level Alternative

    Direct eth_sendTransaction call:

    The .twrite namespace is more convenient as it handles ABI encoding automatically.


    hashtag
    See Also

    • — Encrypted writes for privacy

    • — Transparent reads

    • — Debug writes

    Casting

    hashtag
    Explicit Casting Only

    Shielded types and their unshielded counterparts do not support implicit casting. You must always cast explicitly. This is a deliberate design decision -- every conversion between shielded and unshielded types is a potential information boundary, and the compiler requires you to be explicit about crossing it.

    This applies to all shielded types: suint, sint, sbool, saddress, and sbytes.

    circle-info

    For integer literals specifically, you can use the instead of an explicit cast: suint256 x = 42s; is equivalent to suint256 x = suint256(42);. The explicit cast is still required when converting from a variable.

    hashtag
    Shielding Values (Unshielded to Shielded)

    When you cast from an unshielded type to a shielded type, you are "shielding" the value -- moving it from the public domain into confidential storage/computation.

    circle-exclamation

    Privacy consideration: When going from unshielded to shielded, the original unshielded value is visible in the transaction trace at the point of casting. Observers can see what value was shielded. The value only becomes confidential after the cast, in subsequent operations.

    If you need the value to be confidential from the start, it should arrive as encrypted calldata via a Seismic transaction (type 0x4A), not be cast from a public variable.

    hashtag
    Unshielding Values (Shielded to Unshielded)

    When you cast from a shielded type to an unshielded type, you are "unshielding" the value -- making it publicly visible.

    circle-exclamation

    Privacy consideration: When going from shielded to unshielded, the final unshielded value is visible in the transaction trace. Observers can see the result. This permanently exposes the value.

    This is sometimes necessary (e.g., returning a value from a view function or interfacing with a non-shielded contract), but you should be deliberate about when and why you do it.

    hashtag
    Casting saddress to Payable

    saddress payable is a valid type, but it does not unlock any extra operations — .transfer(), .send(), and .balance are all blocked on shielded addresses regardless of payability. The payable marker exists for type-system consistency (e.g., contracts with receive() payable convert to saddress payable), not for sending ETH.

    To actually send ETH to a shielded address, you must unshield it first:

    circle-exclamation

    Privacy consideration: Unshielding to address payable exposes the address in the transaction trace.

    You can also convert in the other direction:

    hashtag
    Size Casting Between Shielded Integers

    You can cast between different sizes of shielded integers, just as you can with regular Solidity integers:

    The same rules that apply to regular Solidity integer casting apply here:

    • Widening (smaller to larger): Always safe, the value is preserved.

    • Narrowing (larger to smaller): May truncate the value if it exceeds the target type's range.

    You can also cast between signed and unsigned shielded integers:

    hashtag
    Common Patterns

    hashtag
    Returning values from view functions

    Since shielded types cannot be returned from public or external functions, you must unshield them first:

    circle-info

    Returning an unshielded value from a view function makes it visible to the caller. If the caller should only see their own data, use access control and to ensure only authorized users can query it.

    hashtag
    Interfacing with non-shielded contracts

    When calling a contract that expects unshielded types, cast at the call boundary:

    hashtag
    Shielding input from encrypted calldata

    The only way to introduce a value that is private from the start is through encrypted calldata, where the value is never visible in plaintext on-chain:

    hashtag
    Security Implications

    Every cast between shielded and unshielded types is a potential information leak point. Keep these principles in mind:

    1. Unshielded-to-shielded casts expose the input value in the trace. If the value was meant to be secret from the start, use encrypted calldata instead.

    2. Shielded-to-unshielded casts expose the output value in the trace. Only unshield when you intend the value to become public.

    3. Minimize casts. The fewer times you cross the shielded/unshielded boundary, the smaller your attack surface.

    class AsyncShieldedContract:
        def __init__(
            self,
            w3: AsyncWeb3,
            encryption: EncryptionState,
            private_key: PrivateKey,
            address: ChecksumAddress,
            abi: list[dict[str, Any]],
            eip712: bool = False,
        ) -> None:
            ...
    import asyncio
    from seismic_web3 import create_async_wallet_client, AsyncShieldedContract
    
    async def main():
        w3 = await create_async_wallet_client(
            provider_url="https://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: PrivateKey
    def encrypt(
        self,
        plaintext: HexBytes,
        nonce: EncryptionNonce,
        metadata: TxSeismicMetadata,
    ) -> HexBytes
    from 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) + 16
    def decrypt(
        self,
        ciphertext: HexBytes,
        nonce: EncryptionNonce,
        metadata: TxSeismicMetadata,
    ) -> HexBytes
    from 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 == plaintext
    import 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 later
    from 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
    SANVIL
    def 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/ws
    import { 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

    WebSocket endpoint (default: None)

    name

    str

    No

    Human-readable network name (default: "")

    If True, uses WebSocketProvider (default: False)

    create_wallet_client
    create_public_client
    PrivateKey
    Wallet Guides
    minor exceptions
    AES-GCM Decrypt 0x67
    HKDF 0x68
    secp256k1 Sign 0x69
    eth_getStorageAt
    Intelligence Contracts
    Building the Frontend

    32-byte secp256k1 private key for signing transactions

    address

    ChecksumAddress

    Yes

    Contract address (checksummed Ethereum address)

    abi

    list[dict[str, Any]]

    Yes

    Contract ABI (list of function entries)

    eip712

    bool

    No

    Use EIP-712 typed data signing (default: False)

    EncryptionState
    DebugWriteResult
    SeismicSecurityParams
    Contract Namespaces
    EncryptionState
    PrivateKey
    encrypt()
    decrypt()
    EncryptionNonce
    TxSeismicMetadata
    Shielded Write Guide
    Bytes32
    HKDFarrow-up-right
    CompressedPublicKey
    PrivateKey
    EncryptionNonce
    TxSeismicMetadata
    EncryptionNonce
    TxSeismicMetadata
    Returns the transaction hash

    maxPriorityFeePerGas — Max priority fee (EIP-1559)

  • nonce — Transaction nonce (auto-generated if omitted)

  • All function arguments (decoded in calldata)
  • Value transferred

  • Gas parameters

  • Transaction result (success/revert)

  • Decode all parameters from calldata
    Testing — Quick tests where privacy doesn't matter
  • Compatibility — Interacting with contracts that expect standard transactions

  • Compliance requires confidentiality

    Encrypted + debug info

    TxSeismic (0x4a)

    Same as .write

    Development/debugging

    Test with small amounts first — Errors are visible to everyone

    Consider using .write for high-value transactions (extra privacy worth the cost)
    Contract Instance — Contract wrapper reference
  • Web3.py Documentationarrow-up-right — Standard Ethereum transactions

  • value

    int

    0

    ETH value to send (in wei)

    Any eth_sendTransaction param

    Any

    —

    Standard web3.py transaction parameters

    .write

    Encrypted calldata

    TxSeismic (0x4a)

    Standard + encryption overhead

    Privacy-sensitive writes

    .twrite

    No encryption

    eth_sendTransaction

    Standard

    Public writes, lower cost

    .write Namespace
    .tread Namespace
    .dwrite Namespace

    .dwrite

    Audit cast points. During security review, identify every cast in your contract and verify that the information exposure at each point is intentional and acceptable.

    s suffix
    signed reads
    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);    // OK
    uint256 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 truncate
    suint256 unsigned = suint256(100);
    sint256 signed = sint256(unsigned);   // Reinterprets the bits
    suint256 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.
    }
    Signs and broadcasts the transaction
  • Returns the transaction hash

  • security

    | None

    None

    Custom security parameters (block expiry, nonce, etc.)

    Gas used

    gas and gas_price — Gas parameters

  • nonce — Transaction nonce

  • Transaction metadata (block hash, expiry, encryption nonce)

  • Encrypted + debug info

    TxSeismic (0x4a)

    Same as .write

    Development/debugging

    Monitor gas prices and adjust if needed
  • Test with .dwrite first to verify calldata encoding

  • .dwrite Namespace — Debug writes with inspection
  • .twrite Namespace — Transparent writes without encryption

  • value

    int

    0

    ETH value to send (in wei)

    gas

    int | None

    None

    Gas limit (30_000_000 if None)

    gas_price

    int | None

    None

    .write

    Encrypted calldata

    TxSeismic (0x4a)

    Standard + encryption overhead

    Privacy-sensitive writes

    .twrite

    No encryption

    eth_sendTransaction

    Standard

    Public writes, lower cost

    Shielded Write Guide
    Shielded Write Guide
    SeismicSecurityParams
    .read Namespace

    Gas price in wei (uses network default if None)

    .dwrite

    .read

    Signed read namespace for encrypted contract queries

    The .read namespace provides encrypted contract read operations using signed eth_call. Both calldata and return values are encrypted end-to-end, and the transaction is signed to prove your identity to the contract.


    hashtag
    Overview

    When you call contract.read.functionName(...), the SDK:

    1. Encodes your function call using the contract ABI

    2. Encrypts the calldata using AES-GCM with a shared key derived via ECDH

    3. Constructs a full TxSeismic with encryption metadata (nonce, block hash, expiry)

    Unlike standard eth_call, signed reads prove your identity to the contract via msg.sender, enabling access-controlled queries.


    hashtag
    Usage Pattern

    • Sync: Returns decoded Python value immediately

    • Async: Returns decoded Python value (must await)

    Return values are automatically ABI-decoded:

    • Single output (e.g. returns (uint256)): returns the value directly (int, bool, str, etc.)

    • Multiple outputs (e.g. returns (uint256, bool)): returns a tuple


    hashtag
    Parameters

    hashtag
    Function Arguments

    Pass function arguments as positional parameters:

    hashtag
    Call Options (Keyword Arguments)

    All call options are optional keyword arguments:

    Parameter
    Type
    Default
    Description

    Note: Unlike .write, there is no gas_price parameter because reads don't consume gas.


    hashtag
    Examples

    hashtag
    Sync Usage

    hashtag
    Async Usage

    hashtag
    Reading with Arguments

    hashtag
    Custom Gas Limit

    hashtag
    Custom Security Parameters

    hashtag
    Simulating Value Transfer


    hashtag
    Return Value

    Returns the ABI-decoded Python value:

    • Single output (e.g. returns (uint256)): the value directly — int, bool, str, bytes, etc.

    • Multiple outputs (e.g. returns (uint256, bool, address)


    hashtag
    Why Use Signed Reads?

    hashtag
    Identity Matters

    Many contracts use msg.sender to determine access or return personalized data. For example, SRC20's balanceOf() takes no arguments and returns the caller's balance:

    With signed read (.read):

    With transparent read (.tread):

    hashtag
    Common Use Cases

    Use signed reads when the contract function:

    • Checks msg.sender permissions

    • Returns data specific to the caller

    • Has access control (owner-only, role-based, etc.)


    hashtag
    Privacy Guarantees

    hashtag
    What Gets Encrypted

    • Function selector (4 bytes)

    • All function arguments

    • Return value (encrypted by node)

    An observer watching the network can see:

    • Your address (call sender)

    • Contract address (call recipient)

    • That you made a call (via signed transaction)

    But cannot see:

    • Which function you called

    • What arguments you passed

    • What data was returned

    hashtag
    What Remains Visible

    These fields are not encrypted:

    • from — Your wallet address

    • to — Contract address

    • Gas limit


    hashtag
    Comparison with .tread


    hashtag
    Security Considerations

    hashtag
    Block Hash Freshness

    Like shielded writes, signed reads include a recent block hash. The node validates:

    1. Block hash corresponds to a real block

    2. Block is recent (within freshness window)

    hashtag
    Transaction Expiry

    Calls include an expiry block number:

    • Default: 100 blocks (~20 minutes)

    • After expiry, node rejects the call

    hashtag
    Nonce Uniqueness

    Each call uses a cryptographically random 12-byte encryption nonce. The SDK generates random nonces automatically.

    hashtag
    Private Key Security

    Signed reads require your private key to sign the call. Never expose your private key or share it with untrusted parties.


    hashtag
    Error Handling


    hashtag
    Best Practices

    hashtag
    Use .read When

    • Contract function checks msg.sender

    • You need to prove your identity

    • Data is access-controlled (requires authentication)

    hashtag
    Don't Use .read When

    • Function is public and doesn't check msg.sender

    • No privacy needed (use .tread for lower overhead)

    • Function is a simple getter with no access control

    hashtag
    Production Checklist

    • Verify contract function actually uses msg.sender

    • Use appropriate gas limits for complex reads

    • Results are auto-decoded — no manual eth_abi.decode needed


    hashtag
    Low-Level Alternative

    If you need more control (e.g., pre-encoded calldata):

    See for details.


    hashtag
    Async Patterns

    hashtag
    Concurrent Reads

    hashtag
    Read with Timeout


    hashtag
    See Also

    • — Full workflow and examples

    • — Security parameter reference

    • — Encrypted writes

    create_async_wallet_client

    Create async Web3 instance with full Seismic wallet capabilities

    Create an asynchronous AsyncWeb3 instance with full Seismic wallet capabilities.

    hashtag
    Overview

    create_async_wallet_client() is the async factory function for creating a client that can perform shielded writes, signed reads, and deposits. It supports both HTTP and WebSocket connections, fetches the TEE public key asynchronously, derives encryption state via ECDHarrow-up-right, and attaches a fully-configured w3.seismic namespace.

    The returned client works with all standard async web3.py APIs (await w3.eth.get_block(), etc.) plus the additional w3.seismic namespace for Seismic-specific operations.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Type
    Description

    hashtag
    Examples

    hashtag
    Basic Usage (HTTP)

    hashtag
    WebSocket Connection

    hashtag
    Using Chain Configuration

    hashtag
    Context Manager Pattern

    hashtag
    Async Application

    hashtag
    With Custom Encryption Key

    hashtag
    How It Works

    The function performs six steps:

    1. Create provider

    2. Create AsyncWeb3 instance

    3. Fetch TEE public key (async RPC call)

    hashtag
    Client Capabilities

    The returned client provides:

    hashtag
    Standard AsyncWeb3 Methods (e.g. w3.eth, w3.net)

    • await get_block(), await get_transaction(), await get_balance()

    • await send_raw_transaction(), await wait_for_transaction_receipt()

    hashtag
    Async Seismic Methods (w3.seismic)

    • - Send shielded transactions

    • - Debug shielded transactions

    • - Execute signed reads

    hashtag
    HTTP vs WebSocket

    hashtag
    Encryption

    The client automatically:

    • Fetches the network's TEE public key asynchronously

    • Performs ECDH key exchange using encryption_sk (or generates a random one)

    • Derives a shared AES-GCM key via HKDF

    Access the encryption state at w3.seismic.encryption if needed for advanced use cases.

    hashtag
    Notes

    • The function is async and must be await-ed

    • Makes one asynchronous RPC call to fetch the TEE public key

    • If encryption_sk is None

    hashtag
    Warnings

    • Private key security - Never log or expose private keys. Use environment variables or secure key management

    • Connection cleanup - Close WebSocket connections properly to avoid resource leaks

    • Error handling - WebSocket connections can drop; implement reconnection logic for production

    hashtag
    See Also

    • - Sync variant (HTTP only)

    • - Async read-only client

    • - Encryption state class

    AppKit

    Set up AppKit (WalletConnect) with Seismic

    AppKit (formerly Web3Modal) by WalletConnect provides a wallet connection modal with support for 300+ wallets. This guide shows how to integrate AppKit with Seismic React.

    hashtag
    Prerequisites

    circle-exclamation

    You need a WalletConnect Project ID from .

    hashtag
    Step 1: Configure wagmi with AppKit

    Create a wagmi adapter and initialize AppKit with Seismic chains:

    hashtag
    Step 2: Set Up Providers

    Nest the providers with ShieldedWalletProvider inside:

    circle-info

    AppKit does not require a wrapper provider component like RainbowKit or Privy. The createAppKit call initializes it globally, so ShieldedWalletProvider goes directly inside QueryClientProvider.

    hashtag
    Step 3: Add the Connect Button

    AppKit provides a web component for the connect button:

    circle-info

    If you are using TypeScript, you may need to declare the web component type. Add this to a .d.ts file in your project:

    hashtag
    Step 4: Use Shielded Hooks

    Once connected, use seismic-react hooks to interact with shielded contracts:

    hashtag
    Complete Example

    hashtag
    See Also

    • -- Comparison of wallet libraries

    • -- Provider reference and options

    • -- Access shielded wallet context

    .tread

    Transparent read namespace for standard contract calls

    The .tread namespace provides standard (non-encrypted) contract read operations using eth_call. Use this for public queries where privacy is not required.


    hashtag
    Overview

    When you call contract.tread.functionName(...)

    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-viem
    SeismicSecurityParams
    Signs the transaction with your private key
  • Sends the signed transaction to eth_call endpoint

  • Node decrypts calldata inside TEE, executes the call, encrypts the result

  • Returns encrypted result (which the SDK can decrypt if needed)

  • No outputs defined in ABI: returns None

  • ): a
    tuple
    of decoded values
  • No outputs defined in ABI: None

  • Uses msg.sender in any way

    Transaction metadata (block hash, expiry, encryption nonce)

    Access-controlled reads

    Public reads

    Privacy is required (both query and result)

    Consider caching read results if called frequently

    .tread Namespace — Transparent reads without encryption
  • ShieldedContract — Contract wrapper reference

  • value

    int

    0

    ETH value for the call (in wei)

    gas

    int

    30_000_000

    Gas limit for the call

    security

    SeismicSecurityParams | None

    None

    Feature

    .read (Signed)

    .tread (Transparent)

    Calldata encryption

    Yes

    No

    Result encryption

    Yes

    No

    Proves identity

    Yes (msg.sender is your address)

    No (msg.sender is 0x0)

    Gas cost

    None (doesn't broadcast)

    None (doesn't broadcast)

    Signed Read Guide
    Signed Read Guide
    SeismicSecurityParams
    .write Namespace

    Custom security parameters (block expiry, nonce, etc.)

    Use case

    RainbowKit Guide -- Polished wallet modal alternative
  • Privy Guide -- Email/social login alternative

  • WalletConnect Cloudarrow-up-right
    Wallet Guides Overview
    ShieldedWalletProvider
    useShieldedWallet

    ws

    bool

    No

    If True, uses WebSocketProvider (persistent connection, supports subscriptions). Otherwise uses AsyncHTTPProvider. Default: False. WebSocket is only available on async clients — sync clients are HTTP-only

    Generate encryption keypair (if encryption_sk is None, a random ephemeral key is created)
  • Derive encryption state (ECDH + HKDFarrow-up-right)

  • Attach Seismic namespace

  • All other standard async web3.py functionality

    await deposit() - Deposit ETH/tokens
  • await get_tee_public_key() - Get TEE public key

  • await get_deposit_root() - Query deposit merkle root

  • await get_deposit_count() - Query deposit count

  • contract() - Create contract wrappers (methods are async)

  • One-off transactions

    Real-time monitoring, subscriptions

    Uses this key to encrypt all shielded transaction calldata and signed reads
    , a random ephemeral key is generated
  • The encryption key is separate from the transaction signing key

  • WebSocket connections should be properly closed when done

  • All w3.seismic methods are async and must be await-ed

  • For sync operations, use create_wallet_client()

  • HTTPS/WSS recommended - Use secure protocols in production to prevent MITM attacks

    get_encryption - Encryption derivation function
  • AsyncSeismicNamespace - The async w3.seismic namespace

  • Chains Configuration - Pre-configured chain constants

  • provider_url

    str

    Yes

    HTTP(S) or WS(S) URL of the Seismic node

    private_key

    PrivateKey

    Yes

    32-byte secp256k1 private key for signing transactions

    encryption_sk

    PrivateKey

    No

    AsyncWeb3

    An AsyncWeb3 instance with w3.seismic namespace attached (AsyncSeismicNamespace)

    Aspect

    AsyncHTTPProvider (ws=False)

    WebSocketProvider (ws=True)

    Connection

    New connection per request

    Persistent connection

    Latency

    Higher per-request overhead

    Lower latency

    Subscriptions

    Not supported

    Supported (eth.subscribe)

    Resource usage

    Lower idle usage

    Keeps connection open

    await send_shielded_transaction()
    await debug_send_shielded_transaction()
    await signed_call()
    create_wallet_client
    create_async_public_client
    EncryptionState

    Optional 32-byte key for ECDH. If None, a random ephemeral key is generated

    Use case

    result = 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()        # str
    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)
    
    # 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,
    ) -> AsyncWeb3
    import 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 w3
    if 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))
    , the SDK:
    1. Encodes your function call using the contract ABI

    2. Constructs a standard eth_call request

    3. Executes the call against the node's state

    4. ABI-decodes the result and returns Python values

    No encryption is applied — calldata and results are visible to anyone observing the request.


    hashtag
    Usage Pattern

    • Sync: Returns decoded Python value immediately

    • Async: Returns decoded Python value (must await)

    Return values are automatically ABI-decoded:

    • Single output (e.g. returns (uint256)): returns the value directly (int, bool, str, etc.)

    • Multiple outputs (e.g. returns (uint256, bool)): returns a tuple

    • No outputs defined in ABI: returns None


    hashtag
    Parameters

    hashtag
    Function Arguments

    Pass function arguments as positional parameters:

    hashtag
    No Transaction Options

    Unlike .read, .tread does not accept keyword arguments like value, gas, or security.

    The call uses default parameters:

    • No value transfer

    • Reasonable gas limit (set by node)

    • No special security parameters


    hashtag
    Examples

    hashtag
    Sync Usage

    hashtag
    Async Usage

    hashtag
    Reading with Arguments

    hashtag
    Multiple Return Values

    hashtag
    Array and Struct Returns


    hashtag
    Return Value

    Returns the ABI-decoded Python value:

    • Single output (e.g. returns (uint256)): the value directly — int, bool, str, bytes, etc.

    • Multiple outputs (e.g. returns (uint112, uint112, uint32)): a tuple of decoded values

    • No outputs defined in ABI: None


    hashtag
    Key Limitation: No msg.sender

    Standard eth_call does not prove your identity. The contract sees msg.sender as 0x0000000000000000000000000000000000000000.

    hashtag
    Problem Example

    SRC20's balanceOf() takes no arguments and returns the caller's balance:

    With transparent read (.tread):

    With signed read (.read):

    hashtag
    When .tread Works

    Use .tread when the contract function:

    • Does not check msg.sender

    • Is purely computational (no authentication)

    • Returns public data

    Examples:

    hashtag
    When .tread Fails

    Use .read when the contract function:

    • Checks msg.sender for permissions

    • Returns caller-specific data

    • Has any access control

    Examples:


    hashtag
    Privacy Implications

    hashtag
    What's Visible

    Everything is visible:

    • Your IP address (to the node you're querying)

    • Contract address (what contract you're reading)

    • Function selector (which function you're calling)

    • All function arguments

    • The result returned

    hashtag
    Example

    The node (and anyone monitoring) can see:

    • You're checking the balance of 0x1234...

    • Which contract you're querying

    • The result (the balance amount)


    hashtag
    When to Use .tread

    hashtag
    Good Use Cases

    • Public data queries — Total supply, token name, public balances

    • No authentication needed — Function doesn't check msg.sender

    • Cost optimization — Free (no gas cost, no encryption overhead)

    • Public explorers — Block explorers, analytics, public dashboards

    • Testing — Quick tests where privacy doesn't matter

    hashtag
    Examples


    hashtag
    When NOT to Use .tread

    hashtag
    Use .read Instead When

    • Function checks msg.sender

    • Caller-specific data is needed

    • Privacy is required

    • Access control is involved

    hashtag
    Examples (Use .read for these)


    hashtag
    Comparison with Other Namespaces

    Namespace
    Encryption
    Proves Identity
    Gas Cost
    Use Case

    .read

    Encrypted

    Yes (msg.sender is your address)

    None

    Access-controlled reads

    .tread

    No encryption

    No (msg.sender is 0x0)

    None

    Public reads


    hashtag
    Error Handling


    hashtag
    Best Practices

    hashtag
    When to Choose .tread

    Checklist:

    hashtag
    When to Choose .read

    Checklist:

    hashtag
    Common Mistakes

    Mistake: Using .tread for caller-specific functions

    Fix: Use .read to prove your identity


    hashtag
    Async Patterns

    hashtag
    Concurrent Reads

    hashtag
    Read with Timeout


    hashtag
    Standard Web3.py Behavior

    The .tread namespace uses standard eth_call under the hood. All web3.py call features work:

    hashtag
    Block Number

    You can specify a block number (though not directly via .tread):


    hashtag
    Low-Level Alternative

    Direct eth_call:

    The .tread namespace is more convenient as it handles ABI encoding automatically.


    hashtag
    Performance

    .tread is the fastest read method:

    • No encryption overhead

    • No signature computation

    • Direct eth_call to node

    • Can be cached/memoized easily

    Use it for:

    • Public data that changes infrequently

    • High-frequency queries (price feeds, balances)

    • Analytics and dashboards


    hashtag
    See Also

    • .read Namespace — Encrypted signed reads with msg.sender

    • .twrite Namespace — Transparent writes

    • .write Namespace — Encrypted writes

    • — Contract wrapper reference

    • — Standard Ethereum calls

    Precompiles

    Seismic precompiled contracts for cryptographic operations

    Seismic extends the EVM with precompiled contracts for common cryptographic operations. These are available at fixed addresses and can be called from any client with a .call() method -- either a ShieldedPublicClient or a ShieldedWalletClient.

    hashtag
    Import

    hashtag
    Overview

    Precompile
    Address
    Description
    Input
    Output
    circle-info

    Precompile calls execute within the TEE, ensuring cryptographic operations are performed in a secure environment. The inputs and outputs are transmitted over the encrypted channel established during client construction.

    hashtag
    RNG -- Random Number Generation

    Generates cryptographically secure random numbers using the TEE's CSPRNG.

    hashtag
    Standalone Function

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    bigint -- the generated random value.

    hashtag
    Example


    hashtag
    ECDH -- Elliptic Curve Diffie-Hellman

    Performs an ECDH key exchange inside the TEE and returns the shared secret.

    hashtag
    Standalone Function

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Hex -- 32-byte shared secret.

    hashtag
    Example


    hashtag
    AES-GCM Encrypt / Decrypt

    Performs AES-GCM encryption and decryption inside the TEE.

    hashtag
    Encrypt

    hashtag
    Encrypt Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Encrypt Returns

    Hex -- the AES-GCM ciphertext.

    hashtag
    Decrypt

    hashtag
    Decrypt Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Decrypt Returns

    string -- the decrypted plaintext.

    hashtag
    Full Example


    hashtag
    HKDF -- Key Derivation

    Derives a key from input key material using HKDF inside the TEE.

    hashtag
    Standalone Function

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Hex -- the derived key.

    hashtag
    Example


    hashtag
    secp256k1 Signature

    Generates a secp256k1 signature inside the TEE.

    hashtag
    Standalone Function

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Signature -- a viem Signature object with r, s, and v components.

    hashtag
    Example


    hashtag
    Using Precompiles via Client

    All precompiles are available as methods directly on ShieldedPublicClient and ShieldedWalletClient, so you can call them without importing the standalone functions.

    Client Method
    Standalone Function
    Description

    hashtag
    Example

    hashtag
    Custom Precompile Pattern

    Each precompile is defined as a Precompile<P, R> object with a standard interface. You can invoke any precompile directly using its object.

    hashtag
    Precompile<P, R> Type

    Property
    Type
    Description

    hashtag
    Available Precompile Objects

    Export
    Type

    The CallClient type accepts any client with a .call() method, so both ShieldedPublicClient and ShieldedWalletClient work with callPrecompile.

    hashtag
    See Also

    • -- Read-only client with precompile methods

    • -- Full-featured client with precompile methods

    • -- Client-side ECDH key exchange and AES-GCM encryption

    create_public_client

    Create sync Web3 instance with public (read-only) Seismic access

    Create a synchronous Web3 instance with public (read-only) Seismic access.

    hashtag
    Overview

    create_public_client() creates a client for read-only operations on the Seismic network. No private key is required. The w3.seismic namespace provides only public read operations: get_tee_public_key(), get_deposit_root(), get_deposit_count(), and contract() (with .tread only).

    This is useful for applications that only need to query chain state without submitting transactions, such as block explorers, analytics dashboards, or read-only dApps.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    hashtag
    Returns

    Type
    Description

    hashtag
    Examples

    hashtag
    Basic Usage

    hashtag
    Using Chain Configuration

    hashtag
    Read-Only Contract Access

    hashtag
    Standard Web3 Operations

    hashtag
    Block Explorer Pattern

    hashtag
    How It Works

    The function performs two steps:

    1. Create Web3 instance

    2. Attach public Seismic namespace

    No TEE public key fetching or encryption setup is performed since the client cannot perform shielded operations.

    hashtag
    Client Capabilities

    hashtag
    Standard Web3 Methods (e.g. w3.eth, w3.net)

    • get_block(), get_transaction(), get_balance()

    • get_code(), call(), estimate_gas()

    hashtag
    Public Seismic Methods (w3.seismic)

    • - Get TEE public key

    • - Query deposit merkle root

    • - Query deposit count

    hashtag
    NOT Available

    • - Requires private key

    • - Requires private key

    • - Requires private key and encryption

    hashtag
    Public vs Wallet Client

    Feature
    Public Client
    Wallet Client

    hashtag
    Notes

    • HTTP only — Sync clients use Web3 with HTTPProvider, which does not support WebSocket connections. This is a limitation of the underlying web3.py library (WebSocketProvider is async-only). If you need WebSocket support (persistent connections, subscriptions), use with ws=True

    • No private key required or accepted

    hashtag
    Use Cases

    • Block explorers and chain analytics

    • Read-only dApps that display public data

    • Monitoring and alerting systems

    hashtag
    See Also

    • - Async variant with WebSocket support

    • - Full-featured client with private key

    • - The public w3.seismic namespace

    .dwrite

    Debug write namespace for encrypted transactions with inspection

    The .dwrite namespace provides encrypted contract write operations with debug inspection. It broadcasts a real transaction (like .write) but also returns both plaintext and encrypted views for debugging and testing.


    hashtag
    Overview

    When you call contract.dwrite.functionName(...)

    Storage

    hashtag
    How Shielded Storage Works

    Seismic extends the EVM storage model with FlaggedStorage. Every storage slot is represented as a pair:

    The is_private flag determines whether a slot holds public or confidential data. This flag is set automatically by the compiler based on the types you use, and enforced at the opcode level:

    Shielded Balances and Transfers

    Implement transfer() and transferFrom() with suint256

    This chapter walks through the full implementation of shielded transfers, allowances, and minting. You will also write tests using sforge to verify everything works. Estimated time: ~20 minutes.

    hashtag
    Shielded balanceOf

    The core change is in the mapping declaration:

    At the storage level, this is where Seismic's

    create_wallet_client

    Create sync Web3 instance with full Seismic wallet capabilities

    Create a synchronous Web3 instance with full Seismic wallet capabilities.

    hashtag
    Overview

    create_wallet_client() is the primary factory function for creating a sync client that can perform shielded writes, signed reads, and deposits. It fetches the TEE public key, derives encryption state via , and attaches a fully-configured namespace to a standard Web3

    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 Instance
    Web3.py Documentationarrow-up-right

    0x...0066

    AES-GCM encryption

    aesKey, nonce, plaintext

    Hex (ciphertext)

    aesGcmDecrypt

    0x...0067

    AES-GCM decryption

    aesKey, nonce, ciphertext

    string (plaintext)

    hdfk

    0x...0068

    HKDF key derivation

    ikm

    Hex (derived key)

    secp256k1Sig

    0x...0069

    secp256k1 signing

    sk, message

    Signature

    client.hdfk(ikm)

    hdfk(client, ikm)

    HKDF key derivation

    client.secp256k1Signature(params)

    secp256k1Sig(client, params)

    secp256k1 signing

    Chains -- Chain configurations for Seismic networks

    rng

    0x...0064

    Random number generation

    numBytes, pers?

    bigint

    ecdh

    0x...0065

    ECDH key exchange

    sk, pk

    Hex (shared secret)

    numBytes

    bigint | number

    Yes

    Number of random bytes to generate (1--32)

    pers

    Hex | ByteArray

    No

    Personalization string to seed the CSPRNG

    sk

    Hex

    Yes

    32-byte secp256k1 secret key

    pk

    Hex

    Yes

    33-byte compressed secp256k1 public key

    aesKey

    Hex

    Yes

    32-byte AES-256 key

    nonce

    number

    Yes

    Numeric nonce for AES-GCM

    plaintext

    string

    Yes

    aesKey

    Hex

    Yes

    32-byte AES-256 key

    nonce

    number

    Yes

    Nonce used during encryption

    ciphertext

    Hex

    Yes

    ikm

    Hex

    Yes

    Input key material

    sk

    Hex

    Yes

    32-byte secp256k1 secret key

    message

    string

    Yes

    Message to sign

    client.rng(params)

    rng(client, params)

    Random number generation

    client.ecdh(params)

    ecdh(client, params)

    ECDH key exchange

    client.aesGcmEncryption(params)

    aesGcmEncrypt(client, params)

    AES-GCM encryption

    client.aesGcmDecryption(params)

    aesGcmDecrypt(client, params)

    address

    Hex

    Fixed address of the precompile contract

    gasCost(args)

    (args: P) => bigint

    Computes the gas cost for the given arguments

    encodeParams(args)

    (args: P) => Hex

    ABI-encodes the arguments for the call

    decodeResult(result)

    (result: Hex) => R

    Decodes the raw call result

    rngPrecompile

    Precompile<RngParams, bigint>

    ecdhPrecompile

    Precompile<EcdhParams, Hex>

    aesGcmEncryptPrecompile

    Precompile<AesGcmEncryptionParams, Hex>

    aesGcmDecryptPrecompile

    Precompile<AesGcmDecryptionParams, string>

    hdfkPrecompile

    Precompile<Hex, Hex>

    secp256k1SigPrecompile

    Precompile<Secp256K1SigParams, Signature>

    Shielded Public Client
    Shielded Wallet Client
    Encryption

    aesGcmEncrypt

    Plaintext string to encrypt

    Ciphertext to decrypt

    AES-GCM decryption

    All other standard read-only web3.py functionality

    contract() - Create contract wrappers (.tread only)
    deposit() - Requires private key
  • Contract .swrite and .sread methods - Require private key

  • Deposits

    No

    Yes

    TEE queries

    Yes

    Yes

    Standard Web3

    All read operations

    All operations

    No encryption setup performed

  • No RPC calls during client creation (lightweight)

  • Cannot perform any write operations or shielded reads

  • Contract wrappers only expose .tread (transparent read)

  • For write operations, use create_wallet_client()

  • For async operations, use create_async_public_client()

  • Price oracles and data aggregators
  • Public dashboards and visualizations

  • Testing and validation tools

  • Chains Configuration - Pre-configured chain constants

  • Contract Instances - Working with contract wrappers

  • rpc_url

    str

    Yes

    HTTP(S) URL of the Seismic node (e.g., "https://gcp-1.seismictest.net/rpc"). WebSocket URLs are not supported — see note below

    Web3

    A Web3 instance with w3.seismic namespace attached (SeismicPublicNamespace)

    Private key

    Not required

    Required

    Shielded writes

    No

    Yes

    Signed reads

    No

    Yes

    Transparent reads

    Yes

    get_tee_public_key()
    get_deposit_root()
    get_deposit_count()
    send_shielded_transaction()
    debug_send_shielded_transaction()
    signed_call()
    create_async_public_client()
    create_async_public_client
    create_wallet_client
    SeismicPublicNamespace

    Yes

    FlaggedStorage
    comes in. Each storage slot is a tuple of
    (value, is_private)
    . When the compiler sees
    suint256
    , it emits
    CSTORE
    to write and
    CLOAD
    to read, setting the
    is_private
    flag to
    true
    . This means:
    • eth_getStorageAt calls for these slots will fail. External observers cannot read the raw storage.

    • Only CLOAD can access private slots. The standard SLOAD opcode cannot reach them.

    • Anyone inspecting the state trie, transaction traces, or block data sees 0x00...0 in place of the actual balance.

    The developer does not interact with FlaggedStorage directly. The type annotation handles everything.

    hashtag
    transfer()

    Here is the full transfer implementation with shielded amounts:

    hashtag
    What happens at each stage

    1. Calldata submission -- The user sends a Seismic transaction (type 0x4A). The amount parameter is encrypted before it leaves their machine. Observers watching the mempool see 0x00...0 in place of the amount.

    2. Execution inside the TEE -- The Seismic node, running inside Intel TDX, decrypts the calldata. The require check runs against the shielded balance. The subtraction and addition execute normally. All intermediate values involving suint256 are shielded in the trace.

    3. Storage update -- The new balances are written via CSTORE. Both the sender's and recipient's balance slots have is_private = true.

    4. Observer view -- Anyone querying the contract or reading the block sees 0x00...0 for the amount, the sender's balance, and the recipient's balance.

    Comparisons (>=) and arithmetic (-=, +=) work the same on suint256 as on uint256. Solidity 0.8+ overflow checks also work, so if a user tries to transfer more than their balance, the transaction reverts as expected.

    hashtag
    transferFrom()

    The full implementation with shielded allowances:

    The allowance mapping stores suint256 values:

    The pattern is identical to a standard ERC20 transferFrom. The only difference is the type. The allowance check, the allowance deduction, and the balance updates all use shielded arithmetic. An observer cannot see how much allowance was granted, how much was consumed, or how much remains.

    hashtag
    approve()

    Setting shielded allowances:

    The approved amount is stored as suint256. No one can query how much a spender is authorized to transfer on behalf of the owner -- that information is shielded in storage. The Approval event above casts the amount to uint256 for the log. If you need the approved amount to be private in the event as well, see the Encrypted Events chapter.

    hashtag
    The mint pattern

    Here is a mint function that assigns new tokens:

    There is a design decision here: totalSupply is public. It is a regular uint256, so the aggregate supply is visible. This is usually desirable -- users and markets want to know how many tokens exist. However, individual balances remain shielded. An observer knows the total supply increased, but cannot see which address received the tokens or how they are distributed.

    If you want even the total supply to be private, you can change it to suint256:

    But keep in mind that suint256 state variables cannot be public, so you would need to provide a view function that determines who can view it via signed reads.

    hashtag
    Constructor minting

    The simplest approach is to mint the entire initial supply in the constructor:

    The explicit cast suint256(_initialSupply) is required because _initialSupply is a regular uint256. Seismic enforces explicit casting between shielded and unshielded types.

    hashtag
    Testing with sforge

    Create a test file at test/SRC20.t.sol:

    hashtag
    Test: basic transfer

    circle-info

    In sforge tests, the test contract runs inside the same execution context. You can read shielded values by adding an internal helper function to your contract (or using the test contract's access). In production, shielded values are only accessible through signed reads.

    To support this test, add a test-only helper to your contract:

    hashtag
    Test: transfer reverts on insufficient balance

    hashtag
    Test: approve and transferFrom

    hashtag
    Test: transferFrom reverts on insufficient allowance

    hashtag
    Running the tests

    Build and run from your contracts directory:

    You should see all tests passing. The shielded types behave identically to their unshielded counterparts in terms of arithmetic and comparison logic -- the privacy is handled transparently at the storage layer.

    import { rng } from "seismic-viem";
    
    const randomValue = await rng(client, {
      numBytes: 32,
    });
    
    console.log("Random value:", randomValue);
    import { createShieldedPublicClient } from "seismic-viem";
    import { rng } from "seismic-viem";
    import { seismicTestnet } from "seismic-viem";
    import { http } from "viem";
    
    const client = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    
    // Generate 32 random bytes
    const randomValue = await rng(client, { numBytes: 32 });
    console.log("Random 256-bit value:", randomValue);
    
    // Generate 16 random bytes with a personalization string
    const seededValue = await rng(client, {
      numBytes: 16,
      pers: "0x6d79617070",
    });
    console.log("Seeded random value:", seededValue);
    import { ecdh } from "seismic-viem";
    
    const sharedSecret = await ecdh(client, {
      sk: "0x...", // 32-byte secret key
      pk: "0x...", // 33-byte compressed public key
    });
    
    console.log("Shared secret:", sharedSecret);
    import { ecdh } from "seismic-viem";
    
    const sharedSecret = await ecdh(client, {
      sk: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
      pk: "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
    });
    
    console.log("Shared secret:", sharedSecret);
    import { aesGcmEncrypt } from "seismic-viem";
    
    const ciphertext = await aesGcmEncrypt(client, {
      aesKey: "0x...", // 32-byte AES key
      nonce: 1, // numeric nonce
      plaintext: "hello world",
    });
    
    console.log("Ciphertext:", ciphertext);
    import { aesGcmDecrypt } from "seismic-viem";
    
    const plaintext = await aesGcmDecrypt(client, {
      aesKey: "0x...", // same 32-byte AES key
      nonce: 1, // same nonce used for encryption
      ciphertext: "0x...",
    });
    
    console.log("Plaintext:", plaintext);
    import { aesGcmEncrypt, aesGcmDecrypt, rng } from "seismic-viem";
    
    // Generate a random AES key using the RNG precompile
    const aesKeyRaw = await rng(client, { numBytes: 32 });
    const aesKey = `0x${aesKeyRaw.toString(16).padStart(64, "0")}` as const;
    
    // Encrypt
    const ciphertext = await aesGcmEncrypt(client, {
      aesKey,
      nonce: 1,
      plaintext: "secret message",
    });
    
    // Decrypt
    const plaintext = await aesGcmDecrypt(client, {
      aesKey,
      nonce: 1,
      ciphertext,
    });
    
    console.log("Decrypted:", plaintext); // "secret message"
    import { hdfk } from "seismic-viem";
    
    const derivedKey = await hdfk(client, "0x..."); // input key material
    
    console.log("Derived key:", derivedKey);
    import { hdfk } from "seismic-viem";
    
    const inputKeyMaterial = "0xdeadbeef";
    const derivedKey = await hdfk(client, inputKeyMaterial);
    console.log("Derived key:", derivedKey);
    import { secp256k1Sig } from "seismic-viem";
    
    const signature = await secp256k1Sig(client, {
      sk: "0x...", // 32-byte secret key
      message: "hello", // message to sign
    });
    
    console.log("Signature:", signature);
    import { secp256k1Sig } from "seismic-viem";
    
    const signature = await secp256k1Sig(client, {
      sk: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
      message: "sign this message",
    });
    
    console.log("r:", signature.r);
    console.log("s:", signature.s);
    console.log("v:", signature.v);
    import { createShieldedPublicClient } from "seismic-viem";
    import { seismicTestnet } from "seismic-viem";
    import { http } from "viem";
    
    const client = createShieldedPublicClient({
      chain: seismicTestnet,
      transport: http(),
    });
    
    // Call precompiles as client methods
    const randomValue = await client.rng({ numBytes: 32 });
    
    const sharedSecret = await client.ecdh({
      sk: "0x...",
      pk: "0x...",
    });
    
    const ciphertext = await client.aesGcmEncryption({
      aesKey: "0x...",
      nonce: 1,
      plaintext: "hello",
    });
    
    const derivedKey = await client.hdfk("0x...");
    def create_public_client(rpc_url: str) -> Web3
    from 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 client
    from 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 test
    , the SDK:
    1. Encodes your function call using the contract ABI (plaintext)

    2. Encrypts the calldata using AES-GCM with a shared key derived via ECDH

    3. Constructs a TxSeismic with encryption metadata

    4. Signs and broadcasts the encrypted transaction

    5. Returns a DebugWriteResult containing:

      • plaintext_tx — Transaction before encryption

      • shielded_tx — Transaction with encrypted calldata

    Important: The transaction is actually broadcast and consumes gas. This is not a dry run.


    hashtag
    Usage Pattern

    • Sync: Returns DebugWriteResult immediately

    • Async: Returns DebugWriteResult (must await)


    hashtag
    Parameters

    hashtag
    Function Arguments

    Pass function arguments as positional parameters:

    hashtag
    Transaction Options (Keyword Arguments)

    All transaction options are optional keyword arguments (same as .write):

    Parameter
    Type
    Default
    Description

    value

    int

    0

    ETH value to send (in wei)

    gas

    int | None

    None

    Gas limit (30_000_000 if None)

    gas_price

    int | None

    None


    hashtag
    Return Value

    Returns a DebugWriteResult with three fields:

    hashtag
    Field Details

    Field
    Type
    Description

    plaintext_tx

    PlaintextTx

    Transaction with unencrypted calldata (what you intended)

    shielded_tx

    UnsignedSeismicTx

    Full TxSeismic structure with encrypted calldata

    tx_hash

    HexBytes

    Transaction hash from eth_sendRawTransaction


    hashtag
    Examples

    hashtag
    Sync Usage

    hashtag
    Async Usage

    hashtag
    Inspect Plaintext Calldata

    hashtag
    Verify Encryption

    hashtag
    Inspect Transaction Parameters

    hashtag
    Analyze Gas Limit vs Usage

    hashtag
    Compare with Production .write


    hashtag
    Use Cases

    hashtag
    Development and Testing

    hashtag
    Debugging Encryption

    hashtag
    Auditing Transaction Details

    hashtag
    Testing Security Parameters


    hashtag
    Important Warnings

    hashtag
    Transaction is Actually Broadcast

    .dwrite is NOT a dry run:

    • The encrypted transaction is broadcast

    • Gas is consumed

    • State changes are applied

    • You pay transaction fees

    hashtag
    Use in Production

    Don't use .dwrite in production:

    • Adds overhead (returns extra data)

    • No benefit over .write for end users

    • Only useful for development/debugging

    For production, use .write:

    hashtag
    Cost Implications

    .dwrite has the same gas cost as .write:

    • Same transaction is broadcast

    • Same encryption overhead

    • Only difference is SDK returns debug info (no on-chain difference)


    hashtag
    Comparison with Other Namespaces

    Namespace
    Encryption
    Returns
    Transaction Broadcast
    Use Case

    .write

    Yes

    HexBytes (tx hash)

    Yes

    Production shielded writes

    .dwrite

    Yes

    DebugWriteResult

    Yes

    Development/debugging


    hashtag
    Privacy Guarantees

    hashtag
    Identical to .write

    .dwrite has the same privacy guarantees as .write:

    • Calldata is encrypted on-chain

    • Only you and the TEE can see plaintext

    • Debug info is only in the SDK return value (not on-chain)

    hashtag
    Debug Info is Client-Side

    The plaintext_tx and shielded_tx fields are not broadcast:

    • Only the encrypted transaction is sent

    • Debug info exists only in your client

    • No privacy leak from using .dwrite


    hashtag
    Error Handling


    hashtag
    Best Practices

    hashtag
    When to Use .dwrite

    • Development — Verify calldata encoding is correct

    • Testing — Debug encryption/decryption issues

    • Auditing — Inspect transaction parameters before production

    • Debugging — Troubleshoot failed transactions

    hashtag
    When to Use .write Instead

    • Production — No need for debug info

    • High-frequency calls — Avoid overhead of extra return data

    • Simple operations — Debug info not useful

    hashtag
    Testing Workflow


    hashtag
    Low-Level Alternative

    For manual control with debug info:

    See Shielded Write Guide for details.


    hashtag
    See Also

    • .write Namespace — Production shielded writes (no debug info)

    • DebugWriteResult — Return type reference

    • PlaintextTx — Plaintext transaction structure

    • — Shielded transaction structure

    • — Full encryption workflow

    SSTORE / SLOAD operate on public storage slots (the standard EVM behavior).

  • CSTORE (0xB1) / CLOAD (0xB0) operate on confidential storage slots.

  • When you declare a shielded variable (e.g., suint256), the compiler generates CSTORE and CLOAD instructions instead of SSTORE and SLOAD. This happens automatically -- you do not need to manage opcodes yourself.

    hashtag
    Access Control Rules

    The FlaggedStorage model enforces strict separation between public and confidential data:

    Operation
    Result

    SLOAD on a public slot

    Returns the value

    SLOAD on a private slot

    Reverts

    CLOAD on a private slot

    Returns the value

    CLOAD on a public slot

    Returns the value

    SSTORE to a public slot

    Marks the slot as public

    SSTORE to a private slot

    Reverts

    This means that if an external contract or observer uses SLOAD to read a shielded storage slot, the operation will revert. CLOAD can access both private and public slots — the compiler generates CLOAD for all shielded type access.

    hashtag
    Whole Slot Consumption

    Shielded types consume an entire 32-byte storage slot, regardless of their actual size. A suint64, which only needs 8 bytes, still occupies a full slot.

    This is a deliberate design choice. In standard Solidity, the compiler packs multiple small variables into a single slot to save gas. With shielded types, packing is not done for two reasons: a storage slot must be entirely private or entirely public (mixing would break the confidentiality model), and packing would leak the size of the shielded value (see Gas Considerations below).

    hashtag
    Storage Layout Comparison

    In standard Solidity, small types are packed together:

    With shielded types, each field gets its own slot:

    This means shielded contracts consume more storage slots than their unshielded equivalents. Plan your contract's storage layout accordingly.

    hashtag
    Gas Considerations

    CLOAD and CSTORE have a constant gas cost regardless of the value being read or written. This is a critical privacy property.

    In the standard EVM, certain storage operations can have variable gas costs (e.g., writing a nonzero value to a slot that previously held zero costs more than overwriting an existing nonzero value). If CLOAD and CSTORE had similar variable costs, an observer could infer information about shielded values by analyzing gas consumption.

    By making gas costs constant, Seismic prevents this class of information leakage. No matter what value is being stored or loaded, the gas cost is the same.

    circle-exclamation

    While CLOAD and CSTORE themselves have constant gas cost, other operations on shielded values (such as loops, conditionals, and exponentiation) can still leak information through gas. See Be Mindful of Gas-Based Information Leakage for details.

    hashtag
    Manual Slot Packing

    If you need to pack multiple shielded values into a single slot for efficiency, you can do so using inline assembly. However, this is an advanced technique and carries significant risk.

    When using inline assembly for slot packing:

    • You must ensure all values packed into a single slot share the same confidentiality level.

    • Incorrect packing can introduce vulnerabilities where private data is partially exposed or corrupted.

    • The compiler cannot verify the correctness of your assembly-level storage operations.

    triangle-exclamation

    Manual slot packing bypasses compiler safety checks. Use it only when absolutely necessary, and audit thoroughly. A mistake here can silently break your contract's privacy guarantees.

    hashtag
    Future Improvements

    Compiler-level slot packing for shielded types is planned for a future release. This will allow the compiler to automatically pack multiple shielded values of compatible sizes into a single confidential slot, reducing storage costs without requiring manual assembly.

    Until then, each shielded variable consumes its own full slot, and manual packing via assembly is the only alternative.

    instance.

    The returned client works with all standard web3.py APIs (w3.eth.get_block(), w3.eth.send_raw_transaction(), etc.) plus the additional w3.seismic namespace for Seismic-specific operations.

    hashtag
    Signature

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    rpc_url

    str

    Yes

    HTTP(S) URL of the Seismic node (e.g., "https://gcp-1.seismictest.net/rpc"). WebSocket URLs are not supported — see note below

    private_key

    Yes

    32-byte secp256k1 private key for signing transactions

    encryption_sk

    No

    hashtag
    Returns

    Type
    Description

    Web3

    A Web3 instance with w3.seismic namespace attached ()

    hashtag
    Examples

    hashtag
    Basic Usage

    hashtag
    Using Chain Configuration

    hashtag
    With Custom Encryption Key

    hashtag
    Standard Web3 Operations

    hashtag
    How It Works

    The function performs five steps:

    1. Create Web3 instance

    2. Fetch TEE public key (synchronous RPC call)

    3. Generate encryption keypair (if encryption_sk is None, a random ephemeral key is created)

    4. Derive encryption state (ECDH + )

    5. Attach Seismic namespace

    hashtag
    Client Capabilities

    The returned client provides:

    hashtag
    Standard Web3 Methods (e.g. w3.eth, w3.net)

    • get_block(), get_transaction(), get_balance()

    • send_raw_transaction(), wait_for_transaction_receipt()

    • All other standard web3.py functionality

    hashtag
    Seismic Methods (w3.seismic)

    • send_shielded_transaction() - Send shielded transactions

    • debug_send_shielded_transaction() - Debug shielded transactions

    • signed_call() - Execute signed reads

    • - Deposit ETH/tokens

    • - Get TEE public key

    • - Query deposit merkle root

    • - Query deposit count

    • - Create contract wrappers

    hashtag
    Encryption

    The client automatically:

    • Fetches the network's TEE public key

    • Performs ECDH key exchange using encryption_sk (or generates a random one)

    • Derives a shared AES-GCM key via HKDF

    • Uses this key to encrypt all shielded transaction calldata and signed reads

    Access the encryption state at w3.seismic.encryption if needed for advanced use cases.

    hashtag
    Notes

    • HTTP only — Sync clients use Web3 with HTTPProvider, which does not support WebSocket connections. This is a limitation of the underlying web3.py library (WebSocketProvider is async-only). If you need WebSocket support (persistent connections, subscriptions), use create_async_wallet_client() with ws=True

    • The function makes one synchronous RPC call to fetch the TEE public key

    • If encryption_sk is None, a random ephemeral key is generated

    • The encryption key is separate from the transaction signing key

    • The returned Web3 instance is fully compatible with all web3.py APIs

    • For async operations, use

    hashtag
    Warnings

    • Private key security - Never log or expose private keys. Use environment variables or secure key management

    • RPC URL validation - Ensure the RPC URL is correct and accessible

    • Network connectivity - The function will fail if it cannot reach the RPC endpoint

    • HTTPS recommended - Use HTTPS URLs in production to prevent MITM attacks

    hashtag
    See Also

    • create_async_wallet_client - Async variant with WebSocket support

    • create_public_client - Read-only client without private key

    • EncryptionState - Encryption state class

    • - Encryption derivation function

    • - The w3.seismic namespace

    • - Pre-configured chain constants

    ECDHarrow-up-right
    w3.seismic

    How Seismic Works

    Seismic adds on-chain privacy to the EVM through three layers: a modified Solidity compiler, a network of TEE-secured nodes, and a shielded storage model. This page explains how they fit together.

    hashtag
    Three pillars

    Layer
    What it does

    Chains

    Pre-configured chain definitions for Seismic networks

    seismic-viem provides pre-configured viem Chain objects with Seismic transaction formatters. These chain definitions include the correct chain IDs, RPC endpoints, and custom formatters needed for Seismic's type 0x4A transactions.

    hashtag
    Import

    result = 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 hash
    from 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 + 16
    result = 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_selector
    result = 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,
    ) -> Web3
    import 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_id

    CSTORE to a private slot

    Marks the slot as private

    CSTORE to a zero-value public slot

    Claims the slot as private

    CSTORE to a non-zero public slot

    Reverts

    tx_hash — Transaction hash from broadcast

    Gas price in wei (uses network default if None)

    security

    SeismicSecurityParams | None

    None

    Custom security parameters (block expiry, nonce, etc.)

    .twrite

    No

    HexBytes (tx hash)

    Yes

    Transparent writes

    UnsignedSeismicTx
    Shielded Write Guide

    Optional 32-byte key for ECDH. If None, a random ephemeral key is generated

    HKDFarrow-up-right
    deposit()
    get_tee_public_key()
    get_deposit_root()
    get_deposit_count()
    contract()
    create_async_wallet_client()
    get_encryption
    SeismicNamespace
    Chains Configuration
    PrivateKey
    PrivateKey
    SeismicNamespace
    encryption = get_encryption(network_pk, encryption_sk)
    w3.seismic = SeismicNamespace(w3, encryption, private_key)

    Seismic network

    EVM-compatible L1 where nodes run inside Trusted Execution Environments (TEEs)

    Shielded storage (FlaggedStorage)

    Storage model where each slot carries an is_private flag, enforced at the opcode level

    hashtag
    The Seismic Solidity compiler

    The Seismic compiler (ssolc) is a fork of solc that understands shielded types. When you write:

    the compiler emits CSTORE (opcode 0xB1) instead of SSTORE to write the value, and CLOAD (opcode 0xB0) instead of SLOAD to read it. These opcodes tell the EVM to treat the storage slot as private.

    The supported shielded types are:

    • suint / sint -- shielded integers (all standard bit widths: suint8 through suint256)

    • sbool -- shielded boolean

    • sbytes -- shielded bytes, both fixed-length (sbytes1 through sbytes32) and dynamic (sbytes)

    • saddress -- shielded address

    Arithmetic, comparisons, and assignments work exactly as they do with regular Solidity types. The compiler handles routing to the correct opcodes. You do not need to call any special APIs or change your contract logic.

    hashtag
    The Seismic network

    Seismic is an EVM-compatible L1 blockchain. Nodes run inside Trusted Execution Environments (TEEs) powered by Intel TDX. The TEE creates a hardware-enforced enclave: code and data inside the enclave cannot be observed or tampered with by the host operating system or the node operator.

    Key network properties:

    • Block time: Sub-second, powered by Summitarrow-up-right (our custom consensus algorithm)

    • Finality: 1 block

    • Transaction types: All standard Ethereum types (Legacy, EIP-1559, EIP-2930, EIP-4844, EIP-7702) plus the Seismic transaction type 0x4A

    hashtag
    The Seismic transaction (type 0x4A)

    The diagram below shows the block structure, state tries, and the Seismic transaction format (TxSeismic). Note how each storage slot in the Account Storage trie carries a boolean flag -- (u256, true) for private slots and (u256, false) for public slots.

    Standard Ethereum transactions send calldata in plaintext. The Seismic transaction type encrypts calldata before it leaves the user's machine.

    The encryption flow:

    1. The client calls seismic_getTeePublicKey on the RPC to fetch the network's TEE public key.

    2. The client performs ECDH key agreement between the user's private key and the TEE public key to derive a shared secret.

    3. The calldata is encrypted using AEAD (authenticated encryption with associated data).

    4. The encrypted transaction is broadcast to the network.

    5. Inside the TEE, the node decrypts the calldata, executes the transaction, and writes results to shielded storage.

    At no point is the plaintext calldata visible outside the TEE -- not in the mempool, not in block data, not in transaction traces. For a deeper dive, see The Seismic Transaction.

    hashtag
    Shielded storage (FlaggedStorage)

    The diagram below shows how the RPC layer, EVM, and storage interact. Notice that eth_getStorageAt is blocked for shielded slots, and cross-contract reads of private storage return zero.

    Seismic extends the EVM storage model with FlaggedStorage. Each storage slot is a tuple:

    When a contract uses CSTORE to write a value, the is_private flag is set to true. This flag has two effects:

    • eth_getStorageAt returns zero for shielded slots, making them indistinguishable from uninitialized storage. External observers cannot read or detect shielded data through the standard RPC.

    • Only CLOAD can read private slots. The standard SLOAD opcode cannot access them. This is enforced in the Seismic EVMarrow-up-right.

    The compiler manages this automatically. When you declare a variable as suint256, the compiler emits CLOAD/CSTORE. When you declare it as uint256, the compiler emits SLOAD/SSTORE. You do not interact with FlaggedStorage directly.

    hashtag
    End-to-end walkthrough

    Here is what happens when a user calls transfer() on an SRC20 contract with shielded balances:

    Step 1: User encrypts calldata. The client library (e.g., seismic-viem) fetches the TEE public key, derives a shared secret via ECDH, and encrypts the function and its arguments (to and amount) using AEAD. The encrypted payload is wrapped in a type 0x4A transaction.

    Step 2: Transaction enters the mempool. The calldata is encrypted. Observers can see that a transaction was submitted to the SRC20 contract, but the recipient address and amount are not readable.

    Step 3: Node decrypts inside the TEE. The Seismic node, running inside Intel TDX, decrypts the calldata using the network's private key. The plaintext arguments are now available only within the enclave.

    Step 4: EVM executes with CSTORE. The EVM processes the transfer() function. Reads from balanceOf use CLOAD. Writes to balanceOf use CSTORE. These storage slots will have is_private = true.

    Step 5: Storage is updated. The new balances are written to shielded storage.

    Step 6: Observers see 0x00...0. Anyone querying the contract state, reading transaction traces, or inspecting block data sees zero in place of all shielded values -- the balances, the transfer amount, and any intermediate computation involving shielded types.

    hashtag
    Precompiles

    Seismic adds six precompiled contracts to the EVM, giving smart contracts access to cryptographic primitives that would be prohibitively expensive to implement in Solidity:

    Address
    Name
    Purpose

    0x64

    Securely generate a random number

    0x65

    Elliptic Curve Diffie-Hellman -- derive a shared secret from a public key and a private key

    0x66

    Encrypt data with AES-GCM

    0x67

    Decrypt data with AES-GCM

    These precompiles enable contracts to perform on-chain encryption, key derivation, and random number generation without relying on external oracles or off-chain computation.

    hashtag
    System architecture

    The diagram below shows the full Seismic node architecture inside the TEE boundary. Encrypted transactions flow from the client through the RPC layer, into the transaction pool, through consensus, and into the block executor where calldata is decrypted and executed. Shielded results are written to storage.

    The system is composed of three components that work together to provide confidential smart contract execution:

    Component
    Role

    Transaction processing, state management, RPC

    Consensus and block production

    Enclave

    TEE operations: key management, encryption/decryption, attestation

    All three components are designed so that private data is only ever accessible inside the Trusted Execution Environment. No plaintext shielded data leaves the TEE boundary at any point in the pipeline.

    hashtag
    Seismic node

    The Seismic node is a fork of retharrow-up-right (the Rust Ethereum execution client). It handles:

    • RPC: Accepts incoming transactions and read requests. Serves responses to clients, redacting shielded data from public queries.

    • EVM execution: Runs a modified EVM that supports CLOAD/CSTORE opcodes and the six Seismic precompiles. This is built on a forked version of revm.

    • State management: Maintains the world state using FlaggedStorage, where each storage slot is tagged as public or private.

    • Transaction pool: Receives both standard Ethereum transactions and Seismic transactions. Encrypted calldata in Seismic transactions is decrypted inside the TEE before execution.

    The entire node process runs inside an Intel TDX Trusted Execution Environment. This means the node operator cannot inspect memory, attach debuggers, or extract keys from the running process.

    hashtag
    Fork chain

    The Seismic execution stack is built on a chain of forks from the Ethereum Rust ecosystem:

    • seismic-alloy-corearrow-up-right (fork of alloy-core): Shielded types and FlaggedStorage primitives.

    • seismic-triearrow-up-right (fork of alloy-trie): Merkle trie encoding for FlaggedStorage values.

    • seismic-revmarrow-up-right (fork of revm): CLOAD/CSTORE opcodes, Seismic precompiles, and FlaggedStorage access rules.

    • (fork of revm-inspectors): EVM tracing/debugging with Seismic support.

    • (fork of alloy-evm): Seismic block execution layer.

    • (fork of reth): Full node with Enclave communication, modified RPC (redacting shielded data), and TEE attestation.

    • (fork of solidity): Compiler adding shielded types (suint, sbool, sbytes, saddress).

    • (fork of foundry): sforge, sanvil, scast dev tools.

    • (fork of compilers): Compiler integration for sforge.

    • (fork of foundry-fork-db): Fork DB with FlaggedStorage support.

    Plus two original repos:

    • summitarrow-up-right: Consensus client.

    • seismic-enclavearrow-up-right: ECDH + AES-GCM crypto for transaction encryption/decryption.

    • seismic-alloyarrow-up-right: Rust SDK with TxSeismic type and encryption-aware providers.

    For the full list and dependency flow, see Repos.

    hashtag
    Summit (consensus)

    Summit is Seismic's consensus layer, built on Commonwarearrow-up-right primitives. It handles:

    • Block production: Ordering transactions into blocks with sub-second block times.

    • Consensus: Reaching agreement among validators on the canonical chain.

    • Finality: Single-block finality -- once a block is produced and agreed upon, it is final.

    Summit communicates with the Seismic node to receive transactions from the mempool and to deliver finalized blocks for execution.

    hashtag
    Enclave

    The Enclave component manages all TEE-related operations. It is the trust anchor of the system.

    Key management:

    • Genesis node: When the network starts, the genesis node generates a root key inside the TEE. This key never leaves the enclave.

    • Peer nodes: When a new node joins the network, it must pass remote attestation before receiving the root key. The existing nodes verify that the new node is running identical, approved code inside a genuine TEE.

    • Encryption secret key: The root key is used to derive the network's encryption secret key. This key is used to decrypt the calldata of Seismic transactions (type 0x4A).

    Attestation:

    Remote attestation is the process by which one TEE proves to another that it is running approved code on genuine hardware. In Seismic:

    1. A new node generates an attestation report inside its TEE.

    2. The report is sent to existing nodes for verification.

    3. Existing nodes check that the report was generated by genuine Intel TDX hardware and that the code measurement matches the approved build.

    4. Only after successful verification does the new node receive the root key.

    This ensures that every node in the network is running the same software and that no node can be modified to leak private data.

    hashtag
    TEE guarantees

    The TEE (Trusted Execution Environment) is the foundation of Seismic's privacy model. Intel TDX provides the following guarantees:

    hashtag
    Code integrity

    Remote attestation ensures that all nodes in the network are running identical, approved code. A node cannot be modified to log private data, skip encryption, or export keys.

    hashtag
    Memory isolation

    The TEE creates a hardware-enforced boundary around the node's memory. The host operating system, hypervisor, and node operator cannot read or write to the enclave's memory space.

    hashtag
    Key protection

    Cryptographic keys (the root key, encryption secret key, and any derived keys) are generated inside the TEE and never leave it. There is no API to export keys from the enclave. Even if the node operator has full root access to the host machine, they cannot extract the keys.

    hashtag
    What TEEs do not protect against

    • Side-channel attacks: While Intel TDX mitigates many known side-channel attacks, this is an active area of research. Seismic's design minimizes the attack surface, but hardware-level side channels remain a theoretical concern.

    • Bugs in the node software: If the approved node code has a bug that leaks private data through a public channel (e.g., writing shielded values to public storage), the TEE will faithfully execute that buggy code. This is why the code is open-source and subject to audit.

    • Transaction metadata: The TEE protects calldata and storage values, but metadata such as sender address, gas usage, and the target contract address remain visible on-chain.

    Seismic Solidity compiler

    Adds shielded types (suint, sint, sbool, sbytes, saddress) that compile to privacy-aware opcodes

    hashtag
    Available Chains
    Chain
    Export
    Chain ID
    RPC URL
    Description

    Seismic Testnet

    seismicTestnet

    5124

    https://gcp-1.seismictest.net/rpc

    Public testnet

    Sanvil

    sanvil

    31337

    http://127.0.0.1:8545

    Local Seismic Anvil

    hashtag
    Seismic Testnet

    The public testnet for development and testing against a live Seismic network:

    hashtag
    Sanvil

    Local development chain using Sanvil (Seismic's fork of Anvil). Chain ID 31337 matches Anvil/Hardhat defaults:

    hashtag
    Local Devnet

    For running a local seismic-reth --dev node. Uses chain ID 5124 (same as testnet) but connects to localhost:

    hashtag
    Choosing a Chain

    hashtag
    SEISMIC_TX_TYPE

    All chain configs use the Seismic transaction type constant:

    The value 74 (0x4A) is the EIP-2718 transaction type envelope identifier for Seismic transactions. This constant is used internally by the chain formatters and encryption pipeline to identify and construct Seismic-specific transaction payloads.

    hashtag
    Chain Formatters

    Each chain config includes seismicChainFormatters -- custom viem chain formatters that handle Seismic transaction fields. These formatters are applied automatically when you use a Seismic chain definition.

    The formatters extend viem's standard transaction formatting to support the additional fields required by Seismic's type 0x4A transactions, including encryption metadata and signed read parameters.

    circle-info

    You do not need to configure or interact with seismicChainFormatters directly. They are embedded in every pre-configured chain object and in chains created via createSeismicDevnet.

    hashtag
    Custom Chain Factory

    hashtag
    createSeismicDevnet

    Create a custom chain definition for any Seismic-compatible node:

    hashtag
    Parameters

    Parameter
    Type
    Required
    Description

    nodeHost

    string

    Yes

    Base URL of the Seismic node (without /rpc)

    explorerUrl

    string

    No

    Block explorer URL for the chain

    The factory returns a viem Chain object with:

    • Chain ID 5124

    • RPC URL set to {nodeHost}/rpc

    • Seismic chain formatters included

    • Optional block explorer configuration

    hashtag
    Helper Factories

    Convenience factories for common Seismic infrastructure:

    These generate chain configs pointing to numbered Seismic testnet instances on Azure and GCP infrastructure respectively.

    hashtag
    SeismicTransactionRequest

    Seismic chain configs use a custom transaction request type that extends viem's standard TransactionRequest with Seismic-specific fields:

    circle-info

    You do not need to populate these fields manually. The shielded wallet client's encryption pipeline fills them automatically when sending transactions. They are documented here for reference and debugging purposes.

    Field
    Type
    Description

    encryptionPubkey

    Hex

    Client's compressed secp256k1 public key

    encryptionNonce

    Hex

    Random nonce for AES-GCM encryption of calldata

    messageVersion

    number

    Version of the Seismic message format

    recentBlockHash

    Hex

    Recent block hash used for transaction replay protection

    hashtag
    See Also

    • Installation -- Install seismic-viem and viem

    • Seismic Viem Overview -- Full SDK overview and architecture

    • Shielded Wallet Client -- Create a client using a chain config

    • Encryption -- How calldata encryption uses chain-level formatters

    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-db
    import {
      seismicTestnet,
      sanvil,
      localSeismicDevnet,
      createSeismicDevnet,
    } from "seismic-viem";
    import { http } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";
    
    const client = await createShieldedWalletClient({
      chain: seismicTestnet,
      transport: http(),
      account: privateKeyToAccount("0x..."),
    });
    import { http } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { createShieldedWalletClient, sanvil } from "seismic-viem";
    
    const client = await createShieldedWalletClient({
      chain: sanvil,
      transport: http(),
      account: privateKeyToAccount("0x..."),
    });
    import { http } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { createShieldedWalletClient, localSeismicDevnet } from "seismic-viem";
    
    const client = await createShieldedWalletClient({
      chain: localSeismicDevnet,
      transport: http(),
      account: privateKeyToAccount("0x..."),
    });
    Are you developing against a live network?
      -> Use seismicTestnet
    
    Are you running Sanvil locally for rapid iteration?
      -> Use sanvil
    
    Are you running seismic-reth --dev locally?
      -> Use localSeismicDevnet
    
    Do you need a custom chain configuration?
      -> Use createSeismicDevnet()
    import { SEISMIC_TX_TYPE } from "seismic-viem";
    
    console.log(SEISMIC_TX_TYPE); // 74 (0x4A)
    import { createSeismicDevnet } from "seismic-viem";
    
    const myChain = createSeismicDevnet({
      nodeHost: "https://my-seismic-node.example.com",
      explorerUrl: "https://explorer.example.com",
    });
    import { createSeismicAzTestnet, createSeismicGcpTestnet } from "seismic-viem";
    
    // Azure-hosted testnet instance N
    const azChain = createSeismicAzTestnet(1);
    
    // GCP-hosted testnet instance N
    const gcpChain = createSeismicGcpTestnet(1);
    interface SeismicTxExtras {
      encryptionPubkey: Hex; // Client's compressed secp256k1 public key
      encryptionNonce: Hex; // AES-GCM nonce for calldata encryption
      messageVersion: number; // Seismic message format version
      recentBlockHash: Hex; // Recent block hash for replay protection
      expiresAtBlock: bigint; // Block number at which the transaction expires
      signedRead: boolean; // Whether this is a signed read request
    }

    Local Devnet

    localSeismicDevnet

    5124

    http://127.0.0.1:8545

    Local seismic-reth --dev

    expiresAtBlock

    bigint

    Block number after which the transaction becomes invalid

    signedRead

    boolean

    true for signed read requests, false for standard txs

    0x68

    HKDF

    Derive cryptographic keys from a parent key

    0x69

    secp256k1 Sign

    Sign a message with a secret key

    seismic-revm-inspectorsarrow-up-right
    seismic-evmarrow-up-right
    seismic-retharrow-up-right
    seismic-solidityarrow-up-right
    seismic-foundryarrow-up-right
    seismic-compilersarrow-up-right
    seismic-foundry-fork-dbarrow-up-right
    RNG
    ECDH
    AES-GCM Encrypt
    AES-GCM Decrypt
    Seismic Retharrow-up-right
    Summitarrow-up-right
    Block structure, state tries, and TxSeismic transaction format
    RPC, EVM, and storage interaction diagram showing how shielded slots are protected
    Seismic node architecture showing components inside the TEE boundary