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

***

### One letter changes everything

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

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

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

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.

***

### 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
* **Private voting** — On-chain governance where votes are secret until tallied

***

### 3-minute quickstart

Already have Rust and the [Seismic tools installed](/getting-started/installation)?

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

You just ran shielded contract tests locally. See the [full quickstart](/getting-started/quickstart) for next steps.

***

### Find what you need

| I want to...                         | Go to                                                                           |
| ------------------------------------ | ------------------------------------------------------------------------------- |
| Understand why Seismic exists        | [Why Seismic](/overview/why-seismic)                                            |
| See how it works under the hood      | [How Seismic Works](/overview/how-seismic-works)                                |
| Set up my dev environment            | [Installation](/getting-started/installation)                                   |
| Run my first shielded contract       | [Quickstart](/getting-started/quickstart)                                       |
| Build a complete app step by step    | [Clown Beatdown Tutorial](/tutorials/understanding-the-clown-beatdown-contract) |
| Build a shielded ERC20 token         | [SRC20 Tutorial](/tutorials/src20)                                              |
| Learn about shielded types           | [Shielded Types](/seismic-solidity/shielded-types)                              |
| Integrate a frontend                 | [Client Libraries](/clients/clients)                                            |
| Deploy to testnet                    | [Migrating from Ethereum](/reference/migrating-from-ethereum)                   |
| Understand the transaction lifecycle | [Seismic Transaction](/reference/seismic-transaction)                           |
| Run a node                           | [Node Operator FAQ](/reference/node-operator-faq)                               |

***

### Pre-requisite knowledge

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

* [Solidity](https://www.soliditylang.org/)
* [Foundry](https://getfoundry.sh/)
* [Viem](https://viem.sh/)

If you're new to blockchain app development or need a refresher, we recommend starting out with the [CryptoZombies](https://cryptozombies.io/en/course) tutorial.

***

### Work with us

If you might benefit from direct support from the team, please don't hesitate to reach out to `dev@seismic.systems`. We pride ourselves in fast response time.

You can also check out our [X account](https://x.com/SeismicSys) for the latest updates, or join our [Discord](https://discord.gg/XSPNseXCvW) community.


# Why Seismic

As shown on the [welcome page](/), shielding a Solidity contract can be as simple as adding an `s` prefix to your types. But why does that matter?

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

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

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

## The Seismic approach

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

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

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

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


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

## Three pillars

| Layer                                 | What it does                                                                                               |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Seismic Solidity compiler**         | Adds shielded types (`suint`, `sint`, `sbool`, `sbytes`, `saddress`) that compile to privacy-aware opcodes |
| **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                   |

## The Seismic Solidity compiler

The Seismic compiler (`ssolc`) is a fork of `solc` that understands shielded types. When you write:

```solidity
suint256 balance = 100s;
```

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.

## 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 [Summit](https://github.com/SeismicSystems/summit) (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`

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

<figure><img src="/files/6PknhkcW3oi9DVQR3hZ5" alt="Block structure, state tries, and TxSeismic transaction format"><figcaption></figcaption></figure>

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](/reference/seismic-transaction).

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

<figure><img src="/files/LlpwqnW6HCAN3pgTiAH4" alt="RPC, EVM, and storage interaction diagram showing how shielded slots are protected"><figcaption></figcaption></figure>

Seismic extends the EVM storage model with **FlaggedStorage**. Each storage slot is a tuple:

```
(value, is_private)
```

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 EVM](https://github.com/SeismicSystems/seismic-revm).

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.

## End-to-end walkthrough

Here is what happens when a user calls `transfer()` on an SRC20 contract with shielded balances:

```solidity
mapping(address => suint256) balanceOf;

function transfer(address to, suint256 amount) public {
    balanceOf[msg.sender] -= amount;
    balanceOf[to] += amount;
}
```

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

## 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`  | [RNG](/reference/precompiles/rng)                         | Securely generate a random number                                                           |
| `0x65`  | [ECDH](/reference/precompiles/ecdh)                       | Elliptic Curve Diffie-Hellman -- derive a shared secret from a public key and a private key |
| `0x66`  | [AES-GCM Encrypt](/reference/precompiles/aes-gcm-encrypt) | Encrypt data with AES-GCM                                                                   |
| `0x67`  | [AES-GCM Decrypt](/reference/precompiles/aes-gcm-decrypt) | Decrypt data with AES-GCM                                                                   |
| `0x68`  | [HKDF](/reference/precompiles/hkdf)                       | Derive cryptographic keys from a parent key                                                 |
| `0x69`  | [secp256k1 Sign](/reference/precompiles/secp256k1-sign)   | Sign a message with a secret key                                                            |

These precompiles enable contracts to perform on-chain encryption, key derivation, and random number generation without relying on external oracles or off-chain computation.

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

<figure><img src="/files/WtcBLZfH0Tq5s6kAVIY0" alt="Seismic node architecture showing components inside the TEE boundary"><figcaption></figcaption></figure>

The system is composed of three components that work together to provide confidential smart contract execution:

| Component                                                          | Role                                                               |
| ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| [**Seismic Reth**](https://github.com/SeismicSystems/seismic-reth) | Transaction processing, state management, RPC                      |
| [**Summit**](https://github.com/SeismicSystems/summit)             | 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.

### Seismic node

The Seismic node is a fork of [reth](https://github.com/paradigmxyz/reth) (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](/reference/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.

### Fork chain

The Seismic execution stack is built on a chain of forks from the Ethereum Rust ecosystem:

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

* [**seismic-alloy-core**](https://github.com/SeismicSystems/seismic-alloy-core) (fork of alloy-core): Shielded types and FlaggedStorage primitives.
* [**seismic-trie**](https://github.com/SeismicSystems/seismic-trie) (fork of alloy-trie): Merkle trie encoding for FlaggedStorage values.
* [**seismic-revm**](https://github.com/SeismicSystems/seismic-revm) (fork of revm): `CLOAD`/`CSTORE` opcodes, Seismic precompiles, and FlaggedStorage access rules.
* [**seismic-revm-inspectors**](https://github.com/SeismicSystems/seismic-revm-inspectors) (fork of revm-inspectors): EVM tracing/debugging with Seismic support.
* [**seismic-evm**](https://github.com/SeismicSystems/seismic-evm) (fork of alloy-evm): Seismic block execution layer.
* [**seismic-reth**](https://github.com/SeismicSystems/seismic-reth) (fork of reth): Full node with Enclave communication, modified RPC (redacting shielded data), and TEE attestation.
* [**seismic-solidity**](https://github.com/SeismicSystems/seismic-solidity) (fork of solidity): Compiler adding shielded types (`suint`, `sbool`, `sbytes`, `saddress`).
* [**seismic-foundry**](https://github.com/SeismicSystems/seismic-foundry) (fork of foundry): `sforge`, `sanvil`, `scast` dev tools.
* [**seismic-compilers**](https://github.com/SeismicSystems/seismic-compilers) (fork of compilers): Compiler integration for sforge.
* [**seismic-foundry-fork-db**](https://github.com/SeismicSystems/seismic-foundry-fork-db) (fork of foundry-fork-db): Fork DB with FlaggedStorage support.

Plus two original repos:

* [**summit**](https://github.com/SeismicSystems/summit): Consensus client.
* [**seismic-enclave**](https://github.com/SeismicSystems/enclave): ECDH + AES-GCM crypto for transaction encryption/decryption.
* [**seismic-alloy**](https://github.com/SeismicSystems/seismic-alloy): Rust SDK with TxSeismic type and encryption-aware providers.

For the full list and dependency flow, see [Repos](/reference/repos).

### Summit (consensus)

Summit is Seismic's consensus layer, built on [Commonware](https://github.com/commonwarexyz/monorepo) 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.

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

## TEE guarantees

The TEE (Trusted Execution Environment) is the foundation of Seismic's privacy model. Intel TDX provides the following guarantees:

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

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

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

### 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.
* **Low-entropy private storage values**: Shielded storage hides values from RPC reads, but each value is still committed into the storage Merkle trie at a publicly known slot. A value with little entropy — a flag, a small enum — can be recovered by guessing candidates and checking them against the public trie commitment, without ever reading the slot. See [Footguns](/seismic-solidity/footguns#low-entropy-shielded-storage-values) for details and mitigations.


# Differences from Ethereum

## Overview

The [Seismic EVM](https://github.com/SeismicSystems/seismic-revm/blob/c29f4ea0681f09fb1e9998fa16568d4979c47ee3/README.md) is approximately a superset of the EVM

### What's the same

* 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-compatibility) RPC methods are identical to reth
* Standard Solidity bytecode will behave identically on Seismic, with [minor exceptions](/reference/opcodes#sstore-sload-standard) (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

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

## EVM Compatibility

### Opcodes

* [`CLOAD`](/reference/opcodes#cload) – load shielded data from storage
* [`CSTORE`](/reference/opcodes#cstore) – write shielded data to storage
* [`TIMESTAMP_MS`](/reference/opcodes#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

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

### Precompiles

All standard Ethereum precompiles are still available. Seismic added 6 new [precompiles](/reference/precompiles) to our EVM:

* [**RNG `0x64`**](/reference/precompiles/rng) — securely generate a random number
* [**ECDH `0x65`**](/reference/precompiles/ecdh) — Elliptic Curve Diffie-Hellman, for generating a shared secret given a public key and secret key
* [**AES-GCM Encrypt `0x66`**](/reference/precompiles/aes-gcm-encrypt) — encrypt data with AES-GCM
* [**AES-GCM Decrypt `0x67`**](/reference/precompiles/aes-gcm-decrypt) — decrypt data with AES-GCM
* [**HKDF `0x68`**](/reference/precompiles/hkdf) — derive cryptographic keys from a parent key
* [**secp256k1 Sign `0x69`**](/reference/precompiles/secp256k1-sign) — sign a message given a secret key

### Staking

Seismic uses the same staking contract as Ethereum, which is hardcoded into our Genesis block at address `0x00000000219ab540356cbb839cbe05303d7705fa`

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

### 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/rpc-methods) reference for details.

**Seismic-specific methods:**

* [`seismic_getTeePublicKey`](/reference/rpc-methods/seismic-get-tee-public-key) — returns the TEE's encryption public key for ECDH key exchange

**Modified Ethereum methods:**

* [`eth_call`](/reference/rpc-methods/eth-call) — zeroes the `from` field on unsigned calls; supports ["signed reads"](/reference/seismic-transaction/signed-reads) via type `0x4A`
* [`eth_sendRawTransaction`](/reference/rpc-methods/eth-send-raw-transaction) — accepts Seismic transaction type `0x4A` with encrypted calldata
* [`eth_getStorageAt`](/reference/rpc-methods/eth-get-storage-at) — 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


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

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

```solidity
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;
}
```

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 [SRC20 tutorial](/tutorials/src20).

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

```solidity
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;
    }
}
```

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.

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

```solidity
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;
}
```

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.

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

```solidity
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);
}
```

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.

## Sealed-bid auctions

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

```solidity
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);
}
```

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.


# Installation

Setting up your local machine to develop with Seismic

***

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

***

### 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 [rust](https://www.rust-lang.org/tools/install) and [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) on your machine if you don't already have them. Default installation works well.

```bash
curl https://sh.rustup.rs -sSf | sh
```

2. Download and execute the sfoundryup installation script.

```bash
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
```

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

```bash
sfoundryup
source ~/.zshenv  # or ~/.bashrc, depending on your shell
```

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

```bash
sforge clean  # run in your project's contract directory
```

***

### Set up the VSCode extension

We recommend adding syntax highlighting via the [`seismic`](https://marketplace.visualstudio.com/items?itemName=SeismicSys.seismic) (or [`seismic`](https://open-vsx.org/extension/SeismicSys/seismic) 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.


# Development Toolkit

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

***

### Mappings to foundry

Seismic's development toolkit closely mirrors [Foundry](https://getfoundry.sh/) (it's a [fork](https://github.com/SeismicSystems/seismic-foundry)!). The mapping is as follows:

```
// foundry tool -> seismic version of foundry tool
forge -> sforge
anvil -> sanvil
cast -> scast
```

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

***

### Quick actions

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

{% tabs %}
{% tab title="Initialize sforge project" %}

```bash
# Initializes a project called `Counter`
sforge init Counter
```

{% endtab %}

{% tab title="Run tests" %}

```bash
# Run tests for the Counter contract
sforge test
```

{% endtab %}

{% tab title="Deploy contract" %}

```bash
# 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
```

{% endtab %}
{% endtabs %}

***

### Local node

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

```bash
sanvil
```

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


# Quickstart

You're two commands away from running an encrypted protocol

You can play around with shielded types using our [starter repository](https://github.com/SeismicSystems/seismic-starter). This assumes you went through everything in [Installation](/getting-started/installation).

<pre class="language-bash"><code class="lang-bash"><strong>git clone "https://github.com/SeismicSystems/seismic-starter.git"
</strong><strong>cd seismic-starter/packages/contracts
</strong><strong>sforge test -vv
</strong></code></pre>

Or if you prefer a more detailed walkthrough of building on Seismic, check out our [Tutorials](/tutorials/src20) section.


# Deploy an SRC20 in 60 Seconds

Deploy a private SRC20 token on Seismic testnet with a single command — no compiler required

The SRC20 Factory is a pre-deployed contract on Seismic testnet that lets anyone create a private token without installing `sforge` or writing a single line of Solidity. You give it a name, symbol, and supply; it hands back a deployed token address.

## Quickstart

```bash
bunx create-src20
```

That's it. The CLI walks you through the rest interactively, or you can pass everything up front:

```bash
bunx create-src20 \
  --name "My Private Token" \
  --symbol "MPT" \
  --supply 1000000 \
  --key 0xYourPrivateKey
```

Example output:

```
  Create SRC20 Token on Seismic

  Deploying to Seismic testnet...

  Token deployed!

  Address:  0xabc...
  Name:     My Private Token
  Symbol:   MPT
  Supply:   1,000,000
  Owner:    0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  Tx:       0xdef...
  Explorer: https://seismic-testnet.socialscan.io/address/0xabc...
```

## CLI flags

| Flag       | Description                                                    | Default                                 |
| ---------- | -------------------------------------------------------------- | --------------------------------------- |
| `--name`   | Token name                                                     | prompted                                |
| `--symbol` | Token symbol                                                   | prompted                                |
| `--supply` | Initial supply in whole tokens (multiplied by 10¹⁸ internally) | prompted                                |
| `--key`    | 0x-prefixed 64-character hex private key                       | prompted (hidden input)                 |
| `--rpc`    | Custom RPC URL                                                 | `https://testnet-2.seismictest.net/rpc` |

{% hint style="info" %}
Supply is always treated as whole tokens. Passing `--supply 1000000` mints `1,000,000 × 10¹⁸` base units, using 18 decimals.
{% endhint %}

## What you get

Every deployed token is a full `SRC20Token` contract — Seismic's private variant of ERC20. Balances, transfer amounts, and allowances are encrypted on-chain using `suint256` shielded types. Outside observers see only encrypted ciphertext. The token deployer becomes the `owner` and has exclusive mint and burn rights.

## Interfaces

The factory exposes four interfaces depending on how you want to integrate:

| Interface                                            | When to use                               |
| ---------------------------------------------------- | ----------------------------------------- |
| [CLI](/getting-started/src20-factory)                | Deploy a token manually from the terminal |
| [TypeScript SDK](/getting-started/src20-factory/sdk) | Embed token creation in a dApp or script  |
| [Web GUI](/getting-started/src20-factory/web)        | Deploy via browser without writing code   |
| [REST API](/getting-started/src20-factory/api)       | Query deployed tokens from any language   |

## Network

* **Chain ID:** 5124 (Seismic testnet)
* **Factory address:** `0x87F850cbC2cFfac086F20d0d7307E12d06fA2127`
* **Explorer:** [seismic-testnet.socialscan.io](https://seismic-testnet.socialscan.io/address/0x87F850cbC2cFfac086F20d0d7307E12d06fA2127)


# TypeScript SDK

API reference for the @seismic/src20-sdk package used internally by the CLI and web app

`@seismic/src20-sdk` is the TypeScript library that powers the CLI and web interface in this repo. It wraps the SRC20 Factory contract and exposes three functions and the contract ABIs.

{% hint style="info" %}
`@seismic/src20-sdk` is an internal workspace package — it is not published to npm. It is used by the `packages/cli` and `packages/web` packages within this monorepo. This page documents its API surface as a reference.
{% endhint %}

***

## createToken

Deploys a new SRC20 token through the factory and returns the deployed address.

### Signature

```typescript
async function createToken(
  client: ShieldedWalletClient,
  params: CreateTokenParams,
): Promise<CreateTokenResult>;
```

### Parameters

| Parameter              | Type                   | Required | Description                                           |
| ---------------------- | ---------------------- | -------- | ----------------------------------------------------- |
| `client`               | `ShieldedWalletClient` | yes      | A shielded wallet client from `seismic-viem`          |
| `params.name`          | `string`               | yes      | Token name                                            |
| `params.symbol`        | `string`               | yes      | Token symbol                                          |
| `params.initialSupply` | `bigint`               | yes      | Supply in base units (e.g. `1_000_000n * 10n ** 18n`) |
| `params.decimals`      | `number`               | no       | Token decimals, defaults to `18`                      |

### Returns

```typescript
interface CreateTokenResult {
  tokenAddress: Address; // address of the deployed SRC20Token contract
  txHash: Hash; // transaction hash
}
```

***

## getTokenInfo

Reads public metadata from a deployed SRC20 token using a standard public client.

### Signature

```typescript
async function getTokenInfo(
  client: PublicClient,
  tokenAddress: Address,
): Promise<TokenInfo>;
```

### Returns

```typescript
interface TokenInfo {
  name: string;
  symbol: string;
  decimals: number;
  owner: Address;
  totalSupply: bigint; // in base units
}
```

***

## getFactoryAddress

Resolves the factory contract address for a given chain ID. Throws if the chain is not supported.

### Signature

```typescript
function getFactoryAddress(chainId: number): Address;
```

### FACTORY\_ADDRESSES

The raw mapping of chain IDs to factory addresses:

```typescript
const FACTORY_ADDRESSES: Record<number, Address> = {
  5124: "0x87F850cbC2cFfac086F20d0d7307E12d06fA2127",
};
```

***

## ABIs

The package exports the full contract ABIs:

* **`SRC20FactoryAbi`** — `createToken`, `getTokenCount`, `tokens(uint256)`, `TokenCreated` event
* **`SRC20TokenAbi`** — `name`, `symbol`, `decimals`, `owner`, `totalSupply`, `mint`, `burn`, and all inherited SRC20 functions (`transfer`, `transferFrom`, `approve`, `balance`, `allowance`, `balanceOfSigned`, `permit`)


# Web Interface

Deploy SRC20 tokens from a browser using the SRC20 Factory web interface

The SRC20 Factory ships a React web GUI (`packages/web`) for deploying tokens directly from a browser wallet, no CLI or code required.

## Running the web app

Clone the repo and start the dev server:

```bash
bun install
bun run dev:web
```

## Deploying a token

1. Open the web app and connect MetaMask
2. Switch to the Seismic testnet (chain ID 5124)
3. Fill in the token name, symbol, and initial supply
4. Click **Deploy** and confirm the transaction in your wallet
5. The deployed token address and transaction hash appear on success

{% hint style="info" %}
Supply is entered in whole tokens. Entering `1000000` mints `1,000,000 × 10¹⁸` base units.
{% endhint %}

## Wagmi configuration

The web app connects MetaMask to Seismic testnet via wagmi:

```typescript
import { createConfig, http } from "wagmi";
import { injected } from "wagmi/connectors";
import { seismicTestnet } from "seismic-viem";

export const wagmiConfig = createConfig({
  chains: [seismicTestnet],
  connectors: [injected({ target: "metaMask" })],
  transports: {
    [seismicTestnet.id]: http(),
  },
});
```

Other wallet connectors (WalletConnect, Coinbase, etc.) are not configured by default but can be added following the [wallet guides](/clients/typescript/react/wallet-guides).


# REST API

Query SRC20 tokens deployed through the factory via a REST API

The `src20-factory` repo includes a Rust API server (Axum) that reads factory and token state over HTTP. It is useful for indexing deployed tokens or fetching token metadata from non-TypeScript environments.

## Running the server

```bash
cd packages/api
cargo run
```

The server starts on port **3001** and connects to Seismic testnet at `https://testnet-2.seismictest.net/rpc`.

***

## GET /api/tokens

Returns all tokens ever deployed through the factory, with their metadata.

### Request

```
GET http://localhost:3001/api/tokens
```

### Response

```json
{
  "count": 2,
  "tokens": [
    {
      "address": "0xabc123...",
      "name": "My Private Token",
      "symbol": "MPT",
      "decimals": 18,
      "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
      "total_supply": "1000000000000000000000000"
    },
    {
      "address": "0xdef456...",
      "name": "Another Token",
      "symbol": "AT",
      "decimals": 6,
      "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
      "total_supply": "500000000000"
    }
  ]
}
```

{% hint style="info" %}
Addresses are returned as lowercase hex, not EIP-55 checksummed.
{% endhint %}

Note that `total_supply` is always in base units (not scaled by decimals).

***

## GET /api/token/{address}

Returns metadata for a single token by address.

### Request

```
GET http://localhost:3001/api/token/0xabc...
```

### Response

```json
{
  "name": "My Private Token",
  "symbol": "MPT",
  "decimals": 18,
  "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
  "total_supply": "1000000000000000000000000"
}
```

### Error response

If the address is invalid or the RPC call fails, the server returns a JSON error:

```json
{
  "error": "Invalid address: ..."
}
```

***

## What the API can and cannot read

The API uses a standard public provider — it does not hold a private key. This means it can read public state like `name`, `symbol`, `decimals`, `owner`, and `totalSupply`, but it cannot read shielded state like individual balances or allowances, which require a signed read from the balance holder. See [Signed Reads](/reference/seismic-transaction/signed-reads) for how those work.


# Contracts

Solidity interface reference for SRC20Factory and SRC20Token

## SRC20Factory

Pre-deployed on Seismic testnet at `0x87F850cbC2cFfac086F20d0d7307E12d06fA2127`.

```solidity
contract SRC20Factory {
    event TokenCreated(
        address indexed creator,
        address indexed token,
        string name,
        string symbol
    );

    address[] public tokens;

    function createToken(
        string memory name,
        string memory symbol,
        uint8 decimals,
        suint256 initialSupply
    ) external returns (address);

    function getTokenCount() external view returns (uint256);
}
```

### createToken

Deploys a new `SRC20Token`, records it in the `tokens` array, emits `TokenCreated`, and returns the deployed address. The caller becomes the token's `owner`.

`initialSupply` is a `suint256` — it is encrypted in the transaction and never visible on-chain. The supply is minted directly to `msg.sender` in the token's constructor.

### tokens

The public `tokens` array stores every token address in deployment order. Access by index:

```solidity
address token = factory.tokens(0);
```

### getTokenCount

Returns `tokens.length`. Use this to iterate the full list.

### TokenCreated event

Emitted on every successful deployment. The `name` and `symbol` fields are unencrypted strings; `creator` and `token` are indexed for log filtering.

***

## SRC20Token

Each token deployed by the factory is an instance of `SRC20Token`, which extends the base `SRC20` contract from `seismic-std-lib`.

```solidity
contract SRC20Token is SRC20 {
    address public owner;

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals,
        suint256 _initialSupply,
        address _owner
    );

    function totalSupply() public view returns (uint256);
    function mint(address to, suint256 amount) external;   // owner only
    function burn(address from, suint256 amount) external; // owner only
}
```

### mint / burn

Only callable by `owner`. Both revert with `"NOT_OWNER"` if called by anyone else. The `amount` parameter is `suint256` — it is shielded in transit.

### Inherited from SRC20

| Function                                                                    | Description                                               |
| --------------------------------------------------------------------------- | --------------------------------------------------------- |
| `transfer(address to, suint256 amount) → bool`                              | Encrypted transfer to `to`                                |
| `transferFrom(address from, address to, suint256 amount) → bool`            | Encrypted transferFrom                                    |
| `approve(address spender, suint256 amount) → bool`                          | Set encrypted allowance                                   |
| `balance() → uint256`                                                       | Caller's own encrypted balance (requires a signed read)   |
| `allowance(address spender) → uint256`                                      | Caller's allowance for `spender` (requires a signed read) |
| `balanceOfSigned(address owner, uint256 expiry, bytes signature) → uint256` | Signed read pattern for balance                           |
| `permit(...)`                                                               | EIP-2612 permit with encrypted amounts                    |

`balance()` and `allowance()` use Seismic's signed-read mechanism — the caller must submit an off-chain signature that authorizes the node to decrypt and return their private state. See [Signed Reads](/reference/seismic-transaction/signed-reads) for details.

### Why not clones?

The factory deploys a full `SRC20Token` contract for each token rather than using EIP-1167 minimal proxies. `SRC20` stores `decimals` as an immutable field and computes the EIP-2612 domain separator in the constructor — both must be baked into bytecode at deployment time. Clones share bytecode and cannot carry per-instance immutables, so they are not compatible with `SRC20`.


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

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

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

## Prerequisites

Before starting, make sure you have:

* **Seismic development tools installed** -- `sforge`, `sanvil`, and `ssolc`. See the [Installation guide](/getting-started/installation) if you have not set these up yet.
* **Solidity familiarity** -- You should be comfortable writing and reading Solidity contracts.
* **Basic understanding of ERC20** -- You should know what `balanceOf`, `transfer`, `approve`, and `transferFrom` do.

## What you'll learn

| Chapter | Topic                                                                               | Key concept                                   |
| ------- | ----------------------------------------------------------------------------------- | --------------------------------------------- |
| 1       | [ERC20 to SRC20](/tutorials/src20/erc20-to-src20)                                   | Shielded types and the minimal diff           |
| 2       | [Shielded Balances and Transfers](/tutorials/src20/shielded-balances-and-transfers) | `suint256` in practice, testing with `sforge` |
| 3       | [Encrypted Events](/tutorials/src20/encrypted-events)                               | AES-GCM precompiles for private event data    |
| 4       | [Signed Reads](/tutorials/src20/signed-reads-for-balance-checking)                  | Letting users view their own balance securely |
| 5       | [Intelligence Contracts](/tutorials/src20/intelligence-contracts)                   | Compliance-compatible access control          |
| 6       | [Building the Frontend](/tutorials/src20/building-the-frontend)                     | React integration with `seismic-react`        |

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


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

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

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

This is standard Solidity. Every balance, transfer amount, and allowance is a `uint256`, visible to anyone who queries the contract or reads the chain.

## The SRC20 version

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

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

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

## Line-by-line diff

### Balances: `uint256` to `suint256`

```diff
- mapping(address => uint256) public balanceOf;
+ mapping(address => suint256) balanceOf;
```

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.

### Allowances: same pattern

```diff
- mapping(address => mapping(address => uint256)) public allowance;
+ mapping(address => mapping(address => suint256)) allowance;
```

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

### Function parameters: shielded amounts

```diff
- function transfer(address to, uint256 amount) public returns (bool) {
+ function transfer(address to, suint256 amount) public returns (bool) {
```

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

### Constructor: casting the initial supply

```diff
- balanceOf[msg.sender] = _initialSupply;
+ balanceOf[msg.sender] = suint256(_initialSupply);
```

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.

### Events: casting back to `uint256`

```diff
- emit Transfer(msg.sender, to, amount);
+ emit Transfer(msg.sender, to, uint256(amount));
```

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](/tutorials/src20/encrypted-events) chapter shows how to use AES-GCM precompiles to encrypt it.

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

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

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

### 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](/tutorials/src20/signed-reads-for-balance-checking).

### 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](/tutorials/src20/encrypted-events).

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


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

## Shielded balanceOf

The core change is in the mapping declaration:

```solidity
mapping(address => suint256) balanceOf;
```

At the storage level, this is where Seismic's **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.

## transfer()

Here is the full `transfer` implementation with shielded amounts:

```solidity
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;
}
```

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

## transferFrom()

The full implementation with shielded allowances:

```solidity
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;
}
```

The allowance mapping stores `suint256` values:

```solidity
mapping(address => mapping(address => suint256)) allowance;
```

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.

## approve()

Setting shielded allowances:

```solidity
function approve(address spender, suint256 amount) public returns (bool) {
    allowance[msg.sender][spender] = amount;
    emit Approval(msg.sender, spender, uint256(amount));
    return true;
}
```

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](/tutorials/src20/encrypted-events) chapter.

## The mint pattern

Here is a mint function that assigns new tokens:

```solidity
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));
}
```

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`:

```solidity
suint256 totalSupply;
```

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](/tutorials/src20/signed-reads-for-balance-checking).

### Constructor minting

The simplest approach is to mint the entire initial supply in the constructor:

```solidity
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
    name = _name;
    symbol = _symbol;
    totalSupply = _initialSupply;
    balanceOf[msg.sender] = suint256(_initialSupply);
}
```

The explicit cast `suint256(_initialSupply)` is required because `_initialSupply` is a regular `uint256`. Seismic enforces explicit casting between shielded and unshielded types.

## Testing with sforge

Create a test file at `test/SRC20.t.sol`:

```solidity
// 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);
    }
}
```

### Test: basic transfer

```solidity
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);
}
```

{% hint style="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.
{% endhint %}

To support this test, add a test-only helper to your contract:

```solidity
// Only for testing -- remove before deployment
function getBalanceForTest(address account) external view returns (uint256) {
    return uint256(balanceOf[account]);
}
```

### Test: transfer reverts on insufficient balance

```solidity
function test_RevertWhen_InsufficientBalance() public {
    vm.prank(bob); // bob has no tokens
    vm.expectRevert("Insufficient balance");
    token.transfer(alice, suint256(1e18));
}
```

### Test: approve and transferFrom

```solidity
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);
}
```

### Test: transferFrom reverts on insufficient allowance

```solidity
function test_RevertWhen_InsufficientAllowance() public {
    token.approve(bob, suint256(50e18));

    vm.prank(bob);
    vm.expectRevert("Insufficient allowance");
    token.transferFrom(alice, bob, suint256(100e18));
}
```

### Running the tests

Build and run from your contracts directory:

```bash
sforge test
```

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.


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

## The problem

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

```solidity
// This will NOT compile
event Transfer(address indexed from, address indexed to, suint256 amount);
```

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.

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

```solidity
event Transfer(address indexed from, address indexed to, bytes encryptedAmount);
```

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.

## Step by step

The encryption flow uses three of Seismic's precompiles:

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

```solidity
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;
}
```

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:

```solidity
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)));
}
```

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

```solidity
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)));
}
```

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.

### Step 3: Encrypt with AES-GCM

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

```solidity
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;
}
```

### Step 4: Emit the encrypted event

Putting it all together in an internal helper:

```solidity
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);
}
```

## Full implementation

Here is the updated `transfer` function using encrypted events:

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

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

## 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.
4. Decrypt the `encryptedAmount` from the event log using AES-GCM Decrypt.

Decrypt events client-side using the ECDH shared secret and AES-GCM decryption. See the [seismic-viem precompiles documentation](/clients/typescript/viem/precompiles) for the cryptographic primitives.

## Who can read what

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

| Data                | Who can see it | Why                                            |
| ------------------- | -------------- | ---------------------------------------------- |
| `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           |

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.

{% hint style="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.
{% endhint %}


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

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

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.

## 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` flag in SeismicElements should be set to `true`, in order to prevent anyone from replaying this payload as a write transaction.

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

## Contract implementation

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

```solidity
function balanceOf(address account) external view returns (uint256) {
    require(msg.sender == account, "Only owner can view balance");
    return uint256(balanceOf[account]);
}
```

{% hint style="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.
{% endhint %}

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 `uint256`** -- The shielded `suint256` value is cast to a regular `uint256` for the return value. Shielded types cannot be returned from external functions.
3. **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.

### Allowance checking

The same pattern applies to allowances:

```solidity
function allowanceOf(address owner, address spender) external view returns (uint256) {
    require(
        msg.sender == owner || msg.sender == spender,
        "Not authorized"
    );
    return uint256(allowance[owner][spender]);
}
```

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

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

### Using a shielded contract instance

```typescript
import { createShieldedWalletClient, getShieldedContract, seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// Create a shielded wallet client
const walletClient = await createShieldedWalletClient({
  chain: seismicTestnet,
  transport: http("https://testnet-1.seismictest.net/rpc"),
  account: privateKeyToAccount(PRIVATE_KEY),
});

// Get a contract instance
const token = getShieldedContract({
  abi: src20Abi,
  address: SRC20_ADDRESS,
  client: walletClient,
});

// Read balance -- this is automatically a signed read
const balance = await token.read.balanceOf([walletClient.account.address]);
console.log("My balance:", balance);
```

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.

### Using the wallet client directly

You can also call `readContract` on the wallet client:

```typescript
const balance = await walletClient.readContract({
  address: SRC20_ADDRESS,
  abi: src20Abi,
  functionName: "balanceOf",
  args: [walletClient.account.address],
});
```

Both approaches produce the same signed read.

## Security model

The signed read has several layers of protection:

| Property                  | How it works                                                                                                                                                     |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **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. |

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

## 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, [Intelligence Contracts](/tutorials/src20/intelligence-contracts), we will add authorized roles (such as compliance officers) who can view specific balances when required -- without compromising privacy for everyone else.


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

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

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

## Implementation with AccessControl

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

```solidity
// 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]);
    }
}
```

## Access tiers

The contract implements three levels of access:

| Role                                       | Can do                                     | How they access                                                                             |
| ------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------- |
| **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 |

### Granting roles

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

```solidity
// Grant compliance role to a specific address
token.grantRole(COMPLIANCE_ROLE, complianceOfficerAddress);

// Grant auditor role
token.grantRole(AUDITOR_ROLE, auditorAddress);
```

In TypeScript:

```typescript
const token = getShieldedContract({
  abi: src20Abi,
  address: SRC20_ADDRESS,
  client: adminWalletClient,
});

// Grant compliance role
await token.write.grantRole([COMPLIANCE_ROLE, complianceOfficerAddress]);
```

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

```typescript
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);
```

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

## 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.
3. **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.
4. **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.
5. **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.

{% hint style="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).
{% endhint %}


# 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 [wagmi](https://wagmi.sh/) to provide shielded reads, shielded writes, and encrypted communication out of the box. *Estimated time: \~25 minutes.*

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

## Setup

Install the required packages:

```bash
npm install seismic-viem seismic-react viem wagmi @tanstack/react-query
```

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

```tsx
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://testnet-1.seismictest.net/rpc"),
  },
});

const queryClient = new QueryClient();

function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <ShieldedWalletProvider config={config}>
          <TokenDashboard />
        </ShieldedWalletProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}
```

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

## Connecting to the contract

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

```typescript
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;
}
```

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

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

```typescript
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>
  );
}
```

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.

## Transferring tokens (shielded write)

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

```typescript
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>
  );
}
```

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.
4. Wraps everything in a Seismic transaction (type `0x4A`).
5. Broadcasts the encrypted transaction.

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

## Decrypting events

If your contract uses encrypted events (from the [Encrypted Events](/tutorials/src20/encrypted-events) 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 [seismic-viem precompiles documentation](/clients/typescript/viem/precompiles) for the cryptographic primitives.

## Complete example

Here is the full dashboard component that ties everything together:

```tsx
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>
  );
}
```

{% hint style="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 [Wallet Guides](/clients/typescript/react/wallet-guides) for integration details.
{% endhint %}

## 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 [Client Libraries section](/clients/clients) 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 [Wallet Guides](/clients/typescript/react/wallet-guides) for step-by-step instructions on integrating RainbowKit, Privy, or AppKit with `seismic-react`.
* **Deploy to testnet** -- See the [deploy section](/reference/migrating-from-ethereum#step-8-deploy) for deploying your SRC20 to a live Seismic network.
* **Extend the contract** -- Consider adding features like burn functions or governance mechanisms.


# Clown Beatdown

Imagine a clown standing in front of you, taunting the crowd. Hidden in the clown's pockets are **secrets** — strings that **only become visible once the clown is knocked out**. There are primarily two actions you can take: **hitting** the clown or **adding secrets** to the clown's pockets. **Hitting** the clown reduces its stamina by one, bringing it closer to being knocked out. **Adding a secret** stores an encrypted string that no one can read until it's revealed. **You can only rob the clown of a secret if you contributed to knocking it out, i.e., you hit the clown at least once in the current round.** This collaborative challenge is the heart of the Clown Beatdown game, and it's all powered by the ClownBeatdown 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 `contracts/src/ClownBeatdown.sol` file of the starter repo.

## State variables

### initialClownStamina and clownStamina

Think of stamina as the clown's durability. `initialClownStamina` is the clown's starting strength, and `clownStamina` tracks how much remains as players hit it.

### secrets, secretsCount, and secretIndex

These are the hidden values at the heart of the game. `secrets` is a mapping of `sbytes` (shielded bytes) — encrypted strings stored on-chain that remain hidden until revealed. `secretsCount` tracks how many secrets have been added. `secretIndex` is a `suint256` (shielded integer) that determines which secret will be revealed when the clown is robbed. Because `secretIndex` is shielded, no one can predict which secret will be returned.

### round

A counter that increments with each new round/reset, ensuring every round has a fresh clown to beat down.

### hitsPerRound

A mapping that records every player's contribution to the current round, ensuring only participants can rob the clown's secret.

## Functions

### addSecret (string \_secret)

This function allows anyone to add a secret to the clown's pool. Since `addSecret` converts a plain `string` into `sbytes` (shielded bytes) and stores it in the `secrets` mapping, it performs a **shielded write** — the secret's contents are encrypted on-chain and invisible to observers.

**What happens:**

* Converts the input string to `sbytes` and stores it in the `secrets` mapping
* Increments `secretsCount`
* Re-picks a random `secretIndex` using `_randomIndex()`

### hit ( )

This function allows a player to hit the clown, reducing its stamina and bringing it one step closer to being knocked out:

**What happens:**

* Checks if the clown is still standing (`clownStamina > 0`)
* If it is, decrements `clownStamina` 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.

### rob ( )

This function allows contributors to the current round to steal a randomly selected secret from the clown. Since this is a **view** function that **reveals an `sbytes` value**, calling this function constitutes a **signed read.**

**What happens:**

* Requires the clown to be knocked out (`requireDown` modifier)
* Ensures the function caller contributed to knocking out the clown for this round (`onlyContributor` modifier)
* Returns the secret at the shielded `secretIndex` position, decrypted from `sbytes` to `bytes`.

### reset ( )

This function resets the clown for a new round of gameplay:

**What happens:**

* Requires the clown to be knocked out (`requireDown` modifier)
* Restores `clownStamina` to `initialClownStamina`
* Picks a new random `secretIndex`
* Increments the `round` counter
* Emits the `Reset` event.

## Modifiers

Modifiers enforce the rules of the game:

### requireDown

Ensures that `rob()` and `reset()` can only be called if the clown's stamina has reached zero.

### requireStanding

Ensures that `hit()` can only be called while the clown still has stamina remaining.

### onlyContributor

Restricts access to `rob()`, and hence the secret being revealed, only to players who contributed **at least one hit in the current round.**


# Setting Up Your Project

In this chapter, you'll set up the foundation for the Clown Beatdown game 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.


# Verify devtool installation

Before continuing, ensure that you have completed the steps in the [Installation](/getting-started/installation) 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.

Also ensure that you have [bun](https://bun.sh) installed on your machine. If you do not have `bun` installed, follow the instructions [here](https://bun.sh/docs/installation) to install it on your machine.


# Create project structure

1. **Create the project folder and navigate into it:**

```bash
mkdir clown-beatdown
cd clown-beatdown
```

2. **Create the `packages` directory with subdirectories for `contracts` and `cli`**

```bash
mkdir -p packages/contracts packages/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.

3. **Initialize a bun project in the root directory:**

```bash
bun init -y && rm index.ts && rm tsconfig.json && touch .prettierrc && touch .gitmodules
```

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.

4. **Replace the default `package.json` with the following content for a monorepo setup:**

```json
{
  "workspaces": ["packages/**"],
  "dependencies": {},
  "devDependencies": {
    "@trivago/prettier-plugin-sort-imports": "^5.2.1",
    "prettier": "^3.4.2"
  }
}
```

5. **Add the following to the `.prettierrc` file for consistent code formatting:**

```json
{
  "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
}
```

6. **Replace the `.gitignore` file with:**

```gitignore
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env

node_modules/
```

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

```git
[submodule "packages/contracts/lib/forge-std"]
	path = packages/contracts/lib/forge-std
	url = https://github.com/foundry-rs/forge-std
```


# Initialize contracts

1. **Navigate to the contracts subdirectory:**

```bash
cd packages/contracts
```

2. **Initialize a project with `sforge`:**

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

This command will:

* 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)

3. Edit the `.gitignore` file to be the following:

```bash
.env
broadcast/
cache/
```

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

```bash
# Remove the Counter files
rm -f src/Counter.sol test/Counter.t.sol script/Counter.s.sol

# Create empty ClownBeatdown files in the same locations
touch src/ClownBeatdown.sol test/ClownBeatdown.t.sol script/ClownBeatdown.s.sol
```

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


# 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 CLI subdirectory:**

```bash
# Assuming you are currently in the contracts directory
cd ../cli
```

2. **Initialize a new bun project**

```bash
bun init -y
```

3. Now, create an `src/` folder and move `index.ts` there.

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

4. Now, edit `package.json` to be the following:

```json
{
  "name": "clown-beatdown-cli",
  "license": "MIT License",
  "type": "module",
  "scripts": {
    "dev": "bun run src/index.ts"
  },
  "dependencies": {
    "dotenv": "^16.4.7",
    "seismic-viem": "1.1.1",
    "viem": "^2.22.3"
  },
  "devDependencies": {
    "@types/node": "^22.7.6",
    "typescript": "^5.6.3"
  }
}
```

5. Edit `.gitignore` to be:

```gitignore
node_modules
```

Your environment is now set!


# Writing the Contract

This section dives into the heart of Clown Beatdown — the **shielded smart contract** that powers its functionality. You'll start by building the secrets pool using shielded storage, then implement the stamina system and the robbery mechanic, before adding rounds, resets, and contributor-based access control. By the end of this section, you'll have a fully functional, round-based ClownBeatdown contract that is secure, fair, and replayable.

### What You'll Learn

In this section, you'll:

* Define a pool of **shielded secrets** using `sbytes` and a shielded index using `suint256`.
* Build the **stamina bar**, the protective layer that guards the secrets, and implement a `hit()` function to reduce it.
* Add a `rob()` function to reveal a randomly selected secret to authorized contributors.
* Implement a reset mechanism to restart the game for multiple rounds.
* Track player contributions in each round, ensuring that only contributors can rob the clown.

### Overview of Chapters

* [**Chapter 1: The Secrets Pool**](/tutorials/understanding-the-clown-beatdown-contract/writing-the-contract/chapter-1-the-secrets-pool)

You'll define the secrets pool using shielded storage (`sbytes`) and a shielded index (`suint256`), and implement an `addSecret()` function to populate it. This chapter introduces **shielded writes.**

* [**Chapter 2: The Stamina Bar and Robbing Secrets**](/tutorials/understanding-the-clown-beatdown-contract/writing-the-contract/chapter-2-the-stamina-bar-and-robbing-secrets)

Learn how to build the stamina system, which protects the secrets from being accessed prematurely. You'll implement the `hit()` function to reduce stamina and the `rob()` function to reveal a secret once conditions are met.

* [**Chapter 3: Reset Mechanism, Rounds, and Contributor Access**](/tutorials/understanding-the-clown-beatdown-contract/writing-the-contract/chapter-3-reset-mechanism-rounds-and-contributor-access)

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 knock out the clown in a specific round can rob it. This chapter introduces **signed reads.**


# Ch 1: The Secrets Pool

In this chapter, you'll learn to create and initialize the secrets pool — a collection of hidden strings stored inside the clown's pockets — and implement a function to add new secrets. *Estimated time: \~10 minutes.*

### Defining the secrets pool

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

```solidity
// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;

contract ClownBeatdown {
    mapping(uint256 => sbytes) secrets; // Pool of possible secrets (shielded).
    uint256 secretsCount; // Number of secrets for modular arithmetic.
    suint256 secretIndex; // Shielded index into the secrets mapping.
    uint256 round; // The current round number (used by _randomIndex).

    constructor(uint256 _clownStamina) {
        round = 1; // Start with the first round.
    }
}
```

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

### Add the addSecret function

Next, let's implement a function to add secrets to the pool. The `addSecret` function takes a plain `string` and converts it to `sbytes` for shielded storage:

```solidity
function addSecret(string memory _secret) public {
    secrets[secretsCount] = sbytes(_secret);
    secretsCount++;
    secretIndex = suint256(_randomIndex()); // Re-pick a random secret.
}
```

### Add the random index helper

The `_randomIndex` function generates a pseudo-random index into the secrets array using on-chain randomness sources:

```solidity
// Generate a pseudo-random index into the secrets array.
function _randomIndex() private view returns (uint256) {
    return uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, round))) % secretsCount;
}
```

### What's happening here?

The `addSecret` function converts a plain `string` into `sbytes` (shielded bytes) and stores it in the `secrets` mapping. Because `sbytes` is a shielded type, the secret's contents are encrypted on-chain and invisible to observers.

The function also updates shielded state (`secretIndex` is a `suint256`), which makes a call to this function a **shielded write.**

Note that we're using two different shielded types here:

* **`sbytes`** — for storing encrypted strings (the secrets themselves)
* **`suint256`** — for storing an encrypted integer (the index that determines which secret gets revealed)


# Ch 2: Stamina and Robbing Secrets

In this chapter, you'll build the **stamina bar**, the protective layer that guards the clown's secrets. You'll initialize the clown's stamina and implement a `hit` function to reduce it. Additionally, you'll add a `rob()` function with a `requireDown` modifier to ensure secrets can only be stolen once the clown is knocked out. *Estimated Time: \~10 minutes.*

### Defining the stamina bar

The stamina bar determines the clown's resilience. It has an integer value (`clownStamina`), which represents how many hits the clown can withstand before going down. Let's define the stamina and initialize it in the constructor:

```solidity
    uint256 initialClownStamina; // Starting stamina restored on reset.
    uint256 clownStamina; // Remaining stamina before the clown is down.

    constructor(uint256 _clownStamina) {
        initialClownStamina = _clownStamina; // Set starting stamina.
        clownStamina = _clownStamina; // Initialize remaining stamina.
        round = 1; // Start with the first round.
    }
```

### Adding the hit function

Each time the clown is hit, its stamina decreases, bringing it one step closer to being knocked out. This is crucial for revealing secrets, **as the clown must be fully knocked out for secrets to be accessible:**

```solidity
    // Event to log hits.
    event Hit(uint256 indexed round, address indexed hitter, uint256 remaining);

    // Hit the clown to reduce stamina.
    function hit() public requireStanding {
        clownStamina--; // Decrease stamina.
        emit Hit(round, msg.sender, clownStamina); // Log the hit.
    }

    // Modifier to ensure the clown is still standing.
    modifier requireStanding() {
        require(clownStamina > 0, "CLOWN_ALREADY_DOWN");
        _;
    }
```

### What's happening here?

* **The `requireStanding` modifier**: Ensures that the function cannot be called if the clown is already knocked out (`clownStamina == 0`). This prevents unnecessary calls after the clown is down.
* **Decrementing stamina**: Each call to `hit` decreases the clown's stamina (`clownStamina`) by one.
* **Logging the action**: The `Hit` event records the round, the hitter's address (`msg.sender`), and the remaining stamina.

### Adding a stamina getter

Add a public view function so the current stamina can be checked:

```solidity
    // Get the current clown stamina.
    function getClownStamina() public view returns (uint256) {
        return clownStamina;
    }
```

### Robbing the clown

Now that we have implemented the stamina system and the ability to reduce it using the hit function, we can introduce the robbery mechanic: the clown's secret should only be revealed once it is fully knocked out.

* The secret **should remain hidden** while the **clown is standing**.
* The secret **can only be stolen** once the clown's stamina reaches **zero, i.e. when it is knocked out**.

To enforce this, we will create a function called `rob()`, which will return a randomly selected secret, but only if the clown has been fully knocked out.

Here's how we define `rob()` with a `requireDown` modifier:

```solidity
    // Reveal secret once the clown is down and the caller contributed.
    function rob() public view requireDown returns (bytes memory) {
        sbytes memory secret = secrets[uint256(secretIndex)];
        return bytes(secret); // Return the randomly selected secret.
    }

    // Modifier to ensure the clown is down.
    modifier requireDown() {
        require(clownStamina == 0, "CLOWN_STILL_STANDING");
        _;
    }
```

### What's happening here?

* **Restricting Access with a Condition**: The `requireDown` modifier ensures that `rob()` can only be called if `clownStamina == 0`, meaning the clown has been fully knocked out.
* **Revealing the Secret**: Once the condition is met, `rob()` reads the secret at the shielded `secretIndex` position. It converts the `sbytes` value to plain `bytes` for the caller.
* **Preventing Premature Access**: If `rob()` is called before the clown is knocked out, the function will revert with the error `"CLOWN_STILL_STANDING"`.

### Updated contract with hit and rob

```solidity
// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;

contract ClownBeatdown {
    uint256 initialClownStamina; // Starting stamina restored on reset.
    uint256 clownStamina; // Remaining stamina before the clown is down.
    uint256 round; // The current round number.

    mapping(uint256 => sbytes) secrets; // Pool of possible secrets (shielded).
    uint256 secretsCount; // Number of secrets for modular arithmetic.
    suint256 secretIndex; // Shielded index into the secrets mapping.

    // Event to log hits.
    event Hit(uint256 indexed round, address indexed hitter, uint256 remaining);

    constructor(uint256 _clownStamina) {
        initialClownStamina = _clownStamina; // Set starting stamina.
        clownStamina = _clownStamina; // Initialize remaining stamina.
        round = 1; // Start with the first round.
    }

    // Get the current clown stamina.
    function getClownStamina() public view returns (uint256) {
        return clownStamina;
    }

    function addSecret(string memory _secret) public {
        secrets[secretsCount] = sbytes(_secret);
        secretsCount++;
        secretIndex = suint256(_randomIndex()); // Re-pick a random secret.
    }

    // Hit the clown to reduce stamina.
    function hit() public requireStanding {
        clownStamina--; // Decrease stamina.
        emit Hit(round, msg.sender, clownStamina); // Log the hit.
    }

    // Reveal secret once the clown is down.
    function rob() public view requireDown returns (bytes memory) {
        sbytes memory secret = secrets[uint256(secretIndex)];
        return bytes(secret); // Return the randomly selected secret.
    }

    // Generate a pseudo-random index into the secrets array.
    function _randomIndex() private view returns (uint256) {
        return uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, round))) % secretsCount;
    }

    // Modifier to ensure the clown is down.
    modifier requireDown() {
        require(clownStamina == 0, "CLOWN_STILL_STANDING");
        _;
    }

    // Modifier to ensure the clown is still standing.
    modifier requireStanding() {
        require(clownStamina > 0, "CLOWN_ALREADY_DOWN");
        _;
    }
}
```


# Ch 3: Rounds and Contributor Access

In this chapter, we'll implement a reset mechanism that allows the clown to get back up for multiple rounds, ensuring each game session starts fresh. We'll also track contributors per round so that only players who participated in knocking out the clown can call `rob()`. By the end, we'll have a fully functional round-based game where secrets remain shielded until conditions are met! *Estimated time: \~15 minutes.*

### The need for a Reset mechanism

Right now, once the clown is knocked out, there's no way to reset it. If a game session were to continue, we'd have no way to start fresh — the stamina would remain at 0, and the secret would be permanently accessible.

To solve this, we need to introduce:

* A `reset` function that restores the clown to its original state.
* Round tracking, so each reset creates a new round.

### The need for a contributor check

While the reset mechanism and round tracking allow us to restart the game for continuous play, they still don't address **who** should be allowed to call the `rob()` function.

Right now, any player can call `rob()` once the clown is knocked out, 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 robbing the clown.
* **Incentivizing Contribution**: The game needs to encourage active participation by ensuring that only those who helped knock out the clown in a specific round are rewarded with access to the secret.

The solution to this is implementing a conditional check on `rob()` which allows only those players who **contributed at least one hit in the current round** to steal the secret.

### Implementing the Reset Mechanism

The reset mechanism allows the clown to get back up for multiple rounds, with each round starting fresh. It restores the clown's stamina and picks a new random secret, then increments the round counter.

Here's how we can implement the reset function:

```solidity
    // Event to log resets.
    event Reset(uint256 indexed newRound, uint256 remainingClownStamina);

    // Reset the beatdown for a new round.
    function reset() public requireDown {
        clownStamina = initialClownStamina; // Reset stamina.
        secretIndex = suint256(_randomIndex()); // Pick a new random secret.
        round++; // Move to the next round.
        emit Reset(round, clownStamina); // Log the reset.
    }
```

**What's Happening Here?**

* **Condition for Reset (`requireDown`):** The reset function can only be called once the clown is knocked out, enforced by the `requireDown` modifier.
* **Restoring Initial State**: The stamina is reset to `initialClownStamina`, and a new random secret is selected via `_randomIndex()`.
* **Round Tracking**: The `round` counter increments each time the clown is reset, allowing us to distinguish between rounds.

### Modifying hit() to track contributions

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

```solidity
    // Tracks the number of hits per player per round.
    mapping(uint256 => mapping(address => uint256)) hitsPerRound;
```

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

```solidity
    // Hit the clown to reduce stamina.
    function hit() public requireStanding {
        clownStamina--; // Decrease stamina.
        hitsPerRound[round][msg.sender]++; // Record the player's hit for the current round.
        emit Hit(round, msg.sender, clownStamina); // Log the hit.
    }
```

**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 clown was knocked out.
* **Replayable Rounds**: Because contributions are tracked by round, the game can fairly reset and start fresh without losing player data from previous rounds.

### Restricting rob() with a contributor check

To ensure only contributors can rob the clown, we'll use a modifier called `onlyContributor`:

```solidity
    // Modifier to ensure the caller has contributed in the current round.
    modifier onlyContributor() {
        require(hitsPerRound[round][msg.sender] > 0, "NOT_A_CONTRIBUTOR");
        _;
    }
```

We'll then apply this modifier to the `rob()` function:

```solidity
    // Reveal secret once the clown is down and the caller contributed.
    function rob() public view requireDown onlyContributor returns (bytes memory) {
        sbytes memory secret = secrets[uint256(secretIndex)];
        return bytes(secret); // Return the randomly selected secret.
    }
```

**Congratulations!** You made it through writing the entire shielded smart contract for a multiplayer, multi-round, Clown Beatdown game!

**Final ClownBeatdown contract**

```solidity
// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;

contract ClownBeatdown {
    uint256 initialClownStamina; // Starting stamina restored on reset.
    uint256 clownStamina; // Remaining stamina before the clown is down.
    uint256 round; // The current round number.

    mapping(uint256 => sbytes) secrets; // Pool of possible secrets (shielded).
    uint256 secretsCount; // Number of secrets for modular arithmetic.
    suint256 secretIndex; // Shielded index into the secrets mapping.

    // Tracks the number of hits per player per round.
    mapping(uint256 => mapping(address => uint256)) hitsPerRound;

    // Events to log hits and resets.

    // Event to log hits.
    event Hit(uint256 indexed round, address indexed hitter, uint256 remaining); // Logged when a hit lands.
    // Event to log resets.
    event Reset(uint256 indexed newRound, uint256 remainingClownStamina);

    constructor(uint256 _clownStamina) {
        initialClownStamina = _clownStamina; // Set starting stamina.
        clownStamina = _clownStamina; // Initialize remaining stamina.
        round = 1; // Start with the first round.
    }

    // Get the current clown stamina.
    function getClownStamina() public view returns (uint256) {
        return clownStamina;
    }

    function addSecret(string memory _secret) public {
        secrets[secretsCount] = sbytes(_secret);
        secretsCount++;
        secretIndex = suint256(_randomIndex()); // Re-pick a random secret.
    }

    // Hit the clown to reduce stamina.
    function hit() public requireStanding {
        clownStamina--; // Decrease stamina.
        hitsPerRound[round][msg.sender]++; // Record the player's hit for the current round.
        emit Hit(round, msg.sender, clownStamina); // Log the hit.
    }


    // Reset the beatdown for a new round.
    function reset() public requireDown {
        clownStamina = initialClownStamina; // Reset stamina.
        secretIndex = suint256(_randomIndex()); // Pick a new random secret.
        round++; // Move to the next round.
        emit Reset(round, clownStamina); // Log the reset.
    }

    // Reveal secret once the clown is down and the caller contributed.
 function rob() public view requireDown onlyContributor returns (bytes memory) {
        sbytes memory secret = secrets[uint256(secretIndex)];
        return bytes(secret); // Return the randomly selected secret.
    }

    // Generate a pseudo-random index into the secrets array.
    function _randomIndex() private view returns (uint256) {
        return uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, round))) % secretsCount;
    }

    // Modifier to ensure the clown is down.
    modifier requireDown() {
        require(clownStamina == 0, "CLOWN_STILL_STANDING");
        _;
    }

    // Modifier to ensure the clown is still standing.
    modifier requireStanding() {
        require(clownStamina > 0, "CLOWN_ALREADY_DOWN");
        _;
    }

    // Modifier to ensure the caller has contributed in the current round.
    modifier onlyContributor() {
        require(hitsPerRound[round][msg.sender] > 0, "NOT_A_CONTRIBUTOR");
        _;
    }
}
```

Now, onto testing the contract!


# Ch 4: Testing

In this chapter, you'll write tests to verify that the ClownBeatdown 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.*

### Getting Started

Navigate to the test folder in your project and open the `ClownBeatdown.t.sol` file located at:

```bash
packages/contracts/test/ClownBeatdown.t.sol
```

This file is where you'll write all the test cases for the ClownBeatdown contract. Start with the following base code:

```solidity
// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";
import {ClownBeatdown} from "../src/ClownBeatdown.sol";

contract ClownBeatdownTest is Test {
    ClownBeatdown public clownBeatdown;

    function setUp() public {
        clownBeatdown = new ClownBeatdown(2);
        clownBeatdown.addSecret("Secret A");
        clownBeatdown.addSecret("Secret B");
        clownBeatdown.addSecret("Secret C");
    }
}
```

The `setUp()` function initializes the ClownBeatdown contract with a stamina of 2 and adds three secrets to the pool.

### Writing Test Cases

Start off with testing the basic functionalities: `hit`, `rob`, and `reset`.

#### Core functionalities

1. **Basic hit functionality**

Ensures the clown's stamina decreases when hit.

```solidity
function test_Hit() public {
    clownBeatdown.hit();
    assertEq(clownBeatdown.getClownStamina(), 1);
}
```

2. **Knockout and rob**

Validates that after knocking out the clown, a contributor can rob a valid secret.

```solidity
function test_KnockoutAndRob() public {
    clownBeatdown.hit();
    clownBeatdown.hit();
    // rob() should return one of the secrets
    bytes memory secret = clownBeatdown.rob();
    assertTrue(
        keccak256(secret) == keccak256(bytes("Secret A")) ||
        keccak256(secret) == keccak256(bytes("Secret B")) ||
        keccak256(secret) == keccak256(bytes("Secret C"))
    );
}
```

3. **Reset functionality**

```solidity
function test_Reset() public {
    clownBeatdown.hit();
    clownBeatdown.hit();
    clownBeatdown.reset();
    assertEq(clownBeatdown.getClownStamina(), 2); // Stamina should be reset to 2
}
```

4. **Secret can change after reset**

Validates that secrets returned across rounds are always valid (they may or may not differ depending on randomness).

```solidity
function test_SecretCanChangeAfterReset() public {
    // Knock out and rob in round 1
    clownBeatdown.hit();
    clownBeatdown.hit();
    bytes memory secret1 = clownBeatdown.rob();

    // Reset and knock out again in round 2
    clownBeatdown.reset();
    clownBeatdown.hit();
    clownBeatdown.hit();
    bytes memory secret2 = clownBeatdown.rob();

    // Both should be valid secrets (they may or may not differ depending on randomness)
    assertTrue(
        keccak256(secret1) == keccak256(bytes("Secret A")) ||
        keccak256(secret1) == keccak256(bytes("Secret B")) ||
        keccak256(secret1) == keccak256(bytes("Secret C"))
    );
    assertTrue(
        keccak256(secret2) == keccak256(bytes("Secret A")) ||
        keccak256(secret2) == keccak256(bytes("Secret B")) ||
        keccak256(secret2) == keccak256(bytes("Secret C"))
    );
}
```

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

#### Restricting Actions

1. **Preventing `hit` when clown is down**

Ensures that hitting a knocked-out clown is not allowed.

```solidity
function test_CannotHitWhenDown() public {
    clownBeatdown.hit();
    clownBeatdown.hit();
    vm.expectRevert("CLOWN_ALREADY_DOWN");
    clownBeatdown.hit();
}
```

2. **Preventing `rob` when clown is standing**

Ensures that robbing while the clown still has stamina is not allowed.

```solidity
function test_CannotRobWhenStanding() public {
    clownBeatdown.hit();
    vm.expectRevert("CLOWN_STILL_STANDING");
    clownBeatdown.rob();
}
```

3. **Preventing `reset` when clown is standing**

Validates that the clown cannot be reset unless it is fully knocked out.

```solidity
function test_CannotResetWhenStanding() public {
    vm.expectRevert("CLOWN_STILL_STANDING");
    clownBeatdown.reset();
}
```

Now, test for more complex scenarios.

#### Complex scenarios

1. **Prevent Non-Contributors From Using `rob()`**

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

```solidity
function test_RevertWhen_NonContributorTriesToRob() public {
    address nonContributor = address(0xabcd);

    // Knock out the clown
    clownBeatdown.hit();
    clownBeatdown.hit();

    // Non-contributor should be rejected
    vm.prank(nonContributor);
    vm.expectRevert("NOT_A_CONTRIBUTOR");
    clownBeatdown.rob();

    // Original contributor can still rob
    bytes memory secret = clownBeatdown.rob();
    assertTrue(secret.length > 0);
}
```

2. **Contributor Tracking Across Rounds**

Validates that contributions are tracked independently for each round. The test has one contributor knock out the clown in round 1, and a different contributor knock it out in round 2. We check that the round 2 contributor can rob while the round 1 contributor cannot.

```solidity
function test_ContributorInRound2() public {
    address contributorRound2 = address(0xabcd);

    // Round 1: knocked out by address(this)
    clownBeatdown.hit();
    clownBeatdown.hit();
    bytes memory secret1 = clownBeatdown.rob();
    assertTrue(secret1.length > 0);

    // Reset for round 2
    clownBeatdown.reset();

    // Round 2: knocked out by contributorRound2
    vm.prank(contributorRound2);
    clownBeatdown.hit();
    vm.prank(contributorRound2);
    clownBeatdown.hit();

    // contributorRound2 can rob in round 2
    vm.prank(contributorRound2);
    bytes memory secret2 = clownBeatdown.rob();
    assertTrue(secret2.length > 0);

    // address(this) cannot rob in round 2 (not a contributor this round)
    vm.expectRevert("NOT_A_CONTRIBUTOR");
    clownBeatdown.rob();
}
```

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

```
sforge build
sforge test
```

The contract has been tested, time to deploy it!


# Deploying

In this chapter, you'll deploy your ClownBeatdown 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.*

### Writing the deploy script

Navigate to the script folder in your project and open the `ClownBeatdown.s.sol` file located at:

```bash
packages/contracts/script
```

and add the following to it:

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {ClownBeatdown} from "../src/ClownBeatdown.sol";

contract ClownBeatdownScript is Script {
    ClownBeatdown public clownBeatdown;

    function run() public {
        uint256 deployerPrivateKey = vm.envUint("PRIVKEY");

        vm.startBroadcast(deployerPrivateKey);
        clownBeatdown = new ClownBeatdown(3);
        vm.stopBroadcast();

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

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

### Deploying the contract

1. In a separate terminal window, run

```bash
sanvil
```

in order to spin up a local Seismic node.

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

```properties
RPC_URL=http://127.0.0.1:8545
PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```

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

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

```bash
source .env
sforge script script/ClownBeatdown.s.sol:ClownBeatdownScript \
    --rpc-url $RPC_URL \
    --broadcast
```

The output will show the deployed contract address (e.g. `0x5FbDB2315678afecb367f032d93F642f64180aa3`).

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

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

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


# Building the CLI

In this section, you will write a CLI to interact with the deployed ClownBeatdown 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*


# 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](/clients/typescript/viem). *Estimated time: \~15 minutes*

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

```typescript
const walletClient = await createShieldedWalletClient({
  chain: seismicChain,
  transport: httpTransport,
  privateKey: "0xabcdef...",
});
```

`createShieldedWalletClient` takes in the following parameters:

1. `chain`: a well-defined [Chain](https://github.com/wevm/viem/blob/main/src/types/chain.ts) object
2. `transport`: the method of transport of interacting with the chain (`http`/`ws` along with the corresponding RPC URL)
3. `privateKey`: the private key to create the client for

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

### 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 `getShieldedContract` as follows:

```typescript
const contract = getShieldedContract({
  abi: myContractAbi,
  address: "0x1234...",
  client: shieldedWalletClient,
});
```

It takes in the following parameters:

1. `abi`: the ABI of the contract it is interacting with.
2. `address`: the address of the deployed contract it is interacting with.
3. `client`: the shielded wallet client that the interactions are to be performed by.

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.
* Proxy-based access to dynamically invoke contract methods.

```typescript
// 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);
```

We will extensively use **shielded writes (for `addSecret`) and signed reads (for `rob()`) in our CLI.**


# Ch 1: Constants and Utilities

In this chapter, you will learn about defining 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, navigate to make a `lib` folder inside `packages/cli` with the files `constants.ts` and `utils.ts` and navigate to it:

```
mkdir -p packages/cli/lib
touch packages/cli/lib/constants.ts packages/cli/lib/utils.ts
cd packages/cli/lib
```

`constants.ts` will contain the constants we use throughout the project while `utils.ts` will contain the necessary utility functions.

Add the following to `constants.ts`:

```typescript
import { join } from "path";

const CONTRACT_NAME = "ClownBeatdown";
const CONTRACT_DIR = join(__dirname, "../../contracts");

export { CONTRACT_NAME, CONTRACT_DIR };
```

This file centralizes key project constants:

* `CONTRACT_NAME`: The ClownBeatdown contract name.
* `CONTRACT_DIR`: Path to the contracts directory.

Now, add the following to `utils.ts`:

```typescript
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 };
```

This file contains utility functions to interact with your ClownBeatdown 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.


# Ch 2: Core App Logic

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

Now, navigate to `packages/cli/src/` and create a file called `app.ts` which will contain the core logic for the CLI:

```
# Assuming you are in packages/cli/lib
cd ../src
touch app.ts
```

### Import required dependencies

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

```typescript
import {
  type ShieldedContract,
  type ShieldedWalletClient,
  createShieldedWalletClient,
} from "seismic-viem";
import { Abi, Address, Chain, http, hexToString } from "viem";
import { privateKeyToAccount } from "viem/accounts";

import { getShieldedContractWithCheck } from "../lib/utils";
```

### Define the app configuration

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

```typescript
interface AppConfig {
  players: Array<{
    name: string; // Name of the player
    privateKey: string; // Private key for the player's wallet
  }>;
  wallet: {
    chain: Chain; // Blockchain network (e.g., Seismic Testnet or sanvil)
    rpcUrl: string; // RPC URL for blockchain communication
  };
  contract: {
    abi: Abi; // The contract's ABI for interaction
    address: Address; // The contract's deployed address
  };
}
```

### Create the App class

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

```typescript
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;
  }
}
```

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

```typescript
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
  }
}
```

### Add helper methods to App

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

`getPlayerContract`:

```typescript
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
}
```

### Implement Contract Interaction Methods

`reset`

Resets the clown for the next round. The reset restores stamina and picks a new random secret.

```typescript
async reset(playerName: string) {
  console.log(`- Player ${playerName} writing reset()`)
  const contract = this.getPlayerContract(playerName)
  await contract.write.reset([])
}
```

`hit`

A player can hit the clown to reduce its stamina. Each hit is logged for the respective player.

```typescript
async hit(playerName: string) {
  console.log(`- Player ${playerName} writing hit()`)
  const contract = this.getPlayerContract(playerName)
  await contract.write.hit([])
}
```

`rob`

Reveals a secret for a specific player if they contributed to knocking out the clown. This ensures fairness in multiplayer gameplay. Uses **signed reads.**

```typescript
async rob(playerName: string) {
  console.log(`- Player ${playerName} reading rob()`)
  const contract = this.getPlayerContract(playerName)
  const result = await contract.read.rob() // signed read
  const decoded = hexToString(result as `0x${string}`)
  console.log(`- Player ${playerName} robbed secret:`, decoded)
}
```


# Ch 3: Bringing It All Together

Now that we've built the core logic for interacting with the ClownBeatdown 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*

### Set Up Environment Variables

Before running the 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`:

```bash
touch .env
```

Open `.env` and paste the following:

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

**What's Happening Here?**

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

### 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 ClownBeatdown contract. Open `packages/cli/src/index.ts` and follow these steps:

#### Import Dependencies

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

```typescript
import dotenv from "dotenv";
import { join } from "path";
import { sanvil, seismicTestnet } from "seismic-viem";

import { CONTRACT_DIR, CONTRACT_NAME } from "../lib/constants";
import { readContractABI, readContractAddress } from "../lib/utils";
import { App } from "./app";

// Load environment variables from .env file
dotenv.config();
```

#### Define the main() function

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

```typescript
async function main() {
  if (!process.env.CHAIN_ID || !process.env.RPC_URL) {
    console.error('Please set your environment variables.')
    process.exit(1)
  }
```

#### Read Contract Details

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

```typescript
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`,
);
```

#### Select the blockchain network

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

```typescript
const chain =
  process.env.VITE_CHAIN_ID === sanvil.id.toString() ? sanvil : seismicTestnet;
```

#### Define players

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

```typescript
const players = [
  { name: "Alice", privateKey: process.env.ALICE_PRIVKEY! },
  { name: "Bob", privateKey: process.env.BOB_PRIVKEY! },
];
```

#### Initialize the Game App

Create an `App` instance to interact with the ClownBeatdown contract.

```typescript
const app = new App({
  players,
  wallet: {
    chain,
    rpcUrl: process.env.RPC_URL!,
  },
  contract: {
    abi: readContractABI(abiFile),
    address: readContractAddress(broadcastFile),
  },
});

await app.init();
```

#### Simulate the game round by round

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

**Round 1 — Alice Plays**

Alice hits the clown three times (stamina starts at 3), knocking it out, then robs a secret.

```typescript
console.log("=== Round 1 ===");
await app.hit("Alice");
await app.hit("Alice");
await app.hit("Alice");

// Alice robs the clown's secret in round 1
await app.rob("Alice");
```

**Round 2 — Bob Plays**

Bob resets the clown, hits it three more times, then robs his own secret.

```typescript
console.log("=== Round 2 ===");
await app.reset("Bob");
await app.hit("Bob");
await app.hit("Bob");
await app.hit("Bob");

// Bob robs the clown's secret in round 2
await app.rob("Bob");
```

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

```typescript
  // Alice tries to rob in round 2, should fail by reverting
  console.log('=== Testing Access Control ===')
  console.log("Attempting Alice's rob() in Bob's round (should revert)")
  try {
    await app.rob('Alice')
    console.error('Expected rob() to revert but it succeeded')
    process.exit(1)
  } catch (error) {
    console.log('Received expected revert')
  }
}
```

### Execute the main() function

This ensures that the script runs when executed.

```typescript
main();
```

### Running the CLI

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

```bash
bun dev
```

You should see something like this as the output:

```
=== Round 1 ===
- Player Alice writing hit()
- Player Alice writing hit()
- Player Alice writing hit()
- Player Alice reading rob()
- Player Alice robbed secret: The cake is a lie
=== Round 2 ===
- Player Bob writing reset()
- Player Bob writing hit()
- Player Bob writing hit()
- Player Bob writing hit()
- Player Bob reading rob()
- Player Bob robbed secret: 42 is the answer
=== Testing Access Control ===
Attempting Alice's rob() in Bob's round (should revert)
Received expected revert
```

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

Congratulations! You've reached the end of the tutorial.


# Building the Frontend

In this section, you'll build a React frontend for the Clown Beatdown game. Players connect their wallet, punch the clown to reduce its stamina, and rob a shielded secret once it's knocked out — all from the browser. *Estimated time: \~45 minutes*

The frontend uses **seismic-react** to integrate shielded wallet functionality with a standard React + wagmi + RainbowKit stack. By the end of this section, you'll have a fully playable web app running against your local Seismic node.

### What You'll Learn

* Set up a React + Vite project with **seismic-react**, **wagmi**, and **RainbowKit**
* Configure providers for shielded wallet support
* Build custom hooks to interact with the ClownBeatdown contract
* Create game UI components with animations and responsive layout

### Overview of Chapters

* [**Ch 1: Project Setup and Providers**](/tutorials/understanding-the-clown-beatdown-contract/building-the-frontend/chapter-1-project-setup-and-providers)

Install dependencies, configure Vite, and wire up the provider stack: WagmiProvider, RainbowKitProvider, and ShieldedWalletProvider from seismic-react.

* [**Ch 2: Contract Hooks**](/tutorials/understanding-the-clown-beatdown-contract/building-the-frontend/chapter-2-contract-hooks)

Build the hooks that connect your UI to the ClownBeatdown contract — `useContract`, `useContractClient`, and `useGameActions`.

* [**Ch 3: Game UI Components**](/tutorials/understanding-the-clown-beatdown-contract/building-the-frontend/chapter-3-game-ui-components)

Create the game interface: the clown sprite with punch animations, action buttons (hit, rob, reset), and the entry screen with wallet connection.


# Ch 1: Project Setup and Providers

In this chapter, you'll set up the React frontend project and configure the provider stack that enables shielded wallet interactions in the browser. *Estimated time: \~15 minutes*

### Creating the web package

From the root of your `clown-beatdown` monorepo, create a new Vite + React + TypeScript project:

```bash
cd packages
bun create vite web --template react-ts
cd web
```

### Install dependencies

Install the core dependencies for wallet connection, shielded interactions, UI, and animations:

```bash
bun add seismic-react@1.1.1 seismic-viem@1.1.1 viem@^2.22.3 \
  wagmi@^2.0.0 @rainbow-me/rainbowkit@^2.0.0 \
  @tanstack/react-query@^5.55.3 \
  @mui/material@^6.4.3 @emotion/react @emotion/styled \
  framer-motion@^12.7.3 react-router-dom@^7.1.4 \
  react-toastify@^11.0.5 use-sound@^5.0.0 \
  react-redux@^9.2.0 @reduxjs/toolkit@^2.5.1 \
  @tailwindcss/vite tailwindcss@^4
```

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

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

### Copy public assets

Copy the `public/` folder from the [seismic-starter](https://github.com/SeismicSystems/seismic-starter) repo into `packages/web/public/`. This includes the clown sprites, button images, background, logo, and audio files used by the game UI.

### Configure Vite

Update `vite.config.ts`:

```typescript
import { resolve } from "path";
import { defineConfig } from "vite";

import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  envDir: resolve(__dirname, "../.."),
  resolve: {
    alias: {
      "@": resolve(__dirname, "src"),
    },
  },
});
```

The `envDir` points to the monorepo root so that `.env` files at the top level are available to the web package.

### Environment variables

Create a `.env` file at the monorepo root:

```properties
VITE_CHAIN_ID=31337
VITE_RPC_URL=http://127.0.0.1:8545
VITE_FAUCET_URL=https://faucet-2.seismicdev.net/
```

`VITE_CHAIN_ID` determines which chain the app connects to — `31337` is the local `sanvil` node.

### Setting up the provider stack

The key architectural pattern in a seismic-react app is the **provider stack**. This wraps your application in the context providers needed for wallet connection and shielded operations.

Create `src/App.tsx`:

```typescript
import React from 'react'
import { PropsWithChildren, useCallback } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import {
  type OnAddressChangeParams,
  ShieldedWalletProvider,
} from 'seismic-react'
import { sanvil, seismicTestnet } from 'seismic-react/rainbowkit'
import { http } from 'viem'
import { type Config, WagmiProvider } from 'wagmi'

import { AuthProvider } from '@/components/chain/WalletConnectButton'
import Home from '@/pages/Home'
import NotFound from '@/pages/NotFound'
import { getDefaultConfig } from '@rainbow-me/rainbowkit'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

import './App.css'

const configuredChainId = String(import.meta.env.VITE_CHAIN_ID ?? '')
const isSanvilConfig =
  configuredChainId === 'sanvil' || configuredChainId === String(sanvil.id)
const CHAIN = isSanvilConfig ? sanvil : seismicTestnet
const CHAINS = [CHAIN]

const config = getDefaultConfig({
  appName: 'Seismic Starter',
  projectId: 'd705c8eaf9e6f732e1ddb8350222cdac',
  // @ts-expect-error: this is fine
  chains: CHAINS,
  ssr: false,
})

const client = new QueryClient()

const Providers: React.FC<PropsWithChildren<{ config: Config }>> = ({
  config,
  children,
}) => {
  const publicChain = CHAINS[0]
  const publicTransport = http(publicChain.rpcUrls.default.http[0])
  const handleAddressChange = useCallback(
    async ({ publicClient, address }: OnAddressChangeParams) => {
      if (publicClient.chain.id !== sanvil.id) return

      const existingBalance = await publicClient.getBalance({ address })
      if (existingBalance > 0n) return

      const setBalance = publicClient.request as unknown as (args: {
        method: string
        params?: unknown[]
      }) => Promise<unknown>

      await setBalance({
        method: 'anvil_setBalance',
        params: [address, `0x${(10_000n * 10n ** 18n).toString(16)}`],
      })
    },
    []
  )

  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={client}>
        <RainbowKitProvider>
          <ShieldedWalletProvider
            config={config}
            options={{
              publicTransport,
              publicChain,
              onAddressChange: handleAddressChange,
            }}
          >
            <AuthProvider>{children}</AuthProvider>
          </ShieldedWalletProvider>
        </RainbowKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  )
}

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <Providers config={config}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </Providers>
    </BrowserRouter>
  )
}

export default App
```

### What's happening here?

The provider stack nests four layers, each adding functionality:

1. **WagmiProvider** — manages wallet connections and chain state via wagmi hooks (`useAccount`, `useConnect`, etc.)
2. **QueryClientProvider** — provides React Query for caching and background data fetching
3. **RainbowKitProvider** — adds a polished wallet connect modal UI
4. **ShieldedWalletProvider** — the Seismic-specific layer from `seismic-react` that derives a shielded wallet client from the connected wagmi account, enabling shielded reads and writes. It takes `config` and `options` — the options include `publicTransport`, `publicChain`, and an `onAddressChange` callback.

The `onAddressChange` handler auto-funds new wallets when running on `sanvil` (local dev), so you don't need to manually send ETH to test accounts.

### Supporting files

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

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

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

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

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

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

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

export default theme
```

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

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

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

Create `src/pages/NotFound.tsx`:

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

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

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

### Entry point: main.tsx

Create `src/main.tsx` to bootstrap the app with theme and state management:

```typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { ToastContainer } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'

import App from '@/App.tsx'
import { store } from '@/store/store'
import theme from '@/theme.ts'
import { ThemeProvider } from '@mui/material/styles'

import './index.css'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ThemeProvider theme={theme}>
      <Provider store={store}>
        <App />
        <ToastContainer />
      </Provider>
    </ThemeProvider>
  </StrictMode>
)
```


# Ch 2: Contract Hooks

In this chapter, you'll build the React hooks that connect your UI to the ClownBeatdown contract. These hooks encapsulate all contract interaction logic so your components stay clean. *Estimated time: \~15 minutes*

### Contract ABI setup

First, copy the compiled ABI from your contracts build output into the web package. After deploying (see [Deploying](/tutorials/understanding-the-clown-beatdown-contract/writing-the-contract/deploying)), copy the ABI file:

```bash
mkdir -p packages/web/src/abis/contracts
cp packages/contracts/out/ClownBeatdown.sol/ClownBeatdown.json \
   packages/web/src/abis/contracts/ClownBeatdown.json
```

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

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

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

### Contract type definition

Create `src/types/contract.ts`:

```typescript
export type ContractInterface = {
  chainId: number;
  abi: Array<Record<string, unknown>>;
  methodIdentifiers: Record<string, string>;
};

export type DeployedContract = ContractInterface & {
  address: `0x${string}`;
};
```

### useContract hook

This hook creates a shielded contract instance using `seismic-react`. Create `src/hooks/useContract.ts`:

```typescript
import { useShieldedContract } from "seismic-react";

import * as contractJson from "@/abis/contracts/ClownBeatdown.json" with { type: "json" };
import type { DeployedContract } from "@/types/contract";

export const useAppContract = () =>
  useShieldedContract(contractJson as DeployedContract);
```

The `useShieldedContract` hook from `seismic-react` returns a contract instance that supports both shielded writes and signed reads — the same interface you used in the CLI with `getShieldedContract`, but integrated with React's lifecycle.

### useContractClient hook

This hook wraps the contract methods into callable functions with proper error handling. Create `src/hooks/useContractClient.ts`:

```typescript
import { useCallback, useEffect, useState } from "react";
import { useShieldedWallet } from "seismic-react";
import {
  type ShieldedPublicClient,
  type ShieldedWalletClient,
  addressExplorerUrl,
  txExplorerUrl,
} from "seismic-viem";
import { type Hex, hexToString } from "viem";

import { useAppContract } from "./useContract";

export const useContractClient = () => {
  const [loaded, setLoaded] = useState(false);
  const { walletClient, publicClient } = useShieldedWallet();
  const { contract } = useAppContract();

  useEffect(() => {
    if (walletClient && publicClient && contract) {
      setLoaded(true);
    } else {
      setLoaded(false);
    }
  }, [walletClient, publicClient, contract]);

  const wallet = useCallback((): ShieldedWalletClient => {
    if (!walletClient) {
      throw new Error("Wallet client not found");
    }
    return walletClient;
  }, [walletClient]);

  const pubClient = useCallback((): ShieldedPublicClient => {
    if (!publicClient) {
      throw new Error("Public client not found");
    }
    return publicClient;
  }, [publicClient]);

  const walletAddress = useCallback((): Hex => {
    return wallet().account.address;
  }, [wallet]);

  const appContract = useCallback((): ReturnType<
    typeof useAppContract
  >["contract"] => {
    if (!contract) {
      throw new Error("Contract not found");
    }
    return contract;
  }, [contract]);

  /*
    function getClownStamina() external view returns (uint256);
    function rob() external view returns (bytes32);
    function hit() external;
    function reset() external;
  */

  const clownStamina = useCallback(async (): Promise<bigint> => {
    return appContract().tread.getClownStamina();
  }, [appContract]);

  const rob = useCallback(async (): Promise<string> => {
    const result = (await appContract().read.rob()) as Hex;
    return hexToString(result);
  }, [appContract]);

  const hit = useCallback(async (): Promise<Hex> => {
    return appContract().twrite.hit();
  }, [appContract]);

  const reset = useCallback(async (): Promise<Hex> => {
    return appContract().twrite.reset();
  }, [appContract]);

  const txUrl = useCallback(
    (txHash: Hex): string | null => {
      return txExplorerUrl({ chain: pubClient().chain, txHash });
    },
    [pubClient],
  );

  const addressUrl = useCallback(
    (address: Hex): string | null => {
      return addressExplorerUrl({ chain: pubClient().chain, address });
    },
    [pubClient],
  );

  const waitForTransaction = useCallback(
    async (hash: Hex) => {
      return await pubClient().waitForTransactionReceipt({ hash });
    },
    [pubClient],
  );

  return {
    loaded,
    walletClient,
    publicClient,
    walletAddress,
    appContract,
    pubClient,
    wallet,
    clownStamina,
    rob,
    hit,
    reset,
    txUrl,
    addressUrl,
    waitForTransaction,
  };
};
```

### What's happening here?

Notice the different contract namespaces used for each method:

* **`appContract().twrite.hit()`** and **`appContract().twrite.reset()`** — these are **shielded write** transactions. The `twrite` namespace sends a Seismic transaction (type 0x70) that encrypts calldata.
* **`appContract().read.rob()`** — this is a **signed read**. The `read` namespace performs a `signed_call` that proves the caller's identity to the contract, allowing `onlyContributor` to verify access. The result comes back as `Hex` and is decoded with `hexToString()`.
* **`appContract().tread.getClownStamina()`** — this is a **transparent read**. The `tread` namespace performs a standard `eth_call` since stamina is public state.

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

### Supporting components

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

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

```typescript
import React from 'react'

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

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

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

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

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

### useGameActions hook

This hook orchestrates the game logic, managing state and coordinating contract calls with UI feedback. Create `src/hooks/useGameActions.ts`:

```typescript
import { useCallback, useEffect, useState } from "react";
import React from "react";
import { useSound } from "use-sound";

import { ExplorerToast } from "@/components/chain/ExplorerToast";
import { useContractClient } from "@/hooks/useContractClient";
import { useToastNotifications } from "@/hooks/useToastNotifications";

export const useGameActions = () => {
  const [clownStamina, setClownStamina] = useState<number | null>(null);
  const [currentRoundId] = useState<number | null>(1);

  const {
    loaded,
    hit,
    rob,
    reset,
    txUrl,
    waitForTransaction,
    clownStamina: readClownStamina,
  } = useContractClient();

  const { notifySuccess, notifyError, notifyInfo } = useToastNotifications();
  const [isHitting, setIsHitting] = useState(false);
  const [isResetting, setIsResetting] = useState(false);
  const [isRobbing, setIsRobbing] = useState(false);
  const [robResult, setRobResult] = useState<string | null>(null);
  const [punchCount, setPunchCount] = useState(0);
  const [playHit] = useSound("/audio/hit_sfx.wav", { volume: 0.1 });
  const [playReset] = useSound("/audio/reset_sfx.wav", { volume: 0.1 });
  const [playRob] = useSound("/audio/rob_sfx.wav", { volume: 0.1 });

  const fetchGameRounds = useCallback(() => {
    if (!loaded) return;
    readClownStamina()
      .then((stamina) => {
        setClownStamina(Number(stamina));
      })
      .catch((error) => {
        const message = error instanceof Error ? error.message : String(error);
        console.error("Error fetching clown stamina:", message);
      });
  }, [loaded, readClownStamina]);

  // Fetch initial state when contract is loaded
  useEffect(() => {
    fetchGameRounds();
  }, [fetchGameRounds]);

  const resetGameState = useCallback(() => {
    setRobResult(null);
    setPunchCount(0);
  }, [punchCount]);

  const handleHit = async () => {
    playHit();
    if (!loaded || isHitting) return;
    setIsHitting(true);
    hit()
      .then((hash) => {
        const url = txUrl(hash);
        if (url) {
          notifyInfo(
            React.createElement(ExplorerToast, {
              url: url,
              text: "Sent punch tx: ",
              hash: hash,
            }),
          );
        } else {
          notifyInfo(`Sent punch tx: ${hash}`);
        }
        if (clownStamina && clownStamina > 0) {
          setPunchCount((prev) => {
            const newCount = Math.min(prev + 1, 3);
            return newCount;
          });
        }
        return waitForTransaction(hash);
      })
      .then((receipt) => {
        if (receipt.status === "success") {
          notifySuccess("Punch successful");
          // Re-read stamina from contract after successful hit
          fetchGameRounds();
        } else {
          notifyError("Punch failed");
        }
      })
      .catch((error) => {
        const message = error instanceof Error ? error.message : String(error);
        notifyError(`Error punching clown: ${message}`);
      })
      .finally(() => {
        setIsHitting(false);
      });
  };

  const handleReset = async () => {
    playReset();
    if (!loaded || isResetting) return;
    if (clownStamina !== 0) {
      notifyError("Clown must be KO to reset");
      return;
    }
    setIsResetting(true);
    reset()
      .then((hash) => {
        const url = txUrl(hash);
        if (url) {
          notifyInfo(
            React.createElement(ExplorerToast, {
              url: url,
              text: "Sent reset tx: ",
              hash: hash,
            }),
          );
        } else {
          notifyInfo(`Sent reset tx: ${hash}`);
        }
        setPunchCount(0);
        return waitForTransaction(hash);
      })
      .then((receipt) => {
        if (receipt.status === "success") {
          notifySuccess("Reset successful");
          setRobResult(null);
          // Re-read stamina from contract after successful reset
          fetchGameRounds();
        } else {
          notifyError("Reset failed");
        }
      })
      .catch((error) => {
        const message = error instanceof Error ? error.message : String(error);
        notifyError(`Error resetting clown: ${message}`);
      })
      .finally(() => {
        setIsResetting(false);
      });
  };

  const handleRob = async () => {
    playRob();
    if (!loaded || isRobbing) return;
    setIsRobbing(true);
    rob()
      .then((result) => {
        setRobResult(result);
      })
      .catch((error) => {
        const message = error instanceof Error ? error.message : String(error);
        notifyError(`Error robbing clown: ${message}`);
      })
      .finally(() => {
        setIsRobbing(false);
      });
  };

  return {
    loaded,
    clownStamina,
    currentRoundId,
    isHitting,
    isResetting,
    isRobbing,
    robResult,
    punchCount,
    fetchGameRounds,
    resetGameState,
    handleHit,
    handleReset,
    handleRob,
  };
};
```

This hook manages the full game lifecycle:

* **`fetchGameRounds`** — reads the current stamina from the contract via `tread.getClownStamina()`
* **`handleHit`** — sends a shielded write via `twrite.hit()`, shows toast notifications with explorer links, waits for the receipt, increments punch count, and refetches stamina
* **`handleReset`** — validates the clown is KO, sends a shielded write via `twrite.reset()`, clears punch count and rob result, and refetches stamina
* **`handleRob`** — performs a signed read via `read.rob()` to decrypt and reveal a secret from the clown's pool
* **`resetGameState`** — clears the rob result and punch count when the round changes


# Ch 3: Game UI Components

In this chapter, you'll build the game interface — the clown sprite with punch animations, action buttons, and the entry screen. *Estimated time: \~15 minutes*

### ShowClown: Animated clown sprite

The clown sprite changes appearance based on how many times it's been hit. Create `src/components/game/ShowClown.tsx`:

```typescript
import { motion, useAnimation } from 'framer-motion'
import { useEffect, useMemo } from 'react'

import { Box } from '@mui/material'

type ClownProps = {
  isKO: boolean
  isShakingAnimation: boolean
  isHittingAnimation: boolean
  punchCount: number
}

const ShowClown: React.FC<ClownProps> = ({
  isKO,
  isShakingAnimation,
  isHittingAnimation,
  punchCount,
}) => {
  const controls = useAnimation()

  useEffect(() => {
    if (isShakingAnimation) {
      controls.start({
        rotate: [0, -5, 5, -5, 5, 0],
        transition: { duration: 0.5 },
      })
    } else if (isHittingAnimation) {
      controls.start({
        scale: [1, 0.9, 1.1, 1],
        transition: { duration: 0.3 },
      })
    }
  }, [isShakingAnimation, isHittingAnimation, controls])

  // Select the appropriate clown image based on punch count and KO state
  // Using useMemo to prevent recalculating on every render
  const clownImage = useMemo(() => {
    // If shaking, show the shaking clown image
    if (isShakingAnimation) {
      return '/clown_shaking.png'
    }

    if (isKO) {
      return '/clownko.png'
    }

    let imagePath
    switch (punchCount) {
      case 0:
        imagePath = '/clown1.png'
        break
      case 1:
        imagePath = '/clown2.png'
        break
      case 2:
      case 3:
        imagePath = '/clown3.png'
        break
      default:
        imagePath = '/clown1.png'
    }

    return imagePath
  }, [isKO, isShakingAnimation, punchCount])

  return (
    <Box
      sx={{
        width: { xs: '70%', sm: '70%', md: '80%', lg: '80%', xl: '100%' },
        height: { xs: '100%', sm: '70%', md: '80%', lg: '80%', xl: '100%' },
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <div className="relative">
        <motion.div animate={controls} className="relative">
          <img
            src={clownImage}
            alt="Clown"
            style={{
              maxWidth: '100%',
              height: 'auto',
              objectFit: 'contain',
            }}
          />
        </motion.div>
      </div>
    </Box>
  )
}

export default ShowClown
```

The sprite progression creates visual feedback as the clown takes damage:

* **0 hits** — `clown1.png` (full health)
* **1 hit** — `clown2.png` (damaged)
* **2–3 hits** — `clown3.png` (heavily damaged)
* **KO** — `clownko.png` (knocked out)
* **Shaking** — `clown_shaking.png` (mid-animation)

Framer Motion's `useAnimation` hook controls two animations: a **shake** (rotation) when the clown gets hit, and a **scale punch** effect for impact feedback.

### ButtonContainer: Action buttons

The button container renders the game's action buttons — hit, rob, and reset. Create `src/components/game/ButtonContainer.tsx`:

```typescript
import { useState } from 'react'

import { Box, type SxProps, type Theme } from '@mui/material'

type ButtonContainerProps = {
  clownStamina: number | null
  isHitting: boolean
  isResetting: boolean
  isRobbing: boolean
  handleHit: () => void
  handleReset: () => void
  handleRob: () => void
  position?: 'left' | 'right' | 'mobile'
}

type ActionButtonProps = {
  onClick: () => void
  active: boolean
  src: string
  alt: string
  className: string
  sx?: SxProps<Theme>
}

const ActionButton = ({
  onClick,
  active,
  src,
  alt,
  className,
  sx,
}: ActionButtonProps) => (
  <Box
    onClick={onClick}
    component="div"
    sx={{
      cursor: active ? 'default' : 'pointer',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      ...sx,
    }}
  >
    <img
      src={src}
      alt={alt}
      className={className}
      style={{ width: '100%', height: '100%', objectFit: 'contain' }}
    />
  </Box>
)

export default function ButtonContainer({
  clownStamina,
  isHitting,
  isResetting,
  isRobbing,
  handleHit,
  handleReset,
  handleRob,
  position = 'mobile',
}: ButtonContainerProps) {
  const [showRobActive, setShowRobActive] = useState(false)
  const [showResetActive, setShowResetActive] = useState(false)

  const handleRobClick = () => {
    if (!isRobbing) {
      setShowRobActive(true)
      setTimeout(() => {
        setShowRobActive(false)
        handleRob()
      }, 200)
    }
  }

  const handleResetClick = () => {
    if (!isResetting) {
      setShowResetActive(true)
      setTimeout(() => {
        setShowResetActive(false)
        handleReset()
      }, 200)
    }
  }

  const isStanding = clownStamina !== null && clownStamina > 0

  const robBtn = {
    onClick: handleRobClick,
    active: isRobbing,
    src: showRobActive ? '/rob_active.png' : '/rob_btn.png',
    alt: 'Rob',
    className: 'look-btn',
  }

  const hitBtn = {
    onClick: handleHit,
    active: isHitting,
    src: isHitting ? '/punch_active.png' : '/punch_btn.png',
    alt: 'Punch',
    className: 'punch-btn',
  }

  const resetBtn = {
    onClick: handleResetClick,
    active: isResetting,
    src: showResetActive ? '/reset_active.png' : '/reset_btn.png',
    alt: 'Reset',
    className: 'reset-btn',
  }

  const rightBtn = isStanding ? hitBtn : resetBtn

  if (position === 'left') {
    return (
      <Box
        sx={{
          width: { lg: '100%' },
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'flex-end',
          pr: { lg: 4, xl: 6 },
        }}
      >
        <ActionButton
          {...robBtn}
          sx={{ width: '20rem', marginRight: 6, height: '20rem' }}
        />
      </Box>
    )
  }

  if (position === 'right') {
    return (
      <Box
        sx={{
          width: { lg: '100%' },
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'flex-start',
          pl: { lg: 4, xl: 6 },
        }}
      >
        <ActionButton
          {...rightBtn}
          sx={
            isStanding
              ? {
                  marginLeft: { xs: 0, lg: 8 },
                  height: { lg: '18rem' },
                }
              : { width: '20rem', marginLeft: '3rem', height: '18rem' }
          }
        />
      </Box>
    )
  }

  // Mobile layout — both buttons side by side
  const MOBILE_SIZE = {
    xs: '12rem',
    sm: '20rem',
    md: '20rem',
    lg: '30rem',
    xl: '30rem',
  }

  return (
    <Box
      sx={{
        width: '100%',
        height: '100%',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        gap: { xs: 0, sm: 0, md: 0, lg: 70, xl: 70 },
        marginRight: { xs: 0, sm: 4, md: 4, lg: 6, xl: 0 },
        marginLeft: { xs: 0, sm: 4, md: 4, lg: 6, xl: 0 },
      }}
    >
      <ActionButton
        {...robBtn}
        sx={{ height: MOBILE_SIZE, width: MOBILE_SIZE }}
      />
      <ActionButton
        {...rightBtn}
        sx={
          isStanding
            ? {
                marginRight: { xs: 0, sm: 4, md: 0, lg: 0, xl: 0 },
                height: {
                  xs: '10rem',
                  sm: '18rem',
                  md: '20rem',
                  lg: '30rem',
                  xl: '30rem',
                },
                width: {
                  xs: '12rem',
                  sm: '14rem',
                  md: '28rem',
                  lg: '30rem',
                  xl: '30rem',
                },
              }
            : { height: MOBILE_SIZE, width: MOBILE_SIZE }
        }
      />
    </Box>
  )
}
```

The button layout adapts based on game state and screen size:

* **Desktop** — rob button on the left, hit/reset on the right
* **Mobile** — both buttons side by side below the clown
* **Clown standing** — show Hit button (right)
* **Clown KO** — show Reset button (right), Rob button now callable (left)

### ClownPuncher: Main game component

This component ties everything together. Create `src/components/game/ClownPuncher.tsx`:

```typescript
'use client'

import { useEffect, useRef, useState } from 'react'

import { useGameActions } from '@/hooks/useGameActions'
import {
  Backdrop,
  Box,
  CircularProgress,
  Container,
  Fade,
  Typography,
} from '@mui/material'

import { useAuth } from '../chain/WalletConnectButton'
import ButtonContainer from './ButtonContainer'
import EntryScreen from './EntryScreen'
import ShowClown from './ShowClown'

const ClownPuncher: React.FC = () => {
  const { isAuthenticated } = useAuth()
  const [showGame, setShowGame] = useState(false)
  const [showSecretSplash, setShowSecretSplash] = useState(false)
  const [showRobRefused, setShowRobRefused] = useState(false)
  const prevRoundIdRef = useRef<number | null>(null)
  const {
    loaded,
    currentRoundId,
    clownStamina,
    isHitting,
    isResetting,
    isRobbing,
    robResult,
    punchCount,
    fetchGameRounds,
    resetGameState,
    handleHit,
    handleReset,
    handleRob,
  } = useGameActions()

  useEffect(() => {
    // Only fetch data if authenticated and game is shown
    if (isAuthenticated && showGame) {
      fetchGameRounds()
    }
  }, [fetchGameRounds, isAuthenticated, showGame])

  useEffect(() => {
    // Only reset game state when first showing the game or when the round actually changes
    if (
      showGame &&
      (prevRoundIdRef.current === null ||
        (currentRoundId !== null && prevRoundIdRef.current !== currentRoundId))
    ) {
      console.log(
        'Round changed from',
        prevRoundIdRef.current,
        'to',
        currentRoundId,
        '- resetting game state'
      )
      resetGameState()
    }
    // Update the ref to the current round ID
    prevRoundIdRef.current = currentRoundId
  }, [currentRoundId, resetGameState, showGame])

  // Show splash screen when lookResult changes to a non-null value
  useEffect(() => {
    if (robResult !== null) {
      setShowSecretSplash(true)
    }
  }, [robResult])

  // If not showing the game yet, show entry screen
  if (!showGame) {
    return <EntryScreen onEnter={() => setShowGame(true)} />
  }

  const onRob = () => {
    if (clownStamina !== null && clownStamina > 0) {
      setShowRobRefused(true)
      return
    }
    handleRob()
  }

  const buttonProps = {
    clownStamina,
    isHitting,
    isResetting,
    isRobbing,
    handleHit,
    handleReset,
    handleRob: onRob,
  } as const

  return (
    <Container
      sx={{
        height: '100dvh',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'start',
        px: 4,
      }}
    >
      <Box
        sx={{
          mt: { xs: 3, sm: 3, md: 5, lg: 4, xl: 10 },
          height: {
            xs: '30dvh',
          },
          mb: 2,
          display: 'flex',
          justifyContent: 'center',
        }}
      >
        <img
          src="/cblogo.png"
          alt="Clown Beatdown Logo"
          className="clown-logo"
        />
      </Box>

      {/* Splash Screen — secret revealed or rob refused */}
      <Backdrop
        sx={{
          color: '#fff',
          zIndex: (theme) => theme.zIndex.drawer + 1,
          backgroundColor: 'rgba(0, 0, 0, 0.85)',
        }}
        open={(showSecretSplash && robResult !== null) || showRobRefused}
        onClick={() => {
          setShowSecretSplash(false)
          setShowRobRefused(false)
        }}
      >
        <Fade in={(showSecretSplash && robResult !== null) || showRobRefused}>
          <Box
            sx={{
              backgroundColor: 'background.paper',
              borderRadius: 4,
              p: 5,
              textAlign: 'center',
              maxWidth: '90%',
              boxShadow: 24,
            }}
          >
            {showRobRefused ? (
              <>
                <Typography
                  variant="h4"
                  fontWeight="bold"
                  color="white"
                  gutterBottom
                >
                  NOT SO FAST!
                </Typography>
                <Typography variant="h6" color="white" gutterBottom>
                  The clown isn't giving up that easily.
                </Typography>
                <Typography
                  variant="body1"
                  color="text.secondary"
                  sx={{ mt: 2 }}
                >
                  Knock him out first!
                </Typography>
              </>
            ) : (
              <>
                <Typography
                  variant="h4"
                  fontWeight="bold"
                  color="white"
                  gutterBottom
                >
                  SECRET REVEALED!
                </Typography>
                <Typography
                  variant="h1"
                  fontWeight="bold"
                  color="white"
                  gutterBottom
                >
                  {robResult}
                </Typography>
              </>
            )}
            <Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}>
              (Click anywhere to close)
            </Typography>
          </Box>
        </Fade>
      </Backdrop>

      {loaded ? (
        <Box
          sx={{
            display: 'flex',
            flexDirection: { xs: 'column', lg: 'row' },
            justifyContent: { lg: 'space-between' },
            alignItems: 'center',
            width: '100%',
            position: 'relative',
            height: { lg: '500px', xl: '600px' },
            my: { xs: 0, md: 5, lg: 0, xl: 1 },
          }}
        >
          {/* Desktop: left buttons */}
          <Box sx={{ display: { xs: 'none', lg: 'flex' } }}>
            <ButtonContainer {...buttonProps} position="left" />
          </Box>

          {/* Clown — rendered once, responsive positioning */}
          <Box
            className="clown-container"
            sx={{
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              position: { lg: 'absolute' },
              left: { lg: '50%' },
              transform: { lg: 'translateX(-50%)' },
              zIndex: 2,
              width: { lg: '50%', xl: '40%' },
              maxHeight: { xs: '35dvh', md: '30dvh', lg: 'none' },
            }}
          >
            <ShowClown
              isKO={clownStamina === 0}
              isShakingAnimation={false}
              isHittingAnimation={isHitting}
              punchCount={punchCount}
            />
          </Box>

          {/* Desktop: right buttons */}
          <Box sx={{ display: { xs: 'none', lg: 'flex' } }}>
            <ButtonContainer {...buttonProps} position="right" />
          </Box>

          {/* Mobile: all buttons below clown */}
          <Box
            sx={{
              display: { xs: 'flex', lg: 'none' },
              width: '100%',
              justifyContent: 'center',
              alignItems: 'center',
              marginBottom: { xs: 3, md: 5 },
            }}
          >
            <ButtonContainer {...buttonProps} position="mobile" />
          </Box>
        </Box>
      ) : (
        <CircularProgress size={32} />
      )}
    </Container>
  )
}

export default ClownPuncher
```

### EntryScreen: Wallet connection

The entry screen prompts the user to connect their wallet before playing. Create `src/components/game/EntryScreen.tsx`:

```typescript
import React, { useEffect, useState } from 'react'

import { Box, Container } from '@mui/material'

import { useAuth } from '../chain/WalletConnectButton'

type EntryScreenProps = {
  onEnter: () => void
}

const EntryScreen: React.FC<EntryScreenProps> = ({ onEnter }) => {
  const { isAuthenticated, isLoading, openConnectModal } = useAuth()
  const [isAnimating, setIsAnimating] = useState(false)

  // Automatically enter when user becomes authenticated
  useEffect(() => {
    if (isAuthenticated) {
      setIsAnimating(true)
      setTimeout(() => {
        setIsAnimating(false)
        onEnter()
      }, 500)
    }
  }, [isAuthenticated, onEnter])

  const handleLogoClick = () => {
    setIsAnimating(true)

    // If not authenticated, open wallet connect modal
    if (!isAuthenticated) {
      setTimeout(() => {
        setIsAnimating(false)
        openConnectModal()
      }, 300)
    }
  }

  return (
    <Container
      sx={{
        height: '100dvh',
        width: '100dvw',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        position: 'relative',
      }}
    >
      <Box
        sx={{
          display: 'flex',
          justifyContent: 'center',
          flexDirection: 'column',
          cursor: 'pointer',
          transform: isAnimating ? 'scale(0.95)' : 'scale(1)',
          transition: 'transform 0.2s ease-in-out',
        }}
        onClick={handleLogoClick}
      >
        <img
          src="/cblogo.png"
          alt="Clown Beatdown Logo"
          style={{ maxWidth: '100%', height: 'auto' }}
          className="clown-image"
        />
        <Box
          sx={{
            mt: 6,
            color: 'black',
            fontSize: '1.25rem',
            textAlign: 'center',
            opacity: 0.8,
            fontFamily: 'monospace',
            border: '1px solid black',
            borderRadius: '10px',
            padding: '10px',
            backgroundColor: 'var(--midColor)',
          }}
        >
          {isLoading ? '...Loading...' : 'CLICK TO CONNECT'}
        </Box>
      </Box>
    </Container>
  )
}

export default EntryScreen
```

Clicking the logo opens the RainbowKit wallet connection modal. Once authenticated, the `onEnter` callback fires and the `ClownPuncher` component takes over.

### WalletConnectButton: Auth context

Create the auth context and wallet button used throughout the app. Create `src/components/chain/WalletConnectButton.tsx`:

```typescript
import React, { createContext, useContext, useEffect, useState } from 'react'
import { useAccount } from 'wagmi'

import { ConnectButton } from '@rainbow-me/rainbowkit'
import { useConnectModal } from '@rainbow-me/rainbowkit'

// Create authentication context
type AuthContextType = {
  isAuthenticated: boolean
  isLoading: boolean
  openConnectModal: () => void
  accountName?: string
}

const AuthContext = createContext<AuthContextType>({
  isAuthenticated: false,
  isLoading: true,
  openConnectModal: () => {},
})

export const useAuth = () => useContext(AuthContext)

// Wallet icon component using SVG for better quality
const WalletIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    fill="currentColor"
    className="w-5 h-5"
  >
    <path d="M2.273 5.625A4.483 4.483 0 0 1 5.25 4.5h13.5c1.141 0 2.183.425 2.977 1.125A3 3 0 0 0 18.75 3H5.25a3 3 0 0 0-2.977 2.625ZM2.273 8.625A4.483 4.483 0 0 1 5.25 7.5h13.5c1.141 0 2.183.425 2.977 1.125A3 3 0 0 0 18.75 6H5.25a3 3 0 0 0-2.977 2.625ZM5.25 9a3 3 0 0 0-3 3v6a3 3 0 0 0 3 3h13.5a3 3 0 0 0 3-3v-6a3 3 0 0 0-3-3H15a.75.75 0 0 0-.75.75 2.25 2.25 0 0 1-4.5 0A.75.75 0 0 0 9 9H5.25Z" />
  </svg>
)

const WalletButton: React.FC<
  React.PropsWithChildren<
    { onClick: () => void } & React.HTMLAttributes<HTMLButtonElement>
  >
> = ({ children, onClick, ...props }) => {
  return (
    <button onClick={onClick} className="" {...props}>
      {children}
    </button>
  )
}

export const AuthProvider: React.FC<React.PropsWithChildren> = ({
  children,
}) => {
  const { openConnectModal } = useConnectModal() || {
    openConnectModal: () => {},
  }
  const { address, isConnecting, isConnected, isDisconnected } = useAccount()
  const [authState, setAuthState] = useState<AuthContextType>({
    isAuthenticated: false,
    isLoading: true,
    openConnectModal: openConnectModal || (() => {}),
  })

  useEffect(() => {
    setAuthState({
      isAuthenticated: isConnected,
      isLoading: isConnecting,
      openConnectModal: openConnectModal || (() => {}),
      accountName: address
        ? `${address.slice(0, 6)}...${address.slice(-4)}`
        : undefined,
    })
  }, [isConnected, isConnecting, isDisconnected, address, openConnectModal])

  return (
    <AuthContext.Provider value={authState}>{children}</AuthContext.Provider>
  )
}

const WalletConnectButton = () => {
  return (
    <ConnectButton.Custom>
      {({
        account,
        openConnectModal,
        chain,
        openAccountModal,
        openChainModal,
        mounted,
        authenticationStatus,
      }) => {
        if (!mounted || authenticationStatus === 'loading') {
          return <></>
        }
        if (!account || authenticationStatus === 'unauthenticated') {
          return (
            <WalletButton onClick={openConnectModal}>
              <span className="md:inline hidden">CONNECT WALLET</span>
              <span className="md:hidden">
                <WalletIcon />
              </span>
            </WalletButton>
          )
        }
        if (chain?.unsupported) {
          return (
            <WalletButton onClick={openChainModal}>
              <span className="md:inline hidden">Unsupported chain</span>
              <span className="md:hidden">
                <WalletIcon />
              </span>
            </WalletButton>
          )
        }
        return (
          <WalletButton onClick={openAccountModal}>
            <span className="">
              <WalletIcon />
            </span>
          </WalletButton>
        )
      }}
    </ConnectButton.Custom>
  )
}

export default WalletConnectButton
```

### Running the frontend

Start the frontend dev server:

```bash
cd packages/web
bun dev
```

Make sure `sanvil` is running and the contract is deployed (see [Deploying](/tutorials/understanding-the-clown-beatdown-contract/writing-the-contract/deploying)). Open `http://localhost:5173` in your browser, connect your wallet, and start punching the clown!

### Game flow recap

1. **Connect wallet** — RainbowKit modal, ShieldedWalletProvider derives shielded keys
2. **Hit the clown** — `twrite.hit()` sends a shielded transaction, stamina decrements
3. **Clown KO** — stamina reaches 0, sprite changes to `clownko.png`
4. **Rob a secret** — `read.rob()` performs a signed read, secret is decrypted and displayed
5. **Reset** — `twrite.reset()` restores stamina and picks a new random secret for the next round

Congratulations! You've built a complete Seismic dApp — from smart contract to CLI to web frontend.


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

## Shielded Integers

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

### suint - Shielded Unsigned Integer

```
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)
```

### sint - Shielded Signed Integer

```
sint256 a = sint256(-10);
sint256 b = sint256(3);

// == EXAMPLES
a < b   // sbool(true)
a + b   // sint256(-7)
a * b   // sint256(-30)
```

## sbool - Shielded Boolean

All comparisons and operators for `sbool` function identically to `bool`.

We recommend reading the point on [conditional execution](/seismic-solidity/footguns#conditional-execution) prior to using `sbool` since it's easy to accidentally leak information with this type.

```
sbool a = sbool(true);
sbool b = sbool(false);

// == EXAMPLES
a && b  // sbool(false)
!b      // sbool(true)
```

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

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

## sbytes - Shielded Bytes

### Fixed-size: sbytes1 through sbytes32

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

```
sbytes32 a = sbytes32(0xabcd);
sbytes1 b = sbytes1(0xff);
```

### Dynamic: sbytes

Dynamic shielded bytes mirror the standard `bytes` type. The length is stored as shielded — like [dynamic shielded arrays](/seismic-solidity/collections#shielded-arrays), observers cannot read it directly but may infer an upper bound from gas costs.

## Shielded Literals

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

```
suint256 a = suint256(42);   // explicit cast
suint256 b = 42s;            // s suffix — same result
```

The `s` suffix infers the shielded type from context and works with hex, underscores, scientific notation, and unary minus. See [Shielded Literals](/seismic-solidity/shielded-literals) for the full specification.

{% hint style="warning" %}
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](/seismic-solidity/footguns#literals) for more detail.
{% endhint %}


# Collections

Using stype variables in arrays and maps

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

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

```solidity
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
}
```

{% hint style="warning" %}
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.
{% endhint %}

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

```solidity
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
```


# Casting

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

```solidity
uint256 publicNumber = 100;

// Implicit casting -- will NOT compile
suint256 shielded = publicNumber; // Error

// Explicit casting -- correct
suint256 shielded = suint256(publicNumber); // OK
```

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

```solidity
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
```

{% hint style="info" %}
For integer literals specifically, you can use the [`s` suffix](/seismic-solidity/shielded-literals) 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.
{% endhint %}

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

```solidity
uint256 publicValue = 100;
suint256 shieldedValue = suint256(publicValue);
```

{% hint style="warning" %}
**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.
{% endhint %}

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.

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

```solidity
suint256 shieldedValue = /* ... */;
uint256 publicValue = uint256(shieldedValue);
```

{% hint style="warning" %}
**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.
{% endhint %}

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.

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

```solidity
address payable exposed = payable(address(someSaddressValue));
exposed.transfer(1 ether);
```

{% hint style="warning" %}
**Privacy consideration:** Unshielding to `address payable` exposes the address in the transaction trace.
{% endhint %}

You can also convert in the other direction:

```solidity
address payable pay = /* ... */;
saddress shielded = saddress(address(pay));
```

## Size Casting Between Shielded Integers

You can cast between different sizes of shielded integers, just as you can with regular Solidity integers:

```solidity
suint128 smaller = suint128(42);
suint256 larger = suint256(smaller);  // Widening: safe, no data loss

suint256 big = suint256(1000);
suint128 small = suint128(big);       // Narrowing: may truncate
```

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:

```solidity
suint256 unsigned = suint256(100);
sint256 signed = sint256(unsigned);   // Reinterprets the bits
```

## Common Patterns

### Returning values from view functions

Since shielded types cannot be returned from `public` or `external` functions, you must unshield them first:

```solidity
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);
}
```

{% hint style="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 [signed reads](/reference/seismic-transaction/signed-reads) to ensure only authorized users can query it.
{% endhint %}

### Interfacing with non-shielded contracts

When calling a contract that expects unshielded types, cast at the call boundary:

```solidity
suint256 private amount;

function sendToExternal(address externalContract) external {
    IExternalContract(externalContract).deposit(uint256(amount));
}
```

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

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

## 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.
4. **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.

```solidity
// 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.
}
```


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

```solidity
suint256 x = 42s;            // inferred as suint256
suint8 small = 7s;           // inferred as suint8
sint256 neg = -1s;           // inferred as sint256
```

This is equivalent to:

```solidity
suint256 x = suint256(42);
suint8 small = suint8(7);
sint256 neg = sint256(-1);
```

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

## Supported formats

The `s` suffix works with all numeric literal forms:

```solidity
suint256 a = 1_000s;         // underscores
suint256 b = 0xDEADs;        // hex
suint256 c = 1e5s;           // scientific notation
sint256  d = -42s;           // unary minus
```

## Type inference

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

```solidity
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
```

## Rules

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

```solidity
suint256 x = 1s + 1;         // Error — mixed shielded/non-shielded
suint256 x = 1s + 1s;        // OK
```

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

```solidity
uint256 x = 42s;             // Error — cannot assign shielded to uint256
```

**No `immutable` or `constant`.** Shielded literals follow the same restriction as all shielded types — they cannot be used in `immutable` or `constant` declarations. See [Footguns](/seismic-solidity/footguns#immutable-and-constant-shielded-variables).

## 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 [Footguns](/seismic-solidity/footguns#literals) for more detail.


# Events

## The Limitation

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

```solidity
event ConfidentialEvent(suint256 confidentialData); // Compilation error
```

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

## 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 [ECDH precompile](/reference/precompiles/ecdh) at address `0x65`.
2. **Derive an encryption key** from the shared secret using the [HKDF precompile](/reference/precompiles/hkdf) at address `0x68`.
3. **Encrypt the event data** using the [AES-GCM Encrypt precompile](/reference/precompiles/aes-gcm-encrypt) at address `0x66`.
4. **Emit a regular event** containing the encrypted bytes. Since the event parameter is `bytes` (not a shielded type), this compiles and works normally.
5. **Recipient decrypts** the data using the [AES-GCM Decrypt precompile](/reference/precompiles/aes-gcm-decrypt) at address `0x67`, either off-chain or on-chain if needed.

### Precompile Reference

| Precompile      | Address                                          | Purpose                           |
| --------------- | ------------------------------------------------ | --------------------------------- |
| ECDH            | [`0x65`](/reference/precompiles/ecdh)            | Shared secret generation          |
| AES-GCM Encrypt | [`0x66`](/reference/precompiles/aes-gcm-encrypt) | Encrypt data with AES-GCM         |
| AES-GCM Decrypt | [`0x67`](/reference/precompiles/aes-gcm-decrypt) | Decrypt data with AES-GCM         |
| HKDF            | [`0x68`](/reference/precompiles/hkdf)            | Key derivation from shared secret |

## 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 [SRC20: Private Token](/tutorials/src20/encrypted-events) tutorial.

```solidity
// 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);
        }
    }
}
```

The built-in helpers `ecdh()`, `hkdf()`, and `aes_gcm_encrypt()` are compiler-provided globals — no imports needed. See the [Precompiles reference](/reference/precompiles) for details on each.

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

* [**Python**](/clients/python/src20/event-watching) — `watch_src20_events_with_key` and `SRC20EventWatcher`
* [**TypeScript (viem)**](/clients/typescript/viem/shielded-public-client) — `watchSRC20EventsWithKey()` action

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

```solidity
// 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!
}
```

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.

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

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


# Storage

## How Shielded Storage Works

Seismic extends the EVM storage model with **FlaggedStorage**. Every storage slot is represented as a pair:

```
(value: U256, is_private: bool)
```

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:

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

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

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.

## 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](#gas-considerations) below).

### Storage Layout Comparison

In standard Solidity, small types are packed together:

```solidity
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]
    */
}
```

With shielded types, each field gets its own slot:

```solidity
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]
    */
}
```

This means shielded contracts consume more storage slots than their unshielded equivalents. Plan your contract's storage layout accordingly.

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

{% hint style="warning" %}
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](/seismic-solidity/best-practices#be-mindful-of-gas-based-information-leakage) for details.
{% endhint %}

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

```solidity
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);
    }
}
```

{% hint style="danger" %}
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.
{% endhint %}

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


# Best Practices

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

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

```solidity
// This function should ONLY be called via a Seismic transaction
function deposit(suint256 amount) external {
    balances[msg.sender] += amount;
}
```

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

### Use Casting Carefully

Every cast between shielded and unshielded types is a potential information leak. See [Casting](/seismic-solidity/casting) 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.

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

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

```bash
sforge test
```

Write tests that specifically verify:

* Shielded values remain confidential through the expected code paths.
* Access control prevents unauthorized reads of unshielded data.

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

```solidity
// 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
```


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

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

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

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

```solidity
// BETTER: Both arms are identical operations (single assignment), same gas either way
discount = isVIP ? 50s : 0s;
```

### Literals

**The problem:** Assigning literal values to shielded types — whether via explicit cast or the [`s` suffix](/seismic-solidity/shielded-literals) — embeds those values directly in the contract bytecode, which is publicly visible.

```solidity
// Both forms embed `42` in bytecode
suint256 a = suint256(42);
suint256 b = 42s;
```

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

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

```solidity
// 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
}
```

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

```solidity
// 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.
}
```

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

```solidity
// BAD: Anyone can call this and read the shielded value
suint256 private _secretBalance;

function secretBalance() external view returns (uint256) {
    return uint256(_secretBalance);
}
```

**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](/reference/rpc-methods/eth-call#signed-calls-signed-reads) — otherwise `msg.sender` will be the zero address.

```solidity
suint256 private _secretBalance;

function secretBalance() external view returns (uint256) {
    require(msg.sender == owner, "Not authorized");
    return uint256(_secretBalance);
}
```

{% hint style="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.
{% endhint %}

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

```solidity
// Will NOT compile
suint256 public secretBalance;

// Will NOT compile
function getSecret() external view returns (suint256) {
    return secretBalance;
}
```

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

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

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

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

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

```solidity
// BAD: Gas cost reveals the value of `shieldedExp`
suint256 base = 2s;
suint256 shieldedExp = /* ... */;
suint256 result = base ** shieldedExp;  // Gas cost leaks shieldedExp
```

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

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

```solidity
enum Status { Active, Inactive, Suspended }
// Only 3 possible values (0, 1, 2) — shielding provides little protection
suint256 shieldedStatus = suint256(uint256(Status.Active));
```

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

### Low-Entropy Shielded Storage Values

**The problem:** Shielding hides a slot's *value* from RPC reads, but the value still participates in the storage Merkle trie that backs the account's `storageHash`. The trie key (the storage slot) is public, and the value is committed into the trie through a plain keccak hash with no per-slot salt. If a shielded value has low entropy — a boolean, a small enum, a value drawn from a known set — an attacker who knows the slot can guess candidate values, recompute the commitment, and confirm a match. The value is recovered without ever being read directly.

```solidity
// The storage layout is public, so the slot is known, and an `sbool`
// has only two possible values — trivially guessable from the trie.
sbool private isWhitelisted;
```

**Why it leaks:** A storage value enters the trie as roughly `keccak256(RLP([slot_path, value]))`. With no salt, that commitment is a deterministic function of `(slot, value)`, and the slot is known — so the only unknown is the value. The account's `storageHash` always exposes this commitment, but on its own it commits to the whole trie at once, so it is only practical to attack on freshly deployed or sparsely shielded contracts where the root covers just a handful of leaves.

Endpoints that return Merkle proofs (such as `eth_getProof`) make this much worse, and **non-inclusion proofs are the sharpest case**. A proof that a slot does *not* exist still returns the intermediate trie nodes along that slot's path. Because the trie key is `keccak256(slot)`, an attacker can search for a junk, non-existent slot whose hash sits right next to your target's, request a non-inclusion proof for it, and pull back the branch node that *directly* commits to your private leaf. This isolates a single target leaf and makes it brute-forceable regardless of how full the trie is and without needing a conveniently located public slot nearby — defeating the intuition that a contract holding lots of shielded state is automatically safer. This is the same root cause as the [Enums](#enums) footgun, generalized to any low-entropy value.

**What to do instead:** Do not rely on shielding alone to protect a low-entropy secret stored at a known slot. If a value is sensitive and has a small domain, mix in high entropy before storing it — for example store `keccak256(abi.encode(secret, salt))`, where `salt` is itself a high-entropy shielded value, and compare against that commitment rather than the raw secret. This only works for values you check by equality, not ones you do arithmetic on. High-entropy shielded values — balances that accumulate over many transactions, for instance — are not practically guessable and need no special handling.

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

```solidity
// Will NOT compile — compiler error
suint256 immutable SECRET = 42s;

// Will NOT compile — compiler error
suint256 constant MY_VALUE = 1s;
```

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

### RNG Proposer Bias

**The problem:** The synchronous [RNG precompile](/reference/precompiles/rng) 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.

```solidity
// 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];
}
```

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

### RNG Revert If Not Ideal Outcome

**The problem:** A caller of a contract that uses the [RNG precompile](/reference/precompiles/rng) can revert their transaction if they don't like the outcome, giving them a risk-free way of interacting with your contract.

```solidity
// An attacker wraps the call and reverts on unfavorable outcomes,
// making every attempt risk-free.
function playLotteryRiskFree() external {
    bool winner = lotteryContract.playLottery();
    require(winner, "Better luck next time");
}
```

**Why this matters:** You should not use RNG precompile when the caller will be able to analayze the outcome and revert to undo

**What to do instead:** One example would be a poker contract where a dealer makes a call that uses RNG to shuffle the deck and set the state of it. After deck is shuffled players then can put in subsequent calls to deal from the shuffled deck but now they cannot revert to undo the state of the deck.


# Overview

Seismic maintains client libraries for three languages.

## TypeScript

### seismic-viem

[seismic-viem](https://www.npmjs.com/package/seismic-viem) composes with [viem](https://viem.sh/) to add Seismic transaction support, encrypted calldata, and signed reads. See the [full documentation](/clients/typescript/viem).

### seismic-react

[seismic-react](https://www.npmjs.com/package/seismic-react) composes with [wagmi](https://wagmi.sh/) to provide React hooks for shielded reads, writes, and wallet management. See the [full documentation](/clients/typescript/react).

## Python — seismic-web3

[seismic-web3](https://pypi.org/project/seismic-web3/) composes with [web3.py](https://github.com/ethereum/web3.py) to interact with Seismic nodes from Python. See the [full documentation](/clients/python).

## Rust — seismic-alloy

[seismic-alloy](https://github.com/SeismicSystems/seismic-alloy) composes with [alloy](https://github.com/alloy-rs/alloy) to provide Seismic transaction types and encryption-aware providers. See the [full documentation](/clients/alloy).

{% hint style="warning" %}
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.
{% endhint %}


# TypeScript

TypeScript client libraries for Seismic

Seismic provides two TypeScript packages:

| Package                                    | Purpose                                         | Use when                             |
| ------------------------------------------ | ----------------------------------------------- | ------------------------------------ |
| [seismic-viem](/clients/typescript/viem)   | Low-level SDK built on viem 2.x                 | Server-side, scripts, non-React apps |
| [seismic-react](/clients/typescript/react) | 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.


# seismic-viem

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

Low-level TypeScript SDK for [Seismic](https://seismic.systems), built on [viem](https://viem.sh/) 2.x. Provides shielded public and wallet clients, encrypted contract interactions, signed reads, chain configs, and precompile access. This is the foundation that [seismic-react](/clients/typescript/react) builds upon.

```bash
npm install seismic-viem viem
```

## Quick Example

```typescript
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..."),
});

// Smart write — auto-detects shielded params, encrypts only when needed
const hash = await client.writeContract({
  address: "0x...",
  abi: myContractAbi,
  functionName: "transfer", // has suint256/saddress params → encrypted automatically
  args: ["0x...", 100n],
});

// Smart read — auto-detects shielded params, uses signed read only when needed
const balance = await client.readContract({
  address: "0x...",
  abi: myContractAbi,
  functionName: "balanceOf", // has saddress param → signed read automatically
  args: ["0x..."],
});
```

## Architecture

```
seismic-viem
├── Client Layer
│   ├── createShieldedPublicClient  — read-only, TEE key, precompiles
│   └── createShieldedWalletClient  — full capabilities, encryption pipeline
├── Contract Layer
│   ├── getShieldedContract          — .read / .write (smart) / .sread / .swrite (force shielded) / .tread / .twrite (force transparent) / .dwrite
│   ├── shieldedWriteContract        — standalone encrypted write
│   └── signedReadContract           — standalone signed read
├── Chain Configs
│   ├── seismicTestnet               — public testnet (chain ID 5124)
│   ├── sanvil                       — local dev (chain ID 31337)
│   └── createSeismicDevnet          — custom chain factory
├── Encryption
│   ├── getEncryption                — ECDH key exchange → AES key
│   └── AesGcmCrypto                 — encrypt/decrypt calldata
└── Precompiles
    ├── rng                          — random number generation
    ├── ecdh                         — key exchange
    ├── aesGcmEncrypt / Decrypt      — on-chain encryption
    ├── hkdf                         — key derivation
    └── secp256k1Sig                 — signature generation
```

## Documentation Navigation

### Getting Started

| Section                                                   | Description                                           |
| --------------------------------------------------------- | ----------------------------------------------------- |
| [**Installation**](/clients/typescript/viem/installation) | Install seismic-viem, configure viem peer dependency  |
| [**Chains**](/clients/typescript/viem/chains)             | Pre-configured chain definitions for Seismic networks |

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

### Contract Interaction

| Section               | Description                                                                                                                                   |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Contract Instance** | `getShieldedContract` for `.read`, `.write` (smart), `.sread`, `.swrite` (force shielded), `.tread`, `.twrite` (force transparent), `.dwrite` |
| **Shielded Writes**   | Encrypt calldata and send Seismic transactions (type 0x4A)                                                                                    |
| **Signed Reads**      | Prove caller identity in `eth_call` via `seismic_call`                                                                                        |

### Infrastructure

| Section                                       | Description                                                     |
| --------------------------------------------- | --------------------------------------------------------------- |
| [**Chains**](/clients/typescript/viem/chains) | `seismicTestnet`, `sanvil`, `createSeismicDevnet` chain configs |
| **Encryption**                                | ECDH key exchange, AES-GCM calldata encryption                  |
| **Precompiles**                               | RNG, ECDH, AES-GCM, HKDF, secp256k1 signing precompile bindings |

## 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)
* **Smart Routing** -- `.read` and `.write` auto-detect shielded parameters and route to the correct path
* **Contract Abstraction** -- `getShieldedContract` provides `.read`, `.write` (smart), `.sread`, `.swrite` (force shielded), `.tread`, `.twrite` (force transparent), 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

## Quick Links

### 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** -> [Chains](/clients/typescript/viem/chains)
* **Understand calldata encryption** -> Encryption
* **Install the package** -> [Installation](/clients/typescript/viem/installation)

### By Component

* **Client types** -> Shielded Public Client, Shielded Wallet Client
* **Contract interaction** -> Contract Instance, Shielded Writes, Signed Reads
* **Chains** -> [Chains](/clients/typescript/viem/chains) (`seismicTestnet`, `sanvil`, `createSeismicDevnet`)
* **Encryption** -> Encryption (`getEncryption`, `AesGcmCrypto`)
* **Precompiles** -> Precompiles (`rng`, `ecdh`, `aesGcmEncrypt`, `hkdf`, `secp256k1Sig`)

## 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`) | Automatic via `ShieldedWalletProvider`  |
| **State management** | Manual                                | React hooks (`useShieldedWallet`, etc.) |
| **Use when**         | Server-side, scripts, non-React apps  | React applications                      |

{% hint style="info" %}
If you are building a React application, consider using [seismic-react](/clients/typescript/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.
{% endhint %}

## Next Steps

1. [**Install seismic-viem**](/clients/typescript/viem/installation) -- Add the package to your project
2. [**Configure a chain**](/clients/typescript/viem/chains) -- 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


# Installation

Install seismic-viem and configure viem peer dependency

## Prerequisites

| Requirement | Version | Notes                                                                                                  |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------ |
| Node.js     | 18+     | LTS recommended; install via [nvm](https://github.com/nvm-sh/nvm) or [nodejs.org](https://nodejs.org/) |
| viem        | 2.x     | Peer dependency -- installed alongside seismic-viem                                                    |

## Install

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

{% tabs %}
{% tab title="npm" %}

```bash
npm install seismic-viem viem
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add seismic-viem viem
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add seismic-viem viem
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add seismic-viem viem
```

{% endtab %}
{% endtabs %}

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

{% hint style="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.
{% endhint %}

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

```typescript
// ESM (recommended)
import { createShieldedWalletClient, seismicTestnet } from "seismic-viem";

// CJS
const { createShieldedWalletClient, seismicTestnet } = require("seismic-viem");
```

## Minimal Working Example

Create a new project and verify the installation:

```bash
mkdir my-seismic-app && cd my-seismic-app
npm init -y
npm install seismic-viem viem
```

Create `index.ts`:

```typescript
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);
```

Run it:

```bash
npx tsx index.ts
```

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

## TypeScript

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  }
}
```

{% hint style="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.
{% endhint %}

## Troubleshooting

### Peer Dependency Warning

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

```bash
npm ls viem
# Should show viem@2.x.x
```

### Node.js Version

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

```bash
node --version
# Should be >= v18.0.0
```

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

## See Also

* [Chains](/clients/typescript/viem/chains) -- Configure network connections
* [Seismic Viem Overview](/clients/typescript/viem) -- Full SDK overview and architecture
* [seismic-react](/clients/typescript/react) -- React hooks layer built on seismic-viem


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

## Import

```typescript
import { createShieldedPublicClient } from "seismic-viem";
```

## Constructor

```typescript
const publicClient = createShieldedPublicClient({
  chain: seismicTestnet,
  transport: http(),
});
```

### Parameters

| Parameter   | Type        | Required | Description                                  |
| ----------- | ----------- | -------- | -------------------------------------------- |
| `chain`     | `Chain`     | Yes      | Chain configuration (e.g., `seismicTestnet`) |
| `transport` | `Transport` | Yes      | viem transport (e.g., `http()`)              |

### Return Type

`ShieldedPublicClient`

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

## Usage

```typescript
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",
});
```

## Actions

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

### 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>`    | AES-GCM decrypt via precompile                 |
| `hdfk(ikm)`                  | `Promise<Hex>`       | HKDF key derivation via precompile             |
| `secp256k1Signature(params)` | `Promise<Signature>` | secp256k1 signing via precompile               |

### SRC20 Actions

| Action                      | Return Type           | Description                                  |
| --------------------------- | --------------------- | -------------------------------------------- |
| `watchSRC20EventsWithKey()` | `Promise<() => void>` | Watch for SRC20 events with a decryption key |

See [SRC20 Event Watching](/clients/typescript/viem/src20) for the full flow, log types, and example usage.

### Deposit Contract Actions

| Action              | Return Type    | Description                   |
| ------------------- | -------------- | ----------------------------- |
| `getDepositRoot()`  | `Promise<Hex>` | Query the deposit merkle root |
| `getDepositCount()` | `Promise<Hex>` | Query the deposit count       |

See [Deposit Contract](/clients/typescript/viem/deposit-contract) for parameters and validator registration via `deposit()`.

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

## `getStorageAt` Disabled

{% hint style="warning" %}
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.
{% endhint %}

## Examples

### Fetching the TEE Public Key

```typescript
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);
```

### Using Precompiles

```typescript
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...");
```

### Explorer URL Helpers

Each helper accepts an optional `tab` argument to deep-link into a specific view. The helpers return `string | null` -- `null` when the chain has no configured block explorer.

```typescript
const publicClient = createShieldedPublicClient({
  chain: seismicTestnet,
  transport: http(),
});

const txUrl = publicClient.txExplorerUrl({ txHash: "0xabc123..." });
const txLogs = publicClient.txExplorerUrl({
  txHash: "0xabc123...",
  tab: "logs",
});
const addrUrl = publicClient.addressExplorerUrl({
  address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
});
const blockUrl = publicClient.blockExplorerUrl({ blockNumber: 12345 });
const tokenUrl = publicClient.tokenExplorerUrl({
  address: "0xYourTokenAddress",
  tab: "holders",
});
```

#### Tab values by item type

| Helper               | Tab type             | Values                                                                                                        |
| -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `txExplorerUrl`      | `TxExplorerTab`      | `'index'`, `'token_transfers'`, `'internal'`, `'logs'`, `'state'`, `'raw_trace'`                              |
| `addressExplorerUrl` | `AddressExplorerTab` | `'txs'`, `'token_transfers'`, `'tokens'`, `'internal_txns'`, `'coin_balance_history'`, `'logs'`, `'contract'` |
| `tokenExplorerUrl`   | `TokenExplorerTab`   | `'token_transfers'`, `'holders'`, `'contract'`                                                                |
| `blockExplorerUrl`   | `BlockExplorerTab`   | `'index'`, `'txs'`                                                                                            |

#### Standalone functions

The same helpers are exported as standalone functions that take an explicit `chain` and can be used without a client:

```typescript
import {
  addressExplorerUrl,
  blockExplorerUrl,
  getExplorerUrl,
  tokenExplorerUrl,
  txExplorerUrl,
} from "seismic-viem";

const url = txExplorerUrl({
  chain: seismicTestnet,
  txHash: "0xabc...",
  tab: "logs",
});

// Low-level form used by all helpers internally:
const raw = getExplorerUrl(seismicTestnet, {
  item: "address",
  id: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
  tab: "logs",
});
```

## See Also

* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- Full-featured client with encryption and shielded writes
* [Deposit Contract](/clients/typescript/viem/deposit-contract) -- Validator staking read/write actions
* [SRC20 Event Watching](/clients/typescript/viem/src20) -- Watch and decrypt SRC20 token events
* [Chains](/clients/typescript/viem/chains) -- Chain configurations for Seismic networks
* [Precompiles](/clients/typescript/viem/precompiles) -- Precompile details and parameters
* [Encryption](/clients/typescript/viem/encryption) -- Encryption utilities and `getEncryption()`


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

## Import

```typescript
import { createShieldedWalletClient } from "seismic-viem";
```

## Constructor

```typescript
const walletClient = await createShieldedWalletClient({
  chain: seismicTestnet,
  transport: http(),
  account: privateKeyToAccount("0x..."),
});
```

{% hint style="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.
{% endhint %}

### Parameters

| Parameter      | Type                   | Required | Description                                                                      |
| -------------- | ---------------------- | -------- | -------------------------------------------------------------------------------- |
| `chain`        | `Chain`                | Yes      | Chain configuration (e.g., `seismicTestnet`)                                     |
| `transport`    | `Transport`            | Yes      | viem transport (e.g., `http()`)                                                  |
| `account`      | `Account`              | Yes      | 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           |

### Return Type

`Promise<ShieldedWalletClient>`

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

## Usage

```typescript
import { createShieldedWalletClient } from "seismic-viem";
import { seismicTestnet } from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const walletClient = await createShieldedWalletClient({
  chain: seismicTestnet,
  transport: http(),
  account: privateKeyToAccount("0x..."),
});

// Smart write -- auto-detects shielded params in the ABI
// transfer(saddress, suint256) has shielded params → encrypted seismic tx
const hash = await walletClient.writeContract({
  address: "0xContractAddress",
  abi: contractAbi,
  functionName: "transfer",
  args: ["0xRecipient", 1000n],
});

// Smart read -- auto-detects shielded params in the ABI
// balanceOf(saddress) has shielded params → signed read
const balance = await walletClient.readContract({
  address: "0xContractAddress",
  abi: contractAbi,
  functionName: "balanceOf",
  args: ["0xMyAddress"],
});
```

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

## Actions

### Shielded Wallet Actions

| Action                            | Description                                                                                                                                                            |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `writeContract(params)`           | Smart write -- inspects ABI for shielded params; encrypts if shielded, sends transparent otherwise                                                                     |
| `swriteContract(params)`          | Force shielded write -- always encrypts calldata with the AES key before sending                                                                                       |
| `twriteContract(params)`          | Transparent write -- always sends with plaintext calldata (unencrypted)                                                                                                |
| `dwriteContract(params)`          | Send + inspect write -- broadcasts a real shielded tx and returns the plaintext tx, shielded tx, and `txHash`                                                          |
| `readContract(params)`            | Smart read -- inspects ABI for shielded params; uses signed read if shielded, transparent read otherwise                                                               |
| `sreadContract(params)`           | Force signed read -- always authenticated `eth_call` that proves the caller's identity                                                                                 |
| `treadContract(params)`           | Transparent read -- always standard unsigned call. Rejects `account` (Seismic zeroes out `from` on transparent `eth_call`); use `sreadContract` for sender-aware reads |
| `signedCall(params)`              | Low-level signed `eth_call`                                                                                                                                            |
| `sendShieldedTransaction(params)` | Low-level shielded transaction send                                                                                                                                    |

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

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

{% hint style="info" %}
The wallet client automatically includes all public client actions -- you can call `getBlockNumber()`, `getBalance()`, etc. directly on it.
{% endhint %}

## Examples

### Shielded Write

```typescript
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

```typescript
// 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);
```

### Reusing a Public Client

```typescript
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,
});
```

### Send + Inspect Write

```typescript
// dwriteContract broadcasts a real shielded tx AND returns the plaintext
// and shielded tx views for inspection. txHash is a real on-chain hash.
const debugResult = await walletClient.dwriteContract({
  address: "0xContractAddress",
  abi: contractAbi,
  functionName: "transfer",
  args: ["0xRecipient", 1000n],
});

console.log("Plaintext tx:", debugResult.plaintextTx);
console.log("Shielded tx:", debugResult.shieldedTx);
console.log("Tx hash:", debugResult.txHash);
```

### Custom Encryption Key

```typescript
const walletClient = await createShieldedWalletClient({
  chain: seismicTestnet,
  transport: http(),
  account: privateKeyToAccount("0x..."),
  encryptionSk: "0xCustomEncryptionPrivateKey",
});

// Access encryption details
const aesKey = walletClient.getEncryption();
const encPubKey = walletClient.getEncryptionPublicKey();
```

## `getEncryption()` Standalone Function

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

```typescript
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
```

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

## See Also

* [Shielded Public Client](/clients/typescript/viem/shielded-public-client) -- Read-only client without private key
* [Deposit Contract](/clients/typescript/viem/deposit-contract) -- Validator staking via `deposit()`
* [SRC20 Event Watching](/clients/typescript/viem/src20) -- Watch and decrypt SRC20 token events
* [Encryption](/clients/typescript/viem/encryption) -- Encryption utilities and `getEncryption()`
* [Chains](/clients/typescript/viem/chains) -- Chain configurations for Seismic networks
* [Precompiles](/clients/typescript/viem/precompiles) -- Precompile details and parameters
* [Contract Instance](/clients/typescript/viem/contract-instance) -- Working with contract wrappers


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

## Encryption Flow

Every shielded transaction follows this pipeline:

```
1. Client generates an ephemeral secp256k1 keypair (or uses a provided one)
2. Client fetches the TEE public key from the node via seismic_getTeePublicKey
3. ECDH(client_sk, tee_pk) → shared secret
4. Shared secret → AES-256 key (via key derivation)
5. For each transaction:
   a. Generate a random 12-byte nonce
   b. Encode TxSeismicMetadata as Additional Authenticated Data (AAD)
   c. AES-GCM encrypt(plaintext_calldata, nonce, aad) → ciphertext
   d. Include encryptionPubkey + nonce in the transaction's SeismicTxExtras fields
```

{% hint style="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.
{% endhint %}

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

### Import

```typescript
import { getEncryption } from "seismic-viem";
```

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

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

### Example

```typescript
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",
);
```

## 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>` | AES-GCM decrypt ciphertext using metadata as AAD                |

### Example

```typescript
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);
```

## 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         |
| `expiresAtBlock`   | `bigint`  | Block number after which the transaction expires     |
| `signedRead`       | `boolean` | `true` for signed reads, `false` for standard writes |

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.

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

For EIP-712 signing, the `TxSeismic` typed-data message also includes `authorizationListHash = keccak256(rlp(authorizationList))`. This binds the EIP-7702 authorization list carried on the transaction wire format into the signature. The hash for an empty authorization list is `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347`.

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

{% hint style="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.
{% endhint %}

## See Also

* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- Client that performs encryption automatically
* [Shielded Writes](/clients/typescript/viem/shielded-writes) -- How encrypted transactions are built and sent
* [Signed Reads](/clients/typescript/viem/signed-reads) -- Authenticated reads that prove caller identity
* [Precompiles](/clients/typescript/viem/precompiles) -- On-chain cryptographic operations via precompiled contracts
* [Installation](/clients/typescript/viem/installation) -- Bundled crypto dependencies


# Contract Instance

Type-safe shielded contract instances with smart routing and read/write/sread/swrite/tread/twrite/dwrite namespaces

`getShieldedContract` creates a type-safe contract instance with seven 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 smart routing, encrypted writes, signed reads, transparent operations, and debug inspection.

```typescript
import { getShieldedContract } from "seismic-viem";
```

## Constructor

| Parameter | Type                                   | Required | Description                                                                                                                                                                                                                                                                                                                                                                           |
| --------- | -------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `abi`     | `Abi`                                  | Yes      | Contract ABI (use `as const` for full type inference)                                                                                                                                                                                                                                                                                                                                 |
| `address` | `Address`                              | Yes      | Deployed contract address                                                                                                                                                                                                                                                                                                                                                             |
| `client`  | `ShieldedWalletClient` \| keyed client | Yes      | Wallet client for full capabilities, or a keyed client (e.g., `{ public: publicClient }`) for read-only use. All writes (`.write`, `.swrite`, `.twrite`, `.dwrite`) and signed reads (`.sread`, plus `.read` when the target function has shielded params) require a wallet client. A keyed read-only client only supports `.tread` and `.read` on functions without shielded params. |

```typescript
import { getShieldedContract } from "seismic-viem";

const abi = [
  {
    name: "balanceOf",
    type: "function",
    stateMutability: "view",
    inputs: [{ name: "account", type: "saddress" }],
    outputs: [{ name: "", type: "uint256" }],
  },
  {
    name: "transfer",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "to", type: "saddress" },
      { name: "amount", type: "suint256" },
    ],
    outputs: [{ name: "", type: "bool" }],
  },
  {
    name: "totalSupply",
    type: "function",
    stateMutability: "view",
    inputs: [],
    outputs: [{ name: "", type: "uint256" }],
  },
  {
    name: "approve",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "spender", type: "saddress" },
      { name: "amount", type: "suint256" },
    ],
    outputs: [{ name: "", type: "bool" }],
  },
] as const;

const contract = getShieldedContract({
  abi,
  address: "0x1234567890abcdef1234567890abcdef12345678",
  client,
});
```

## Namespaces

The returned `ShieldedContract` exposes seven 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`   | Smart Read        | Auto-detected | Auto-detected    | Inspects ABI -- shielded if shielded params, else transparent      |
| `.write`  | Smart Write       | Auto-detected | Signer's address | Inspects ABI -- shielded if shielded params, else transparent      |
| `.sread`  | Force Signed Read | Encrypted     | Signer's address | Always authenticated read -- proves identity                       |
| `.swrite` | Force Shielded    | Encrypted     | Signer's address | Always encrypted transaction                                       |
| `.tread`  | Transparent Read  | Plaintext     | Zero address     | Always standard read -- rejects `account`                          |
| `.twrite` | Transparent Write | Plaintext     | Signer's address | Always standard write                                              |
| `.dwrite` | Send + Inspect    | Encrypted     | Signer's address | Broadcasts shielded tx and returns plaintext + encrypted tx + hash |

***

### `.read` / `.write` -- Smart Routing

The default `.read` and `.write` namespaces **auto-detect** whether a function has shielded parameters (`suint*`, `sint*`, `sbool`, `saddress`, `sbytes*`, including nested in tuples and arrays). If any input parameter is shielded, the call is routed to the shielded path; otherwise it uses the transparent path.

```typescript
// transfer() has saddress and suint256 params → shielded write (type 0x4A)
const hash = await contract.write.transfer(["0x1234...", 100n]);

// totalSupply() has no shielded params → transparent read
const supply = await contract.read.totalSupply();

// balanceOf() has saddress param → signed read (encrypted, proves identity)
const balance = await contract.read.balanceOf(["0x1234..."]);
```

{% hint style="info" %}
Smart routing inspects **input parameters** only. If a function has no shielded inputs but you still want an encrypted read (e.g., because the return value is sensitive), use `.sread` instead.
{% endhint %}

***

### `.sread` -- Force Signed Read

Always sends an encrypted, signed `eth_call` that proves your identity to the contract, regardless of whether the function has shielded parameters. Use this when you want the response encrypted or when the contract checks `msg.sender` in a view function with no shielded inputs.

```typescript
// Force signed read even though totalSupply() has no shielded params
const supply = await contract.sread.totalSupply();
```

{% hint style="info" %}
The `.sread` 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.
{% endhint %}

See [Signed Reads](/clients/typescript/viem/signed-reads) for details on how signed reads work under the hood.

***

### `.swrite` -- Force Shielded Write

Always encrypts calldata before broadcasting the transaction, regardless of whether the function has shielded parameters. The calldata is not visible on-chain.

```typescript
// Force shielded write even though approve() might not need encryption
const hash = await contract.swrite.approve(["0x1234...", 100n], {
  gas: 100_000n,
});
```

See [Shielded Writes](/clients/typescript/viem/shielded-writes) for the encryption lifecycle and security parameters.

***

### `.tread` -- Transparent Read

Standard read call with plaintext calldata. The `from` address is set to the zero address, so `msg.sender` inside the contract will be `0x0000...0000`. Works with both standard and shielded-typed functions (the ABI types are remapped automatically for encoding).

```typescript
const supply = await contract.tread.totalSupply();
```

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

{% hint style="warning" %}
`.tread` **rejects** the `account` option and will throw. Seismic zeroes out `from` on transparent `eth_call`, so an `account` passed here would be ignored on the node and cause silent bugs. Use `.sread` for sender-aware reads.
{% endhint %}

***

### `.twrite` -- Transparent Write

Standard write call with plaintext (unencrypted) calldata. The transaction is signed by the wallet, so `msg.sender` is the signer's address. Works with both standard and shielded-typed functions (the ABI types are remapped automatically for encoding).

```typescript
const hash = await contract.twrite.approve(["0x1234...", 100n]);
```

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

***

### `.dwrite` -- Send + Inspect

Sends the same encrypted transaction as `.swrite` -- the tx **is** broadcast and `txHash` is a real on-chain hash -- and additionally returns the plaintext transaction view and the shielded (encrypted) transaction view alongside the hash. Useful for inspecting exactly what the SDK encrypted and submitted.

```typescript
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);
```

{% hint style="info" %}
Despite the "debug" flavor of the name, `.dwrite` broadcasts a real shielded transaction. If you just want to see what would be sent without submitting, build the calldata yourself via `getPlaintextCalldata`.
{% endhint %}

## TypeScript ABI Typing

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

```typescript
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..."]);
```

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.

## See Also

* [Shielded Writes](/clients/typescript/viem/shielded-writes) -- Encryption lifecycle and `shieldedWriteContract`
* [Signed Reads](/clients/typescript/viem/signed-reads) -- Authenticated reads and `signedReadContract`
* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- Creating the client passed to `getShieldedContract`
* [Encryption](/clients/typescript/viem/encryption) -- ECDH key exchange and AES-GCM calldata encryption


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

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

```typescript
// Smart read -- balanceOf(saddress) has shielded param → signed read automatically
const myBalance = await contract.read.balanceOf(["0x1234..."]);

// Smart read -- totalSupply() has no shielded params → transparent read automatically
const totalSupply = await contract.read.totalSupply();

// Force signed read -- always encrypted, even for non-shielded functions
const supply = await contract.sread.totalSupply();
```

seismic-viem provides several approaches:

* **`contract.read.functionName()`** -- smart routing via [getShieldedContract](/clients/typescript/viem/contract-instance). Auto-detects shielded parameters and uses signed read only when needed.
* **`contract.sread.functionName()`** -- force signed read via [getShieldedContract](/clients/typescript/viem/contract-instance). Always uses signed read regardless of parameter types.
* **`signedReadContract()`** -- standalone function, same API shape as viem's `readContract`. Always uses signed read.
* **`walletClient.readContract()`** -- smart routing via the wallet client. Same auto-detection as `contract.read`.
* **`walletClient.sreadContract()`** -- force signed read via the wallet client. Always uses signed read.

The signed read paths all encrypt the calldata, sign it, and decrypt the response automatically.

***

## Standalone: `signedReadContract`

```typescript
import { signedReadContract } from "seismic-viem";
```

### Parameters

`signedReadContract(client, parameters, securityParams?)`

| Parameter      | Type     | Required | Description                    |
| -------------- | -------- | -------- | ------------------------------ |
| `address`      | `Hex`    | Yes      | Contract address               |
| `abi`          | `Abi`    | Yes      | Contract ABI                   |
| `functionName` | `string` | Yes      | Name of the view/pure function |
| `args`         | `array`  | No       | Function arguments             |
| `nonce`        | `number` | No       | Override the nonce             |

The optional third argument `securityParams: SeismicSecurityParams` accepts advanced Seismic metadata overrides (see [Security Parameters](/clients/typescript/viem/shielded-writes#security-parameters)). Most callers should omit these; they are mainly useful for deterministic tests/debugging, explicit expiry control, and low-level interop.

### Returns

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

{% hint style="info" %}
If the `client` has no `account` configured, `signedReadContract` falls back to a standard `readContract` (transparent `eth_call`). Pass an account-bearing `ShieldedWalletClient` to get the signed read behavior.
{% endhint %}

### Example

```typescript
import { signedReadContract } from "seismic-viem";

const balance = await signedReadContract(
  client,
  {
    address: "0x1234567890abcdef1234567890abcdef12345678",
    abi: myContractAbi,
    functionName: "balanceOf",
    args: ["0xMyAddress..."],
  },
  {
    blocksWindow: 50n, // optional: expire after 50 blocks instead of the default 100
  },
);
```

***

## Low-level: `signedCall`

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

```typescript
import { signedCall } from "seismic-viem";

const result = await signedCall(client, {
  to: "0x1234567890abcdef1234567890abcdef12345678",
  data: "0x...", // raw calldata
  account: client.account,
  gas: 30_000_000n,
});
```

`signedCall` also accepts `SeismicSecurityParams` as a third argument for overriding the encryption nonce, block hash, or expiry window. See [Shielded Writes](/clients/typescript/viem/shielded-writes#security-parameters) for the full parameter table.

***

## How It Works

When you call `signedReadContract` (or `contract.sread.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.

***

## Signed Read vs Transparent Read

| Aspect       | `.read` (Smart)                      | `.sread` (Force Signed)                    | `.tread` (Transparent)    |
| ------------ | ------------------------------------ | ------------------------------------------ | ------------------------- |
| Calldata     | Auto-detected by ABI                 | Always encrypted                           | Always plaintext          |
| `msg.sender` | Signer if shielded, zero if not      | Always signer's address                    | Always zero address       |
| Use case     | Default -- handles most cases        | Force encryption for sensitive returns     | Public view functions     |
| Performance  | Optimal -- encrypts only when needed | Slightly slower (sign + encrypt + decrypt) | Standard `eth_call` speed |

```typescript
// Smart read -- auto-detects: balanceOf(saddress) → signed, totalSupply() → transparent
const myBalance = await contract.read.balanceOf(["0x1234..."]);
const totalSupply = await contract.read.totalSupply();

// Force signed read -- always encrypted
const supply = await contract.sread.totalSupply();

// Force transparent read -- always plaintext
const supply2 = await contract.tread.totalSupply();
```

{% hint style="info" %}
The smart `.read` namespace handles most cases correctly by inspecting the ABI. Use `.sread` when you need the response encrypted even though the function has no shielded input parameters. Use `.tread` only for public data where you don't need authentication.
{% endhint %}

{% hint style="warning" %}
`.tread` and `walletClient.treadContract` reject the `account` option and will throw. On Seismic, transparent `eth_call` zeroes out `from` on the node, so any `account` you pass would silently be ignored. For any sender-aware read, use `.sread` / `sreadContract`.
{% endhint %}

## See Also

* [Contract Instance](/clients/typescript/viem/contract-instance) -- `getShieldedContract` with `.read` and `.tread` namespaces
* [Shielded Writes](/clients/typescript/viem/shielded-writes) -- Encrypted write transactions using the same pipeline
* [Encryption](/clients/typescript/viem/encryption) -- ECDH key exchange and AES-GCM details
* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- Creating the client used for signed reads


# 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 several approaches:

* **`contract.write.functionName()`** -- smart routing via [getShieldedContract](/clients/typescript/viem/contract-instance). Auto-detects shielded parameters and encrypts only when needed.
* **`contract.swrite.functionName()`** -- force shielded via [getShieldedContract](/clients/typescript/viem/contract-instance). Always encrypts, regardless of parameter types.
* **`shieldedWriteContract()`** -- standalone function, same API shape as viem's `writeContract`. Always encrypts.
* **`walletClient.writeContract()`** -- smart routing via the wallet client. Same auto-detection as `contract.write`.
* **`walletClient.swriteContract()`** -- force shielded via the wallet client. Always encrypts.

The shielded paths all produce the same on-chain result: an encrypted type `0x4A` Seismic transaction.

## Standalone: `shieldedWriteContract`

```typescript
import { shieldedWriteContract } from "seismic-viem";
```

### Parameters

| Parameter      | Type     | Required | Description        |
| -------------- | -------- | -------- | ------------------ |
| `address`      | `Hex`    | Yes      | Contract address   |
| `abi`          | `Abi`    | Yes      | Contract ABI       |
| `functionName` | `string` | Yes      | 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  |

### Returns

`Promise<Hash>` -- the transaction hash.

### Example

```typescript
import { shieldedWriteContract } from "seismic-viem";

const hash = await shieldedWriteContract(client, {
  address: "0x1234567890abcdef1234567890abcdef12345678",
  abi: myContractAbi,
  functionName: "transfer",
  args: ["0xRecipient...", 100n],
  gas: 100_000n,
});
```

***

## Send + Inspect: `shieldedWriteContractDebug`

Broadcasts a shielded transaction (like `shieldedWriteContract`) and additionally returns the plaintext transaction view and the shielded (encrypted) transaction view alongside the resulting transaction hash. Useful for inspecting exactly what the SDK encrypted and submitted.

{% hint style="info" %}
Despite the "debug" flavor of the name, `shieldedWriteContractDebug` does send the transaction -- the returned `txHash` is a real on-chain hash, not a dry run.
{% endhint %}

```typescript
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);
```

***

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

```typescript
const hash = await client.sendShieldedTransaction({
  to: "0x1234567890abcdef1234567890abcdef12345678",
  data: "0x...", // raw calldata
  value: 0n,
  gas: 100_000n,
  gasPrice: 1_000_000_000n,
});
```

***

## How It Works

When you call `shieldedWriteContract` (or `contract.swrite.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.

***

## 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`, passed as the optional third argument to `shieldedWriteContract`, `shieldedWriteContractDebug`, `sendShieldedTransaction`, `signedReadContract`, and `signedCall`:

| Parameter         | Type     | Default    | Description                                     |
| ----------------- | -------- | ---------- | ----------------------------------------------- |
| `blocksWindow`    | `bigint` | `100n`     | Number of blocks before the transaction expires |
| `encryptionNonce` | `Hex`    | Random     | Override the encryption nonce                   |
| `recentBlockHash` | `Hex`    | Latest     | Override the recent block hash                  |
| `expiresAtBlock`  | `bigint` | Calculated | Override the expiry block directly              |

```typescript
const hash = await shieldedWriteContract(
  client,
  {
    address: "0x...",
    abi: myContractAbi,
    functionName: "transfer",
    args: ["0x...", 100n],
  },
  {
    blocksWindow: 50n, // expires after 50 blocks instead of 100
  },
);
```

{% hint style="info" %}
`securityParams` only applies to the low-level shielded paths (`shieldedWriteContract`, `shieldedWriteContractDebug`, `sendShieldedTransaction`, `signedReadContract`, `signedCall`, and the `.swrite`/`.sread`/`.dwrite` namespaces on `getShieldedContract`). The smart-routing `.read`/`.write` and transparent `.tread`/`.twrite` paths do not accept it.
{% endhint %}

{% hint style="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.
{% endhint %}

## See Also

* [Contract Instance](/clients/typescript/viem/contract-instance) -- `getShieldedContract` with `.write` and `.dwrite` namespaces
* [Signed Reads](/clients/typescript/viem/signed-reads) -- Authenticated reads that also use the encryption pipeline
* [Encryption](/clients/typescript/viem/encryption) -- ECDH key exchange and AES-GCM details
* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- Creating the client used for shielded writes


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

## Import

```typescript
import {
  seismicTestnet,
  sanvil,
  localSeismicDevnet,
  createSeismicDevnet,
} from "seismic-viem";
```

## Available Chains

| Chain           | Export               | Chain ID | RPC URL                                 | Description              |
| --------------- | -------------------- | -------- | --------------------------------------- | ------------------------ |
| Seismic Testnet | `seismicTestnet`     | 5124     | `https://testnet-1.seismictest.net/rpc` | Public testnet           |
| Sanvil          | `sanvil`             | 31337    | `http://127.0.0.1:8545`                 | Local Seismic Anvil      |
| Local Devnet    | `localSeismicDevnet` | 5124     | `http://127.0.0.1:8545`                 | Local seismic-reth --dev |

### Seismic Testnet

The public testnet for development and testing against a live Seismic network:

```typescript
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..."),
});
```

### Sanvil

Local development chain using Sanvil (Seismic's fork of Anvil). Chain ID `31337` matches Anvil/Hardhat defaults:

```typescript
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..."),
});
```

### Local Devnet

For running a local `seismic-reth --dev` node. Uses chain ID `5124` (same as testnet) but connects to `localhost`:

```typescript
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..."),
});
```

## Choosing a Chain

```
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()
```

## SEISMIC\_TX\_TYPE

All chain configs use the Seismic transaction type constant:

```typescript
import { SEISMIC_TX_TYPE } from "seismic-viem";

console.log(SEISMIC_TX_TYPE); // 74 (0x4A)
```

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.

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

{% hint style="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`.
{% endhint %}

## Custom Chain Factory

### createSeismicDevnet

Create a custom chain definition for any Seismic-compatible node:

```typescript
import { createSeismicDevnet } from "seismic-viem";

const myChain = createSeismicDevnet({
  nodeHost: "https://my-seismic-node.example.com",
  explorerUrl: "https://explorer.example.com",
});
```

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

### Helper Factories

Convenience factories for common Seismic infrastructure:

```typescript
import { createSeismicAzTestnet, createSeismicGcpTestnet } from "seismic-viem";

// Azure-hosted testnet instance N
const azChain = createSeismicAzTestnet(1);

// GCP-hosted testnet instance N
const gcpChain = createSeismicGcpTestnet(1);
```

These generate chain configs pointing to numbered Seismic testnet instances on Azure and GCP infrastructure respectively.

## SeismicTransactionRequest

Seismic chain configs use a custom transaction request type that extends viem's standard `TransactionRequest` with Seismic-specific fields:

```typescript
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
}
```

{% hint style="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.
{% endhint %}

| 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  |
| `expiresAtBlock`   | `bigint`  | Block number after which the transaction becomes invalid  |
| `signedRead`       | `boolean` | `true` for signed read requests, `false` for standard txs |

## See Also

* [Installation](/clients/typescript/viem/installation) -- Install seismic-viem and viem
* [Seismic Viem Overview](/clients/typescript/viem) -- Full SDK overview and architecture
* Shielded Wallet Client -- Create a client using a chain config
* Encryption -- How calldata encryption uses chain-level formatters


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

## Import

```typescript
import {
  rng,
  ecdh,
  aesGcmEncrypt,
  aesGcmDecrypt,
  hdfk,
  secp256k1Sig,
} from "seismic-viem";
```

## Overview

| Precompile      | Address     | Description              | Input                           | Output                |
| --------------- | ----------- | ------------------------ | ------------------------------- | --------------------- |
| `rng`           | `0x...0064` | Random number generation | `numBytes`, `pers?`             | `bigint`              |
| `ecdh`          | `0x...0065` | ECDH key exchange        | `sk`, `pk`                      | `Hex` (shared secret) |
| `aesGcmEncrypt` | `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`           |

{% hint style="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.
{% endhint %}

## RNG -- Random Number Generation

Generates cryptographically secure random numbers using the TEE's CSPRNG.

### Standalone Function

```typescript
import { rng } from "seismic-viem";

const randomValue = await rng(client, {
  numBytes: 32,
});

console.log("Random value:", randomValue);
```

### Parameters

| Parameter  | Type               | Required | Description                                |
| ---------- | ------------------ | -------- | ------------------------------------------ |
| `numBytes` | `bigint \| number` | Yes      | Number of random bytes to generate (1--32) |
| `pers`     | `Hex \| ByteArray` | No       | Personalization string to seed the CSPRNG  |

### Returns

`bigint` -- the generated random value.

### Example

```typescript
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);
```

***

## ECDH -- Elliptic Curve Diffie-Hellman

Performs an ECDH key exchange inside the TEE and returns the shared secret.

### Standalone Function

```typescript
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);
```

### Parameters

| Parameter | Type  | Required | Description                             |
| --------- | ----- | -------- | --------------------------------------- |
| `sk`      | `Hex` | Yes      | 32-byte secp256k1 secret key            |
| `pk`      | `Hex` | Yes      | 33-byte compressed secp256k1 public key |

### Returns

`Hex` -- 32-byte shared secret.

### Example

```typescript
import { ecdh } from "seismic-viem";

const sharedSecret = await ecdh(client, {
  sk: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
  pk: "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
});

console.log("Shared secret:", sharedSecret);
```

***

## AES-GCM Encrypt / Decrypt

Performs AES-GCM encryption and decryption inside the TEE.

### Encrypt

```typescript
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);
```

#### Encrypt Parameters

| Parameter   | Type     | Required | Description                 |
| ----------- | -------- | -------- | --------------------------- |
| `aesKey`    | `Hex`    | Yes      | 32-byte AES-256 key         |
| `nonce`     | `number` | Yes      | Numeric nonce for AES-GCM   |
| `plaintext` | `string` | Yes      | Plaintext string to encrypt |

#### Encrypt Returns

`Hex` -- the AES-GCM ciphertext.

### Decrypt

```typescript
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);
```

#### Decrypt Parameters

| Parameter    | Type     | Required | Description                  |
| ------------ | -------- | -------- | ---------------------------- |
| `aesKey`     | `Hex`    | Yes      | 32-byte AES-256 key          |
| `nonce`      | `number` | Yes      | Nonce used during encryption |
| `ciphertext` | `Hex`    | Yes      | Ciphertext to decrypt        |

#### Decrypt Returns

`string` -- the decrypted plaintext.

### Full Example

```typescript
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"
```

***

## HKDF -- Key Derivation

Derives a key from input key material using HKDF inside the TEE.

### Standalone Function

```typescript
import { hdfk } from "seismic-viem";

const derivedKey = await hdfk(client, "0x..."); // input key material

console.log("Derived key:", derivedKey);
```

### Parameters

| Parameter | Type  | Required | Description        |
| --------- | ----- | -------- | ------------------ |
| `ikm`     | `Hex` | Yes      | Input key material |

### Returns

`Hex` -- the derived key.

### Example

```typescript
import { hdfk } from "seismic-viem";

const inputKeyMaterial = "0xdeadbeef";
const derivedKey = await hdfk(client, inputKeyMaterial);
console.log("Derived key:", derivedKey);
```

***

## secp256k1 Signature

Generates a secp256k1 signature inside the TEE.

### Standalone Function

```typescript
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);
```

### Parameters

| Parameter | Type     | Required | Description                  |
| --------- | -------- | -------- | ---------------------------- |
| `sk`      | `Hex`    | Yes      | 32-byte secp256k1 secret key |
| `message` | `string` | Yes      | Message to sign              |

### Returns

`Signature` -- a viem `Signature` object with `r`, `s`, and `v` components.

### Example

```typescript
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);
```

***

## 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              |
| ----------------------------------- | ------------------------------- | ------------------------ |
| `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)` | AES-GCM decryption       |
| `client.hdfk(ikm)`                  | `hdfk(client, ikm)`             | HKDF key derivation      |
| `client.secp256k1Signature(params)` | `secp256k1Sig(client, params)`  | secp256k1 signing        |

### Example

```typescript
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...");
```

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

### `Precompile<P, R>` Type

| Property               | Type                  | Description                                   |
| ---------------------- | --------------------- | --------------------------------------------- |
| `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                   |

### Available Precompile Objects

| Export                    | Type                                         |
| ------------------------- | -------------------------------------------- |
| `rngPrecompile`           | `Precompile<RngParams, bigint>`              |
| `ecdhPrecompile`          | `Precompile<EcdhParams, Hex>`                |
| `aesGcmEncryptPrecompile` | `Precompile<AesGcmEncryptionParams, Hex>`    |
| `aesGcmDecryptPrecompile` | `Precompile<AesGcmDecryptionParams, string>` |
| `hdfkPrecompile`          | `Precompile<Hex, Hex>`                       |
| `secp256k1SigPrecompile`  | `Precompile<Secp256K1SigParams, Signature>`  |

The `CallClient` type accepts any client with a `.call()` method, so both `ShieldedPublicClient` and `ShieldedWalletClient` work with `callPrecompile`.

### Calling via `callPrecompile`

```typescript
import { callPrecompile, rngPrecompile } from "seismic-viem";

// Access the precompile address directly
console.log("RNG precompile address:", rngPrecompile.address);

// Call any precompile by passing its object to callPrecompile
const randomValue = await callPrecompile({
  client,
  precompile: rngPrecompile,
  args: { numBytes: 32 },
});
```

`callPrecompile` handles gas cost calculation, parameter encoding, and result decoding automatically using the `Precompile<P, R>` object passed in. It throws if no data is returned, if the arguments fail validation, or if encoding/decoding fails.

## See Also

* [Shielded Public Client](/clients/typescript/viem/shielded-public-client) -- Read-only client with precompile methods
* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- Full-featured client with precompile methods
* [Encryption](/clients/typescript/viem/encryption) -- Client-side ECDH key exchange and AES-GCM encryption
* [Chains](/clients/typescript/viem/chains) -- Chain configurations for Seismic networks


# Deposit Contract

Validator staking operations on the Seismic deposit contract

The deposit contract is the entry point for validator staking on Seismic. It accepts deposits that register validators with their node and consensus keys, and exposes read methods for the current deposit root and count. Seismic ships client extensions for both sides.

* `depositContractPublicActions` -- read-only: `getDepositRoot()`, `getDepositCount()`
* `depositContractWalletActions` -- write: `deposit()`

Both extensions are already applied by `createShieldedPublicClient` and `createShieldedWalletClient`; you can also extend a vanilla viem client with them manually.

## Import

```typescript
import {
  DEPOSIT_CONTRACT_ADDRESS,
  depositContractPublicActions,
  depositContractWalletActions,
} from "seismic-viem";
```

`DEPOSIT_CONTRACT_ADDRESS` is `0x00000000219ab540356cBB839Cbe05303d7705Fa`. Every action accepts an optional `address` to override this default.

## Extending a Client

```typescript
import {
  createShieldedPublicClient,
  createShieldedWalletClient,
  depositContractPublicActions,
  depositContractWalletActions,
  seismicTestnet,
} from "seismic-viem";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const publicClient = createShieldedPublicClient({
  chain: seismicTestnet,
  transport: http(),
}).extend(depositContractPublicActions);

const walletClient = (
  await createShieldedWalletClient({
    chain: seismicTestnet,
    transport: http(),
    account: privateKeyToAccount("0x..."),
  })
).extend(depositContractWalletActions);
```

## Public Actions

### `getDepositRoot`

Returns the current deposit merkle root.

```typescript
const depositRoot = await publicClient.getDepositRoot({});
```

| Parameter | Type      | Required | Description                                          |
| --------- | --------- | -------- | ---------------------------------------------------- |
| `address` | `Address` | No       | Deposit contract address (defaults to the canonical) |

**Returns:** `Promise<Hex>` -- the SHA-256 deposit root.

### `getDepositCount`

Returns the total number of deposits accepted by the contract.

```typescript
const depositCount = await publicClient.getDepositCount({});
```

| Parameter | Type      | Required | Description                                          |
| --------- | --------- | -------- | ---------------------------------------------------- |
| `address` | `Address` | No       | Deposit contract address (defaults to the canonical) |

**Returns:** `Promise<Hex>` -- the deposit count encoded as a little-endian 64-bit number.

## Wallet Actions

### `deposit`

Registers a validator by submitting a deposit transaction.

```typescript
import { parseEther } from "viem";

const txHash = await walletClient.deposit({
  nodePubkey: "0x...", // ED25519 public key (32 bytes)
  consensusPubkey: "0x...", // BLS12-381 public key (48 bytes)
  withdrawalCredentials: "0x...", // commitment to withdrawal pubkey
  nodeSignature: "0x...", // ED25519 signature (64 bytes)
  consensusSignature: "0x...", // BLS12-381 signature (96 bytes)
  depositDataRoot: "0x...", // SHA-256 of SSZ-encoded DepositData
  value: parseEther("32"),
});
```

| Parameter               | Type      | Required | Description                                                         |
| ----------------------- | --------- | -------- | ------------------------------------------------------------------- |
| `nodePubkey`            | `Hex`     | Yes      | Validator ED25519 public key                                        |
| `consensusPubkey`       | `Hex`     | Yes      | Validator BLS12-381 consensus public key                            |
| `withdrawalCredentials` | `Hex`     | Yes      | Commitment to a public key for future withdrawals                   |
| `nodeSignature`         | `Hex`     | Yes      | ED25519 signature over the deposit data                             |
| `consensusSignature`    | `Hex`     | Yes      | BLS12-381 signature over the deposit data                           |
| `depositDataRoot`       | `Hex`     | Yes      | SHA-256 hash of the SSZ-encoded `DepositData` -- acts as a checksum |
| `value`                 | `bigint`  | Yes      | Amount to deposit in wei                                            |
| `address`               | `Address` | No       | Deposit contract address (defaults to the canonical)                |

**Returns:** `Promise<Hex>` -- the transaction hash.

{% hint style="info" %}
`depositDataRoot` is verified on-chain against the supplied keys and signatures. If the root does not match the SSZ-encoded deposit data, the transaction reverts. This prevents malformed deposits from being silently accepted.
{% endhint %}

## See Also

* [Shielded Public Client](/clients/typescript/viem/shielded-public-client) -- base client that already includes these public actions
* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- base client that already includes these wallet actions


# SRC20 Event Watching

Watch and decrypt SRC20 token events

SRC20 is Seismic's confidential token standard. Transfer and approval amounts are encrypted in the event log, so standard event watchers see only ciphertext. seismic-viem ships two client extensions that filter these events and decrypt the amounts for you:

* `src20WalletActions.watchSRC20Events` -- wallet-client action that fetches the viewing key for the connected address from the Directory contract via a signed read, then watches and decrypts events the wallet can read.
* `src20PublicActions.watchSRC20EventsWithKey` -- public-client action that takes an explicit AES viewing key. Useful for server-side monitoring, intelligence providers, or any flow without a connected wallet.

Both actions are already applied by `createShieldedWalletClient` / `createShieldedPublicClient`; you can also extend a vanilla viem client with them manually.

## How Decryption Works

1. An account registers its AES viewing key with the Directory contract.
2. When the SRC20 contract emits `Transfer` or `Approval`, the log carries:
   * `encryptKeyHash` -- `keccak256(viewingKey)`, allowing on-chain filtering
   * `encryptedAmount` -- the AES-GCM ciphertext of the amount
3. The watcher subscribes with a filter on `encryptKeyHash` so it only receives events it can decrypt.
4. For each matching log, the client decrypts `encryptedAmount` with the AES key and invokes the user callback with a `DecryptedTransferLog` / `DecryptedApprovalLog`.

## Import

```typescript
import {
  src20PublicActions,
  src20WalletActions,
} from "seismic-viem";
```

## Wallet Action: `watchSRC20Events`

Watches events for the connected wallet. The viewing key is fetched automatically from the Directory contract via a signed read.

```typescript
const unwatch = await walletClient.watchSRC20Events({
  address: "0xYourTokenAddress",
  onTransfer: (log) => {
    console.log(`Transfer ${log.from} -> ${log.to}: ${log.decryptedAmount}`);
  },
  onApproval: (log) => {
    console.log(
      `Approval ${log.owner} -> ${log.spender}: ${log.decryptedAmount}`,
    );
  },
  onError: (err) => console.error("decrypt failed:", err),
});

// stop watching
unwatch();
```

| Parameter    | Type                                  | Required | Description                              |
| ------------ | ------------------------------------- | -------- | ---------------------------------------- |
| `address`    | `Address`                             | Yes      | SRC20 token contract address             |
| `onTransfer` | `(log: DecryptedTransferLog) => void` | No       | Called for each decrypted Transfer event |
| `onApproval` | `(log: DecryptedApprovalLog) => void` | No       | Called for each decrypted Approval event |
| `onError`    | `(error: Error) => void`              | No       | Called when decryption fails             |

**Returns:** `Promise<() => void>` -- call the returned function to stop watching.

{% hint style="warning" %}
Throws if no AES key is registered in the Directory contract for the connected address. Register a viewing key first (typically during wallet setup) before watching.
{% endhint %}

## Public Action: `watchSRC20EventsWithKey`

Same filter + decryption flow, but takes an explicit viewing key instead of fetching it from the Directory.

```typescript
const viewingKey: Hex = "0x..."; // 32-byte AES key

const unwatch = await publicClient.watchSRC20EventsWithKey(viewingKey, {
  address: "0xYourTokenAddress",
  onTransfer: (log) => console.log(log),
  onApproval: (log) => console.log(log),
  onError: (err) => console.error(err),
});
```

| Parameter    | Type                     | Required | Description                             |
| ------------ | ------------------------ | -------- | --------------------------------------- |
| `viewingKey` | `Hex`                    | Yes      | 32-byte AES key used to decrypt amounts |
| `params`     | `WatchSRC20EventsParams` | Yes      | Same shape as `watchSRC20Events` above  |

**Returns:** `Promise<() => void>` -- call the returned function to stop watching.

## Log Types

```typescript
type DecryptedTransferLog = {
  from: Address;
  to: Address;
  encryptKeyHash: Hex; // keccak256 of the viewing key
  encryptedAmount: Hex; // original AES-GCM ciphertext
  decryptedAmount: bigint; // plaintext amount
  transactionHash: Hex;
  blockNumber: bigint;
};

type DecryptedApprovalLog = {
  owner: Address;
  spender: Address;
  encryptKeyHash: Hex;
  encryptedAmount: Hex;
  decryptedAmount: bigint;
  transactionHash: Hex;
  blockNumber: bigint;
};
```

## See Also

* [Shielded Public Client](/clients/typescript/viem/shielded-public-client) -- base client that includes `watchSRC20EventsWithKey`
* [Shielded Wallet Client](/clients/typescript/viem/shielded-wallet-client) -- base client that includes `watchSRC20Events`
* [Encrypted Events tutorial](/tutorials/src20/encrypted-events) -- end-to-end SRC20 event walkthrough


# 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 [Seismic](https://seismic.systems), built on [wagmi](https://wagmi.sh/) 2.0+ and [viem](https://viem.sh/) 2.x. Provides `ShieldedWalletProvider` context and hooks for encrypted transactions and signed reads in React applications.

```bash
npm install seismic-react
```

## Quick Start

Minimal setup with RainbowKit:

```typescript
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>
  )
}
```

## Architecture

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

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

## Documentation Navigation

### Getting Started

| Section                                                                          | Description                                         |
| -------------------------------------------------------------------------------- | --------------------------------------------------- |
| [**Installation**](/clients/typescript/react/installation)                       | Package setup, peer dependencies, and configuration |
| [**ShieldedWalletProvider**](/clients/typescript/react/shielded-wallet-provider) | React context provider for shielded clients         |

### Hooks Reference

| Section                                                                          | Description                                 |
| -------------------------------------------------------------------------------- | ------------------------------------------- |
| [**Hooks Overview**](/clients/typescript/react/hooks)                            | Summary of all available hooks              |
| [**useShieldedWallet**](/clients/typescript/react/hooks/useshieldedwallet)       | Access shielded wallet and public clients   |
| [**useShieldedContract**](/clients/typescript/react/hooks/useshieldedcontract)   | Create a contract instance with ABI binding |
| [**useShieldedWriteContract**](/clients/typescript/react/hooks/useshieldedwrite) | Send encrypted write transactions           |
| [**useSignedReadContract**](/clients/typescript/react/hooks/useshieldedread)     | Perform signed, encrypted read calls        |

### Wallet Guides

| Section                                                               | Description                           |
| --------------------------------------------------------------------- | ------------------------------------- |
| [**Wallet Guides Overview**](/clients/typescript/react/wallet-guides) | Connecting different wallet providers |
| [**RainbowKit**](/clients/typescript/react/wallet-guides/rainbowkit)  | Setup with RainbowKit wallet UI       |
| [**Privy**](/clients/typescript/react/wallet-guides/privy)            | Embedded wallets with Privy           |
| [**AppKit**](/clients/typescript/react/wallet-guides/appkit)          | WalletConnect AppKit integration      |

## Quick Links

### By Task

* **Connect a shielded wallet** -> [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider)
* **Read shielded state** -> [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread)
* **Send an encrypted transaction** -> [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite)
* **Get a contract instance** -> [useShieldedContract](/clients/typescript/react/hooks/useshieldedcontract)
* **Install the package** -> [Installation](/clients/typescript/react/installation)
* **Set up RainbowKit** -> [RainbowKit Guide](/clients/typescript/react/wallet-guides/rainbowkit)

### By Component

* **Provider** -> [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider)
* **Hooks** -> [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet), [useShieldedContract](/clients/typescript/react/hooks/useshieldedcontract), [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite), [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread)
* **Chain configs** -> `seismicTestnet`, `sanvil`, `localSeismicDevnet`, `createSeismicDevnet`

## Features

* **Shielded Transactions** -- Encrypt calldata before sending via `useShieldedWriteContract`
* **Signed Reads** -- Prove caller identity in `eth_call` with `useSignedReadContract`
* **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

## Next Steps

1. [**Install seismic-react**](/clients/typescript/react/installation) -- Add the package and peer dependencies
2. [**Set up ShieldedWalletProvider**](/clients/typescript/react/shielded-wallet-provider) -- Wrap your app with the provider
3. [**Connect a wallet**](/clients/typescript/react/wallet-guides/rainbowkit) -- Choose a wallet integration
4. [**Use hooks**](/clients/typescript/react/hooks) -- Read and write shielded contract state


# Installation

Install seismic-react and configure peer dependencies

## Prerequisites

| Requirement            | Version | Notes                   |
| ---------------------- | ------- | ----------------------- |
| Node.js                | 18+     | LTS recommended         |
| 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 |

## Install

```bash
npm install seismic-react
```

```bash
yarn add seismic-react
```

```bash
pnpm add seismic-react
```

```bash
bun add seismic-react
```

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

```bash
npm install react wagmi viem seismic-viem @tanstack/react-query
```

{% hint style="info" %}
If you are using RainbowKit for wallet UI, also install `@rainbow-me/rainbowkit`:

```bash
npm install @rainbow-me/rainbowkit
```

{% endhint %}

## Wagmi Config Setup

Create a wagmi config with Seismic chain definitions:

```typescript
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],
});
```

{% hint style="warning" %}
Replace `YOUR_WALLETCONNECT_PROJECT_ID` with a project ID from [WalletConnect Cloud](https://cloud.walletconnect.com/).
{% endhint %}

## Minimal Working App

```typescript
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>
  )
}
```

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

```typescript
// Main entry
import {
  ShieldedWalletProvider,
  useShieldedWallet,
  useShieldedContract,
  useShieldedWriteContract,
  useSignedReadContract,
} from "seismic-react";

// Chain configs for RainbowKit
import { seismicTestnet, sanvil } from "seismic-react/rainbowkit";
```

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

## Troubleshooting

### Module Not Found: seismic-viem

Ensure `seismic-viem` is installed as a peer dependency:

```bash
npm install seismic-viem
```

### wagmi Version Mismatch

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

```bash
npm install wagmi@latest viem@latest
```

### RainbowKit Chain Not Appearing

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

```typescript
// Correct
import { seismicTestnet } from "seismic-react/rainbowkit";

// Incorrect -- will not resolve
import { seismicTestnet } from "seismic-react";
```

### 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`:

```json
{
  "resolutions": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
```

## See Also

* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Provider setup and configuration
* [Hooks Overview](/clients/typescript/react/hooks) -- Available React hooks
* [RainbowKit Guide](/clients/typescript/react/wallet-guides/rainbowkit) -- Wallet UI integration
* [Privy Guide](/clients/typescript/react/wallet-guides/privy) -- Embedded wallet setup


# 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 [seismic-viem](https://github.com/SeismicSystems/seismic-viem). This is the core provider that all Seismic React hooks depend on.

## Import

```typescript
import { ShieldedWalletProvider } from "seismic-react";
```

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

### OnAddressChangeParams

```typescript
type OnAddressChangeParams = {
  publicClient: ShieldedPublicClient;
  walletClient: ShieldedWalletClient;
  address: Hex;
};
```

## Context Value

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

```typescript
interface WalletClientContextType {
  publicClient: ShieldedPublicClient | null;
  walletClient: ShieldedWalletClient | null;
  address: Hex | null;
  error: string | null;
  loaded: boolean;
}
```

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

{% hint style="info" %}
Access these values through the [`useShieldedWallet`](/clients/typescript/react/hooks/useshieldedwallet) hook rather than consuming the context directly.
{% endhint %}

## Setup

### Basic Setup with RainbowKit

```typescript
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>
  )
}
```

### Next.js Setup

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

```typescript
'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>
  )
}
```

Then wrap your layout:

```typescript
// app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
```

## Provider Nesting Order

The providers must be nested in this order:

```
WagmiProvider
  └─ QueryClientProvider
       └─ RainbowKitProvider
            └─ ShieldedWalletProvider
                 └─ Your App
```

{% hint style="warning" %}
`ShieldedWalletProvider` must be inside `WagmiProvider` because it reads the connected wallet from wagmi's context. Placing it outside will cause a runtime error.
{% endhint %}

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

## Usage with onAddressChange

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

```typescript
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>
  )
}
```

## 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`:

```typescript
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>
}
```

## See Also

* [Installation](/clients/typescript/react/installation) -- Package setup and peer dependencies
* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Hook to consume the provider context
* [Hooks Overview](/clients/typescript/react/hooks) -- All available hooks
* [RainbowKit Guide](/clients/typescript/react/wallet-guides/rainbowkit) -- Wallet UI integration
* [Privy Guide](/clients/typescript/react/wallet-guides/privy) -- Embedded wallet setup


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

## Hook Comparison

| seismic-react Hook         | wagmi Equivalent                 | Purpose                                   |
| -------------------------- | -------------------------------- | ----------------------------------------- |
| `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          |

{% hint style="info" %}
All hooks require `ShieldedWalletProvider` context. Calling any hook outside the provider tree will throw an error.
{% endhint %}

## Common Patterns

### Wallet check

Always verify the wallet is loaded before calling contract methods:

```typescript
const { walletClient, loaded } = useShieldedWallet()

if (!loaded) return <div>Loading wallet...</div>
if (!walletClient) return <div>Please connect your wallet</div>
```

### Loading states

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

```typescript
const { writeContract, isLoading } = useShieldedWriteContract({ address, abi, functionName: 'transfer', args })

return (
  <button onClick={writeContract} disabled={isLoading}>
    {isLoading ? 'Sending...' : 'Transfer'}
  </button>
)
```

### Error handling

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

```typescript
const { signedRead, error } = useSignedReadContract({
  address,
  abi,
  functionName: "balanceOf",
});

async function fetchBalance() {
  const result = await signedRead();
  if (error) {
    console.error("Read failed:", error.message);
  }
}
```

## Pages

| Page                                                                         | Description                                            |
| ---------------------------------------------------------------------------- | ------------------------------------------------------ |
| [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet)       | Access shielded wallet and public clients from context |
| [useShieldedContract](/clients/typescript/react/hooks/useshieldedcontract)   | Get a ShieldedContract instance for reads and writes   |
| [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite) | Send encrypted write transactions                      |
| [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread)     | Execute authenticated read calls                       |

## See Also

* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Context provider required by all hooks
* [Installation](/clients/typescript/react/installation) -- Package setup and peer dependencies
* [Seismic React Overview](/clients/typescript/react) -- SDK architecture and quick start


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

```typescript
import { useShieldedWallet } from "seismic-react";
```

## Return Type

| Property       | Type                           | Description                                         |
| -------------- | ------------------------------ | --------------------------------------------------- |
| `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 |

***

## Usage

### Basic

```typescript
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>
}
```

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

```typescript
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>
}
```

### Error handling

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

```typescript
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>
}
```

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

```typescript
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>
  )
}
```

{% hint style="warning" %}
This hook must be used within a `ShieldedWalletProvider`. It will throw if called outside the provider tree.
{% endhint %}

## See Also

* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Context provider that supplies the shielded clients
* [useShieldedContract](/clients/typescript/react/hooks/useshieldedcontract) -- Create a contract instance using the wallet client
* [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite) -- Send encrypted write transactions
* [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread) -- Perform signed, encrypted read calls
* [Hooks Overview](/clients/typescript/react/hooks) -- Summary of all hooks


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

```typescript
import { useShieldedContract } from "seismic-react";
```

## Config

| Parameter | Type      | Required | Description      |
| --------- | --------- | -------- | ---------------- |
| `abi`     | `Abi`     | Yes      | Contract ABI     |
| `address` | `Address` | Yes      | Contract address |

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

***

## Usage

### Basic

```typescript
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>
}
```

### Using the contract for reads and writes

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

```typescript
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>
  )
}
```

### TypeScript ABI typing

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

```typescript
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..." });
```

{% hint style="info" %}
`useShieldedContract` requires a connected wallet via `ShieldedWalletProvider`. The `contract` field is `null` until the wallet client is initialized.
{% endhint %}

## See Also

* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Access the underlying wallet and public clients
* [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite) -- Send encrypted writes without a contract instance
* [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread) -- Perform signed reads without a contract instance
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Context provider required by this hook
* [Hooks Overview](/clients/typescript/react/hooks) -- Summary of all hooks


# useShieldedRead

Execute authenticated read calls on shielded contracts

{% hint style="info" %}
This hook wraps `useSignedReadContract` from seismic-react. The actual export name is `useSignedReadContract`.
{% endhint %}

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

```typescript
import { useSignedReadContract } from "seismic-react";
```

## Config

| Parameter      | Type                | Required | Description                            |
| -------------- | ------------------- | -------- | -------------------------------------- |
| `address`      | `` `0x${string}` `` | Yes      | Contract address                       |
| `abi`          | `Abi`               | Yes      | Contract ABI                           |
| `functionName` | `string`            | Yes      | Name of the view/pure function to call |
| `args`         | `array`             | No       | Arguments to pass to the function      |

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

***

## Usage

### Reading a shielded balance

```typescript
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>
  )
}
```

### Loading state handling

```typescript
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>
  )
}
```

### Error handling

```typescript
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>
  )
}
```

{% hint style="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.
{% endhint %}

## See Also

* [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite) -- Send encrypted write transactions
* [useShieldedContract](/clients/typescript/react/hooks/useshieldedcontract) -- Contract instance with both read and write methods
* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Access the underlying wallet client
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Context provider required by this hook
* [Hooks Overview](/clients/typescript/react/hooks) -- Summary of all hooks


# useShieldedWrite

Send encrypted write transactions to shielded contracts

{% hint style="info" %}
This hook wraps `useShieldedWriteContract` from seismic-react. The actual export name is `useShieldedWriteContract`.
{% endhint %}

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

```typescript
import { useShieldedWriteContract } from "seismic-react";
```

## Config

| Parameter      | Type     | Required | Description                             |
| -------------- | -------- | -------- | --------------------------------------- |
| `address`      | `Hex`    | Yes      | Contract address                        |
| `abi`          | `Abi`    | Yes      | Contract ABI                            |
| `functionName` | `string` | Yes      | 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                      |

## 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            |
| `hash`          | `` `0x${string}` \| null `` | Transaction hash from last successful write |

***

## Usage

### Shielded token transfer

```typescript
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>
  )
}
```

### Transaction hash tracking

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

```typescript
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>
  )
}
```

### Loading and error state handling

```typescript
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>
  )
}
```

### With gas override

Pass `gas` or `gasPrice` to override the automatic estimates:

```typescript
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>
  )
}
```

{% hint style="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.
{% endhint %}

## See Also

* [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread) -- Perform signed, encrypted read calls
* [useShieldedContract](/clients/typescript/react/hooks/useshieldedcontract) -- Contract instance with both read and write methods
* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Access the underlying wallet client
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Context provider required by this hook
* [Hooks Overview](/clients/typescript/react/hooks) -- Summary of all hooks


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

## Supported Libraries

| Library                                                              | Best For                         | Key Feature                                      |
| -------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------ |
| [**RainbowKit**](/clients/typescript/react/wallet-guides/rainbowkit) | dApps wanting polished wallet UI | Built-in modal, chain switching, account display |
| [**Privy**](/clients/typescript/react/wallet-guides/privy)           | Apps needing email/social login  | Embedded wallets, onboarding flow                |
| [**AppKit**](/clients/typescript/react/wallet-guides/appkit)         | WalletConnect ecosystem apps     | WalletConnect modal, broad wallet support        |

## 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
4. **Use hooks** from `seismic-react` in your components

### Provider Nesting Order

All wallet integrations require the same provider hierarchy:

```
WagmiProvider
  └─ QueryClientProvider
       └─ [Wallet Provider] (RainbowKit / Privy / AppKit)
            └─ ShieldedWalletProvider
                 └─ Your App
```

{% hint style="warning" %}
`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.
{% endhint %}

### Chain Configuration

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

```typescript
import { seismicTestnet } from "seismic-react/rainbowkit";
```

## Choosing a Library

* **Want the easiest setup with a beautiful wallet modal?** Use [RainbowKit](/clients/typescript/react/wallet-guides/rainbowkit)
* **Need email/social login or embedded wallets?** Use [Privy](/clients/typescript/react/wallet-guides/privy)
* **Want WalletConnect with maximum wallet compatibility?** Use [AppKit](/clients/typescript/react/wallet-guides/appkit)

## Pages

| Page                                                                 | Description                      |
| -------------------------------------------------------------------- | -------------------------------- |
| [**RainbowKit**](/clients/typescript/react/wallet-guides/rainbowkit) | Setup with RainbowKit wallet UI  |
| [**Privy**](/clients/typescript/react/wallet-guides/privy)           | Embedded wallets with Privy      |
| [**AppKit**](/clients/typescript/react/wallet-guides/appkit)         | WalletConnect AppKit integration |

## See Also

* [Installation](/clients/typescript/react/installation) -- Package setup and peer dependencies
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Provider reference and configuration
* [Hooks Overview](/clients/typescript/react/hooks) -- All available hooks


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

## Prerequisites

```bash
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query seismic-react seismic-viem
```

## Step 1: Configure wagmi

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

```typescript
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],
});
```

{% hint style="warning" %}
Replace `YOUR_WALLETCONNECT_PROJECT_ID` with a project ID from [WalletConnect Cloud](https://cloud.walletconnect.com/).
{% endhint %}

## Step 2: Set Up Providers

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

```typescript
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>
  )
}
```

{% hint style="info" %}
`ShieldedWalletProvider` must be inside `RainbowKitProvider` so it can access the connected wallet from wagmi's context.
{% endhint %}

## Step 3: Add the Connect Button

RainbowKit provides a pre-built connect button component:

```typescript
import { ConnectButton } from '@rainbow-me/rainbowkit'

function Header() {
  return (
    <header>
      <ConnectButton />
    </header>
  )
}
```

## Step 4: Use Shielded Hooks

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

```typescript
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>
}
```

## Next.js Setup

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

```typescript
// 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>
  )
}
```

Then wrap your root layout:

```typescript
// 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>
  )
}
```

{% hint style="info" %}
The `'use client'` directive is required because providers use React context, which is only available in client components.
{% endhint %}

## Local Development with Sanvil

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

```typescript
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,
  ],
});
```

## Complete Example

A full working setup in a single file:

```typescript
'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>
  )
}
```

## See Also

* [Wallet Guides Overview](/clients/typescript/react/wallet-guides) -- Comparison of wallet libraries
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Provider reference and options
* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Access shielded wallet context
* [Privy Guide](/clients/typescript/react/wallet-guides/privy) -- Email/social login alternative
* [AppKit Guide](/clients/typescript/react/wallet-guides/appkit) -- WalletConnect modal alternative


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

## Prerequisites

```bash
npm install @privy-io/react-auth @privy-io/wagmi wagmi viem @tanstack/react-query seismic-react seismic-viem
```

{% hint style="info" %}
You need a Privy App ID from the [Privy Dashboard](https://dashboard.privy.io/).
{% endhint %}

## Step 1: Configure Privy with Seismic Chain

Define the Seismic chain configuration for Privy:

```typescript
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,
};
```

## Step 2: Set Up wagmi Config

Create a wagmi config using Privy's wagmi integration:

```typescript
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(),
  },
});
```

## Step 3: Set Up Providers

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

```typescript
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>
  )
}
```

{% hint style="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.
{% endhint %}

## Step 4: Add Login Button

Use Privy's hooks to trigger the login modal:

```typescript
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>
}
```

## Step 5: Use Shielded Hooks

Once authenticated, use `seismic-react` hooks as normal:

```typescript
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>
}
```

## Complete Example

```typescript
'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>
  )
}
```

## See Also

* [Wallet Guides Overview](/clients/typescript/react/wallet-guides) -- Comparison of wallet libraries
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Provider reference and options
* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Access shielded wallet context
* [RainbowKit Guide](/clients/typescript/react/wallet-guides/rainbowkit) -- Traditional wallet modal alternative
* [AppKit Guide](/clients/typescript/react/wallet-guides/appkit) -- WalletConnect modal alternative


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

## Prerequisites

```bash
npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query seismic-react seismic-viem
```

{% hint style="warning" %}
You need a WalletConnect Project ID from [WalletConnect Cloud](https://cloud.walletconnect.com/).
{% endhint %}

## Step 1: Configure wagmi with AppKit

Create a wagmi adapter and initialize AppKit with Seismic chains:

```typescript
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"],
  },
});
```

## Step 2: Set Up Providers

Nest the providers with `ShieldedWalletProvider` inside:

```typescript
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>
  )
}
```

{% hint style="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`.
{% endhint %}

## Step 3: Add the Connect Button

AppKit provides a web component for the connect button:

```typescript
function Header() {
  return (
    <header>
      <appkit-button />
    </header>
  )
}
```

{% hint style="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:

```typescript
declare namespace JSX {
  interface IntrinsicElements {
    "appkit-button": React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement>,
      HTMLElement
    >;
  }
}
```

{% endhint %}

## Step 4: Use Shielded Hooks

Once connected, use `seismic-react` hooks to interact with shielded contracts:

```typescript
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>
}
```

## Complete Example

```typescript
'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>
  )
}
```

## See Also

* [Wallet Guides Overview](/clients/typescript/react/wallet-guides) -- Comparison of wallet libraries
* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) -- Provider reference and options
* [useShieldedWallet](/clients/typescript/react/hooks/useshieldedwallet) -- Access shielded wallet context
* [RainbowKit Guide](/clients/typescript/react/wallet-guides/rainbowkit) -- Polished wallet modal alternative
* [Privy Guide](/clients/typescript/react/wallet-guides/privy) -- Email/social login alternative


# 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`](/clients/typescript/viem) chain definition with RainbowKit metadata (icon, etc.).

## Import

```typescript
import {
  seismicTestnet,
  sanvil,
  localSeismicDevnet,
  createSeismicDevnet,
} from "seismic-react/rainbowkit";
```

## Available Chains

| Chain                                                                   | Export                  | Chain ID | Description                      |
| ----------------------------------------------------------------------- | ----------------------- | -------- | -------------------------------- |
| [Seismic Testnet](/clients/typescript/react/chains/seismic-testnet)     | `seismicTestnet`        | `5124`   | Public testnet                   |
| [Sanvil](/clients/typescript/react/chains/sanvil)                       | `sanvil`                | `31337`  | Local Sanvil dev node            |
| [Local Devnet](/clients/typescript/react/chains/sanvil)                 | `localSeismicDevnet`    | `5124`   | Local seismic-reth `--dev`       |
| [Custom Devnet](/clients/typescript/react/chains/create-seismic-devnet) | `createSeismicDevnet()` | `5124`   | Factory for custom chain configs |

## Usage with RainbowKit

```typescript
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";

const config = getDefaultConfig({
  appName: "My App",
  projectId: "YOUR_PROJECT_ID",
  chains: [seismicTestnet],
});
```

## Choosing a Chain

```
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()
```

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

{% hint style="info" %}
If you are not using RainbowKit, you can use the `seismic-viem` chain objects directly with wagmi.
{% endhint %}

## Pages

| Page                                                                          | Description                                      |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [Seismic Testnet](/clients/typescript/react/chains/seismic-testnet)           | Public testnet configuration and usage           |
| [Sanvil](/clients/typescript/react/chains/sanvil)                             | Local development chains (Sanvil + seismic-reth) |
| [createSeismicDevnet](/clients/typescript/react/chains/create-seismic-devnet) | Factory for custom chain configurations          |

## See Also

* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) - Provider that accepts chain config
* [Wallet Guides](/clients/typescript/react/wallet-guides) - RainbowKit and wallet setup guides
* [Installation](/clients/typescript/react/installation) - Package setup


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

## Configuration

| Property        | Value                                   |
| --------------- | --------------------------------------- |
| Chain ID        | `5124`                                  |
| Name            | `Seismic`                               |
| RPC (HTTPS)     | `https://testnet-1.seismictest.net/rpc` |
| RPC (WSS)       | `wss://testnet-1.seismictest.net/ws`    |
| Explorer        | `https://seismic-testnet.socialscan.io` |
| Native Currency | ETH (18 decimals)                       |

## Import

```typescript
import { seismicTestnet } from "seismic-react/rainbowkit";
```

## Usage

### With RainbowKit

```typescript
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { seismicTestnet } from "seismic-react/rainbowkit";

const config = getDefaultConfig({
  appName: "My App",
  projectId: "YOUR_PROJECT_ID",
  chains: [seismicTestnet],
});
```

### With wagmi Config

```typescript
import { http, createConfig } from "wagmi";
import { seismicTestnet } from "seismic-react/rainbowkit";

const config = createConfig({
  chains: [seismicTestnet],
  transports: {
    [seismicTestnet.id]: http(),
  },
});
```

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

## See Also

* [Chains Overview](/clients/typescript/react/chains) - All supported chains
* [Sanvil](/clients/typescript/react/chains/sanvil) - Local development chains
* [createSeismicDevnet](/clients/typescript/react/chains/create-seismic-devnet) - Custom chain factory
* [Wallet Guides](/clients/typescript/react/wallet-guides) - RainbowKit setup guides


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

## Configuration

| Property        | Value                   |
| --------------- | ----------------------- |
| Chain ID        | `31337`                 |
| Name            | `Sanvil`                |
| RPC (HTTP)      | `http://127.0.0.1:8545` |
| Native Currency | ETH (18 decimals)       |

## Import

```typescript
import { sanvil } from "seismic-react/rainbowkit";
```

## Usage

### With RainbowKit

```typescript
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { sanvil } from "seismic-react/rainbowkit";

const config = getDefaultConfig({
  appName: "My App",
  projectId: "YOUR_PROJECT_ID",
  chains: [sanvil],
});
```

### With wagmi Config

```typescript
import { http, createConfig } from "wagmi";
import { sanvil } from "seismic-react/rainbowkit";

const config = createConfig({
  chains: [sanvil],
  transports: {
    [sanvil.id]: http(),
  },
});
```

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

```typescript
import { localSeismicDevnet } from "seismic-react/rainbowkit";

const config = getDefaultConfig({
  appName: "My App",
  projectId: "YOUR_PROJECT_ID",
  chains: [localSeismicDevnet],
});
```

{% hint style="info" %}
Use `sanvil` for Sanvil (Seismic Anvil) instances. Use `localSeismicDevnet` for seismic-reth nodes running with the `--dev` flag.
{% endhint %}

## Installing Sanvil

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

```bash
curl -L https://raw.githubusercontent.com/SeismicSystems/seismic-foundry/seismic/sfoundryup/install | bash
sfoundryup
```

Then start a local node:

```bash
sanvil
```

By default, Sanvil:

* Listens on `127.0.0.1:8545`
* Uses chain ID `31337`
* Pre-funds test accounts with ETH
* Provides instant block mining

## See Also

* [Chains Overview](/clients/typescript/react/chains) - All supported chains
* [Seismic Testnet](/clients/typescript/react/chains/seismic-testnet) - Public testnet configuration
* [createSeismicDevnet](/clients/typescript/react/chains/create-seismic-devnet) - Custom chain factory
* [Wallet Guides](/clients/typescript/react/wallet-guides) - RainbowKit setup guides


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

## Import

```typescript
import { createSeismicDevnet } from "seismic-react/rainbowkit";
```

## Parameters

| Parameter     | Type     | Required | Description                                              |
| ------------- | -------- | -------- | -------------------------------------------------------- |
| `nodeHost`    | `string` | Yes      | Hostname for the node (e.g. `testnet-1.seismictest.net`) |
| `explorerUrl` | `string` | No       | Block explorer URL                                       |

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

## Usage

### Basic

```typescript
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
```

### With RainbowKit

```typescript
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],
});
```

### With wagmi Config

```typescript
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(),
  },
});
```

### With Explorer URL

```typescript
const myChain = createSeismicDevnet({
  nodeHost: "my-node.example.com",
  explorerUrl: "https://explorer.example.com",
});
```

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

## See Also

* [Chains Overview](/clients/typescript/react/chains) - All supported chains
* [Seismic Testnet](/clients/typescript/react/chains/seismic-testnet) - Public testnet configuration
* [Sanvil](/clients/typescript/react/chains/sanvil) - Local development chains
* [Wallet Guides](/clients/typescript/react/wallet-guides) - RainbowKit setup guides


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

## Available Examples

| Example                                                     | Description                                                                        |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| [Basic dApp](/clients/typescript/react/examples/basic-dapp) | Complete minimal dApp: provider setup, connect wallet, shielded write, signed read |

## Common Setup

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

```typescript
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>
  )
}
```

{% hint style="info" %}
`ShieldedWalletProvider` must be nested inside `WagmiProvider` and `QueryClientProvider`. It automatically creates shielded clients when a wallet connects.
{% endhint %}

## Prerequisites

* Node.js 18+
* A [WalletConnect](https://cloud.walletconnect.com/) project ID
* `seismic-react` and peer dependencies installed

```bash
npm install seismic-react seismic-viem wagmi viem @rainbow-me/rainbowkit @tanstack/react-query
```

## See Also

* [Hooks](/clients/typescript/react/hooks) - Hook API reference
* [Wallet Guides](/clients/typescript/react/wallet-guides) - RainbowKit, AppKit, and Privy integration
* [Installation](/clients/typescript/react/installation) - Full dependency setup


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

## What You'll Build

1. **Provider setup** with RainbowKit + Seismic
2. **Wallet connection** via RainbowKit's `ConnectButton`
3. **Shielded write** to a contract (encrypted calldata)
4. **Signed read** from a contract (authenticated query)

## Prerequisites

* Node.js 18+
* A [WalletConnect](https://cloud.walletconnect.com/) project ID

Install dependencies:

```bash
npm install seismic-react seismic-viem wagmi viem @rainbow-me/rainbowkit @tanstack/react-query
```

## Step 1: wagmi Config

Create the wagmi configuration with the Seismic testnet chain:

```typescript
// 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],
});
```

{% hint style="warning" %}
Replace `YOUR_WALLETCONNECT_PROJECT_ID` with your actual project ID from [WalletConnect Cloud](https://cloud.walletconnect.com/).
{% endhint %}

## Step 2: Provider Wrapper

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

```typescript
// 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>
  )
}
```

## Step 3: Contract Interaction Component

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

```typescript
// 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>
  )
}
```

{% hint style="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.
{% endhint %}

## Step 4: App Component

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

```typescript
// 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>
  )
}
```

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

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

## Next Steps

* [Hooks Reference](/clients/typescript/react/hooks) - Full API for all hooks
* [Wallet Guides](/clients/typescript/react/wallet-guides) - Use AppKit or Privy instead of RainbowKit
* [Chains](/clients/typescript/react/chains) - Configure Seismic testnet or local Sanvil

## See Also

* [ShieldedWalletProvider](/clients/typescript/react/shielded-wallet-provider) - Provider configuration options
* [useShieldedWriteContract](/clients/typescript/react/hooks/useshieldedwrite) - Shielded write hook API
* [useSignedReadContract](/clients/typescript/react/hooks/useshieldedread) - Signed read hook API
* [Installation](/clients/typescript/react/installation) - Full dependency setup


# Python — seismic-web3

Python SDK for Seismic, built on web3.py

Python SDK for [Seismic](https://seismic.systems), built on [web3.py](https://github.com/ethereum/web3.py). Requires **Python 3.10+**.

```bash
pip install seismic-web3
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv add seismic-web3
```

## Quick Example

```python
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()
```

```python
# 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()
```

## Documentation Navigation

### Getting Started

| Section                                  | Description                                    |
| ---------------------------------------- | ---------------------------------------------- |
| [**Client**](/clients/python/client)     | Create sync/async wallet and public clients    |
| [**Chains**](/clients/python/chains)     | Chain configuration (SEISMIC\_TESTNET, SANVIL) |
| [**Contract**](/clients/python/contract) | Interact with shielded and public contracts    |

### Guides

| Section                              | Description                                  |
| ------------------------------------ | -------------------------------------------- |
| [**Guides**](/clients/python/guides) | Step-by-step tutorials and runnable examples |

### API Reference

| Section                                                                  | Description                                            |
| ------------------------------------------------------------------------ | ------------------------------------------------------ |
| [**API Reference**](/clients/python/api-reference)                       | Complete API documentation for all types and functions |
| [**Types**](/clients/python/api-reference/types)                         | Primitive types (Bytes32, PrivateKey, etc.)            |
| [**Transaction Types**](/clients/python/api-reference/transaction-types) | Seismic transaction dataclasses                        |
| [**EIP-712**](/clients/python/api-reference/eip712)                      | EIP-712 typed data signing functions                   |

### Advanced Features

| Section                                        | Description                                |
| ---------------------------------------------- | ------------------------------------------ |
| [**Namespaces**](/clients/python/namespaces)   | w3.seismic namespace methods               |
| [**Precompiles**](/clients/python/precompiles) | Privacy-preserving cryptographic functions |
| [**SRC20**](/clients/python/src20)             | SRC20 token standard support               |
| [**ABIs**](/clients/python/abis)               | Built-in contract ABIs and helpers         |

## Quick Links

### By Task

* **Send a shielded transaction** → [Shielded Write Guide](/clients/python/guides/shielded-write)
* **Execute a signed read** → [Signed Reads Guide](/clients/python/guides/signed-reads)
* **Work with SRC20 tokens** → [SRC20 Documentation](/clients/python/src20)
* **Use async client** → [Client Documentation](/clients/python/client/create-async-wallet-client)
* **Configure custom chain** → [Chains Documentation](/clients/python/chains/chain-config)

### By Component

* **Client factories** → [create\_wallet\_client](/clients/python/client/create-wallet-client), [create\_public\_client](/clients/python/client/create-public-client)
* **Contract wrappers** → [ShieldedContract](/clients/python/contract/shielded-contract), [PublicContract](/clients/python/contract/public-contract)
* **Encryption** → [EncryptionState](/clients/python/client/encryption-state), [Precompiles](/clients/python/precompiles)
* **Transaction types** → [UnsignedSeismicTx](/clients/python/api-reference/transaction-types/unsigned-seismic-tx), [SeismicElements](/clients/python/api-reference/transaction-types/seismic-elements)

## Features

* **🔒 Shielded Transactions** - Encrypt calldata with TEE public key
* **📝 Signed Reads** - Prove identity in eth\_call
* **🪙 SRC20 Support** - Built-in support for private tokens
* **⚡ 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

## Architecture

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

```
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)
```

## Next Steps

1. [**Install and setup a client**](/clients/python/client) - Get connected to Seismic
2. [**Send your first shielded transaction**](/clients/python/guides/shielded-write) - Step-by-step guide
3. [**Explore the API reference**](/clients/python/api-reference) - 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**](/clients/python/client/create-wallet-client) — Full capabilities (shielded writes, signed reads, deposits). Requires a private key.
* [**Public client**](/clients/python/client/create-public-client) — Read-only (transparent reads, TEE public key, deposit queries). No private key needed.

Both clients are available in **sync** and **async** variants.

## Quick Start

### Installation

```bash
pip install seismic-web3
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv add seismic-web3
```

### Wallet Client (Sync)

```python
import os
from seismic_web3 import SEISMIC_TESTNET, PrivateKey

pk = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = SEISMIC_TESTNET.wallet_client(pk)
```

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.

### Wallet Client (Async)

```python
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)
```

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

### Public Client

```python
from seismic_web3 import SEISMIC_TESTNET

# Sync
public = SEISMIC_TESTNET.public_client()

# Async
public = SEISMIC_TESTNET.async_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).

## Client Factory Functions

### Wallet Clients (Require Private Key)

| Function                                                                           | Type  | Description                             |
| ---------------------------------------------------------------------------------- | ----- | --------------------------------------- |
| [create\_wallet\_client](/clients/python/client/create-wallet-client)              | Sync  | Create sync wallet client from RPC URL  |
| [create\_async\_wallet\_client](/clients/python/client/create-async-wallet-client) | Async | Create async wallet client from RPC URL |

### Public Clients (No Private Key)

| Function                                                                           | Type  | Description                             |
| ---------------------------------------------------------------------------------- | ----- | --------------------------------------- |
| [create\_public\_client](/clients/python/client/create-public-client)              | Sync  | Create sync public client from RPC URL  |
| [create\_async\_public\_client](/clients/python/client/create-async-public-client) | Async | Create async public client from RPC URL |

## Chain-Based Creation

The recommended approach is to use chain configuration objects:

```python
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)
```

See [Chains Configuration](/clients/python/chains) for more details.

## Encryption

The wallet client automatically handles encryption setup:

1. Fetches the network's TEE public key
2. Derives a shared [AES-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) key via [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman)
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.

### Encryption Components

| Component                                                  | Description                                  |
| ---------------------------------------------------------- | -------------------------------------------- |
| [EncryptionState](/clients/python/client/encryption-state) | Holds AES key and encryption keypair         |
| [get\_encryption](/clients/python/client/get-encryption)   | Derives encryption state from TEE public key |

## Client Capabilities

### Wallet Client (`w3.seismic`)

* [`send_shielded_transaction()`](/clients/python/namespaces/methods/send-shielded-transaction) - Send shielded transactions
* [`debug_send_shielded_transaction()`](/clients/python/namespaces/methods/debug-send-shielded-transaction) - Debug shielded transactions
* [`signed_call()`](/clients/python/namespaces/methods/signed-call) - Execute signed reads
* [`get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key) - Get TEE public key
* [`deposit()`](/clients/python/namespaces/methods/deposit) - Deposit ETH/tokens
* [`get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root) - Query deposit merkle root
* [`get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count) - Query deposit count
* [`contract()`](/clients/python/contract) - Create contract wrappers

### Public Client (`w3.seismic`)

* [`get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key) - Get TEE public key
* [`get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root) - Query deposit merkle root
* [`get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count) - Query deposit count
* [`contract()`](/clients/python/contract) - Create contract wrappers (`.tread` only)

## See Also

* [Contract Instances](/clients/python/contract) - Working with shielded and public contracts
* [Namespaces](/clients/python/namespaces) - Detailed `w3.seismic` namespace documentation
* [Chains Configuration](/clients/python/chains) - Chain configs and constants
* [Shielded Write Guide](/clients/python/guides/shielded-write) - Step-by-step shielded transaction guide


# create\_wallet\_client

Create sync Web3 instance with full Seismic wallet capabilities

Create a synchronous `Web3` instance with full Seismic wallet capabilities.

## 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 [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman), and attaches a fully-configured [`w3.seismic`](/clients/python/namespaces/seismic-namespace) namespace to a standard `Web3` 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.

## Signature

```python
def create_wallet_client(
    rpc_url: str,
    private_key: PrivateKey,
    *,
    encryption_sk: PrivateKey | None = None,
) -> Web3
```

## Parameters

| Parameter       | Type                                                            | Required | Description                                                                                                                          |
| --------------- | --------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `rpc_url`       | `str`                                                           | Yes      | HTTP(S) URL of the Seismic node (e.g., `"https://testnet-1.seismictest.net/rpc"`). WebSocket URLs are not supported — see note below |
| `private_key`   | [`PrivateKey`](/clients/python/api-reference/types/private-key) | Yes      | 32-byte secp256k1 private key for signing transactions                                                                               |
| `encryption_sk` | [`PrivateKey`](/clients/python/api-reference/types/private-key) | No       | Optional 32-byte key for ECDH. If `None`, a random ephemeral key is generated                                                        |

## Returns

| Type   | Description                                                                                                                 |
| ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `Web3` | A `Web3` instance with `w3.seismic` namespace attached ([`SeismicNamespace`](/clients/python/namespaces/seismic-namespace)) |

## Examples

### Basic Usage

```python
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://testnet-1.seismictest.net/rpc",
    private_key=private_key,
)

# Now use w3.seismic for Seismic operations
contract = w3.seismic.contract(address, abi)
tx_hash = contract.swrite.transfer(recipient, 1000)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
```

### Using Chain Configuration

```python
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)
```

### With Custom Encryption Key

```python
import os
from seismic_web3 import create_wallet_client, PrivateKey

signing_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
encryption_key = PrivateKey(os.urandom(32))  # Custom encryption keypair

w3 = create_wallet_client(
    "https://testnet-1.seismictest.net/rpc",
    private_key=signing_key,
    encryption_sk=encryption_key,
)
```

### Standard Web3 Operations

```python
import os
from seismic_web3 import create_wallet_client, PrivateKey

private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = create_wallet_client("https://testnet-1.seismictest.net/rpc", private_key=private_key)

# All standard web3.py operations work
block = w3.eth.get_block("latest")
balance = w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
chain_id = w3.eth.chain_id
```

## How It Works

The function performs five steps:

1. **Create Web3 instance**

   ```python
   w3 = Web3(Web3.HTTPProvider(rpc_url))
   ```
2. **Fetch TEE public key** (synchronous RPC call)

   ```python
   network_pk = get_tee_public_key(w3)
   ```
3. **Generate encryption keypair** (if `encryption_sk` is `None`, a random ephemeral key is created)

   ```python
   encryption_sk = encryption_sk or PrivateKey(os.urandom(32))
   ```
4. **Derive encryption state** (ECDH + [HKDF](https://en.wikipedia.org/wiki/HKDF))

   ```python
   encryption = get_encryption(network_pk, encryption_sk)
   ```
5. **Attach Seismic namespace**

   ```python
   w3.seismic = SeismicNamespace(w3, encryption, private_key)
   ```

## Client Capabilities

The returned client provides:

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

### Seismic Methods (`w3.seismic`)

* [`send_shielded_transaction()`](/clients/python/namespaces/methods/send-shielded-transaction) - Send shielded transactions
* [`debug_send_shielded_transaction()`](/clients/python/namespaces/methods/debug-send-shielded-transaction) - Debug shielded transactions
* [`signed_call()`](/clients/python/namespaces/methods/signed-call) - Execute signed reads
* [`deposit()`](/clients/python/namespaces/methods/deposit) - Deposit ETH/tokens
* [`get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key) - Get TEE public key
* [`get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root) - Query deposit merkle root
* [`get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count) - Query deposit count
* [`contract()`](/clients/python/contract) - Create contract wrappers

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

## 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()`](/clients/python/client/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 [`create_async_wallet_client()`](/clients/python/client/create-async-wallet-client)

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

## See Also

* [create\_async\_wallet\_client](/clients/python/client/create-async-wallet-client) - Async variant with WebSocket support
* [create\_public\_client](/clients/python/client/create-public-client) - Read-only client without private key
* [EncryptionState](/clients/python/client/encryption-state) - Encryption state class
* [get\_encryption](/clients/python/client/get-encryption) - Encryption derivation function
* [SeismicNamespace](/clients/python/namespaces/seismic-namespace) - The `w3.seismic` namespace
* [Chains Configuration](/clients/python/chains) - Pre-configured chain constants


# create\_async\_wallet\_client

Create async Web3 instance with full Seismic wallet capabilities

Create an asynchronous `AsyncWeb3` instance with full Seismic wallet capabilities.

## 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 [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman), and attaches a fully-configured [`w3.seismic`](/clients/python/namespaces/async-seismic-namespace) 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.

## Signature

```python
async def create_async_wallet_client(
    provider_url: str,
    private_key: PrivateKey,
    *,
    encryption_sk: PrivateKey | None = None,
    ws: bool = False,
) -> AsyncWeb3
```

## Parameters

| Parameter       | Type                                                            | Required | Description                                                                                                                                                                                                          |
| --------------- | --------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_url`  | `str`                                                           | Yes      | HTTP(S) or WS(S) URL of the Seismic node                                                                                                                                                                             |
| `private_key`   | [`PrivateKey`](/clients/python/api-reference/types/private-key) | Yes      | 32-byte secp256k1 private key for signing transactions                                                                                                                                                               |
| `encryption_sk` | [`PrivateKey`](/clients/python/api-reference/types/private-key) | No       | Optional 32-byte key for ECDH. If `None`, a random ephemeral key is generated                                                                                                                                        |
| `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 |

## Returns

| Type        | Description                                                                                                                                  |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `AsyncWeb3` | An `AsyncWeb3` instance with `w3.seismic` namespace attached ([`AsyncSeismicNamespace`](/clients/python/namespaces/async-seismic-namespace)) |

## Examples

### Basic Usage (HTTP)

```python
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://testnet-1.seismictest.net/rpc",
    private_key=private_key,
)

# Now use w3.seismic for Seismic operations
contract = w3.seismic.contract(address, abi)
tx_hash = await contract.swrite.transfer(recipient, 1000)
receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
```

### WebSocket Connection

```python
import os
from seismic_web3 import create_async_wallet_client, PrivateKey

private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])

# WebSocket provider for persistent connection
w3 = await create_async_wallet_client(
    "wss://testnet-1.seismictest.net/ws",
    private_key=private_key,
    ws=True,
)

# Subscribe to new blocks
async for block in w3.eth.subscribe("newHeads"):
    print(f"New block: {block['number']}")
```

### Using Chain Configuration

```python
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)
```

### Context Manager Pattern

```python
import os
from seismic_web3 import create_async_wallet_client, PrivateKey

private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])

# Use context manager to ensure cleanup
async with create_async_wallet_client(
    "wss://testnet-1.seismictest.net/ws",
    private_key=private_key,
    ws=True,
) as w3:
    contract = w3.seismic.contract(address, abi)
    tx_hash = await contract.swrite.transfer(recipient, 1000)
    receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
```

### Async Application

```python
import asyncio
import os
from seismic_web3 import create_async_wallet_client, PrivateKey

async def main():
    private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])

    w3 = await create_async_wallet_client(
        "https://testnet-1.seismictest.net/rpc",
        private_key=private_key,
    )

    # Get current block
    block = await w3.eth.get_block("latest")
    print(f"Latest block: {block['number']}")

    # Get balance
    address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
    balance = await w3.eth.get_balance(address)
    print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")

asyncio.run(main())
```

### With Custom Encryption Key

```python
import os
from seismic_web3 import create_async_wallet_client, PrivateKey

async def setup_client():
    signing_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
    encryption_key = PrivateKey(os.urandom(32))  # Custom encryption keypair

    w3 = await create_async_wallet_client(
        "https://testnet-1.seismictest.net/rpc",
        private_key=signing_key,
        encryption_sk=encryption_key,
    )

    return w3
```

## How It Works

The function performs six steps:

1. **Create provider**

   ```python
   if ws:
       provider = WebSocketProvider(provider_url)
   else:
       provider = AsyncHTTPProvider(provider_url)
   ```
2. **Create AsyncWeb3 instance**

   ```python
   w3 = AsyncWeb3(provider)
   ```
3. **Fetch TEE public key** (async RPC call)

   ```python
   network_pk = await async_get_tee_public_key(w3)
   ```
4. **Generate encryption keypair** (if `encryption_sk` is `None`, a random ephemeral key is created)

   ```python
   encryption_sk = encryption_sk or PrivateKey(os.urandom(32))
   ```
5. **Derive encryption state** (ECDH + [HKDF](https://en.wikipedia.org/wiki/HKDF))

   ```python
   encryption = get_encryption(network_pk, encryption_sk)
   ```
6. **Attach Seismic namespace**

   ```python
   w3.seismic = AsyncSeismicNamespace(w3, encryption, private_key)
   ```

## Client Capabilities

The returned client provides:

### 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()`
* All other standard async `web3.py` functionality

### Async Seismic Methods (`w3.seismic`)

* [`await send_shielded_transaction()`](/clients/python/namespaces/methods/send-shielded-transaction) - Send shielded transactions
* [`await debug_send_shielded_transaction()`](/clients/python/namespaces/methods/debug-send-shielded-transaction) - Debug shielded transactions
* [`await signed_call()`](/clients/python/namespaces/methods/signed-call) - Execute signed reads
* [`await deposit()`](/clients/python/namespaces/methods/deposit) - Deposit ETH/tokens
* [`await get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key) - Get TEE public key
* [`await get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root) - Query deposit merkle root
* [`await get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count) - Query deposit count
* [`contract()`](/clients/python/contract) - Create contract wrappers (methods are async)

## HTTP vs WebSocket

| 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               |
| **Use case**       | One-off transactions           | Real-time monitoring, subscriptions |

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

## 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`, 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()`](/clients/python/client/create-wallet-client)

## 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
* **HTTPS/WSS recommended** - Use secure protocols in production to prevent MITM attacks

## See Also

* [create\_wallet\_client](/clients/python/client/create-wallet-client) - Sync variant (HTTP only)
* [create\_async\_public\_client](/clients/python/client/create-async-public-client) - Async read-only client
* [EncryptionState](/clients/python/client/encryption-state) - Encryption state class
* [get\_encryption](/clients/python/client/get-encryption) - Encryption derivation function
* [AsyncSeismicNamespace](/clients/python/namespaces/async-seismic-namespace) - The async `w3.seismic` namespace
* [Chains Configuration](/clients/python/chains) - Pre-configured chain constants


# create\_public\_client

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

Create a synchronous `Web3` instance with public (read-only) Seismic access.

## Overview

`create_public_client()` creates a client for read-only operations on the Seismic network. No private key is required. The [`w3.seismic`](/clients/python/namespaces/seismic-public-namespace) namespace provides only public read operations: [`get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key), [`get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root), [`get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count), and [`contract()`](/clients/python/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.

## Signature

```python
def create_public_client(rpc_url: str) -> Web3
```

## Parameters

| Parameter | Type  | Required | Description                                                                                                                          |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `rpc_url` | `str` | Yes      | HTTP(S) URL of the Seismic node (e.g., `"https://testnet-1.seismictest.net/rpc"`). WebSocket URLs are not supported — see note below |

## Returns

| Type   | Description                                                                                                                              |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `Web3` | A `Web3` instance with `w3.seismic` namespace attached ([`SeismicPublicNamespace`](/clients/python/namespaces/seismic-public-namespace)) |

## Examples

### Basic Usage

```python
from seismic_web3 import create_public_client

# Create public client
w3 = create_public_client("https://testnet-1.seismictest.net/rpc")

# Query TEE public key
tee_pk = w3.seismic.get_tee_public_key()
print(f"TEE public key: {tee_pk.to_0x_hex()}")

# Query deposit info
root = w3.seismic.get_deposit_root()
count = w3.seismic.get_deposit_count()
print(f"Deposit root: {root.to_0x_hex()}, count: {count}")
```

### Using Chain Configuration

```python
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)
```

### Read-Only Contract Access

```python
from seismic_web3 import create_public_client

w3 = create_public_client("https://testnet-1.seismictest.net/rpc")

# Create contract wrapper (read-only)
contract = w3.seismic.contract(
    address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    abi=contract_abi,
)

# Only transparent reads are available
result = contract.tread.balanceOf("0x1234...")

# Shielded operations are NOT available
# contract.swrite.transfer(...)  # AttributeError: no swrite on public client
# contract.sread.getBalance(...)  # AttributeError: no sread on public client
```

### Standard Web3 Operations

```python
from seismic_web3 import create_public_client

w3 = create_public_client("https://testnet-1.seismictest.net/rpc")

# All standard web3.py read operations work
block = w3.eth.get_block("latest")
print(f"Latest block: {block['number']}")

balance = w3.eth.get_balance("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")

chain_id = w3.eth.chain_id
print(f"Chain ID: {chain_id}")
```

### Block Explorer Pattern

```python
from seismic_web3 import create_public_client

def get_chain_stats(rpc_url: str):
    w3 = create_public_client(rpc_url)

    # Get latest block
    block = w3.eth.get_block("latest")

    # Get deposit info
    deposit_root = w3.seismic.get_deposit_root()
    deposit_count = w3.seismic.get_deposit_count()

    # Get TEE info
    tee_pk = w3.seismic.get_tee_public_key()

    return {
        "block_number": block["number"],
        "block_hash": block["hash"].to_0x_hex(),
        "deposit_root": deposit_root.to_0x_hex(),
        "deposit_count": deposit_count,
        "tee_public_key": tee_pk.to_0x_hex(),
    }

stats = get_chain_stats("https://testnet-1.seismictest.net/rpc")
print(stats)
```

## How It Works

The function performs two steps:

1. **Create Web3 instance**

   ```python
   w3 = Web3(Web3.HTTPProvider(rpc_url))
   ```
2. **Attach public Seismic namespace**

   ```python
   w3.seismic = SeismicPublicNamespace(w3)
   ```

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

## Client Capabilities

### Standard Web3 Methods (e.g. `w3.eth`, `w3.net`)

* `get_block()`, `get_transaction()`, `get_balance()`
* `get_code()`, `call()`, `estimate_gas()`
* All other standard read-only `web3.py` functionality

### Public Seismic Methods (`w3.seismic`)

* [`get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key) - Get TEE public key
* [`get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root) - Query deposit merkle root
* [`get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count) - Query deposit count
* [`contract()`](/clients/python/contract) - Create contract wrappers (`.tread` only)

### NOT Available

* [`send_shielded_transaction()`](/clients/python/namespaces/methods/send-shielded-transaction) - Requires private key
* [`debug_send_shielded_transaction()`](/clients/python/namespaces/methods/debug-send-shielded-transaction) - Requires private key
* [`signed_call()`](/clients/python/namespaces/methods/signed-call) - Requires private key and encryption
* [`deposit()`](/clients/python/namespaces/methods/deposit) - Requires private key
* Contract `.swrite` and `.sread` methods - Require private key

## Public vs Wallet Client

| Feature               | Public Client       | Wallet Client  |
| --------------------- | ------------------- | -------------- |
| **Private key**       | Not required        | Required       |
| **Shielded writes**   | No                  | Yes            |
| **Signed reads**      | No                  | Yes            |
| **Transparent reads** | Yes                 | Yes            |
| **Deposits**          | No                  | Yes            |
| **TEE queries**       | Yes                 | Yes            |
| **Standard Web3**     | All read operations | All operations |

## 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_public_client()`](/clients/python/client/create-async-public-client) with `ws=True`
* No private key required or accepted
* 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()`](/clients/python/client/create-wallet-client)
* For async operations, use [`create_async_public_client()`](/clients/python/client/create-async-public-client)

## Use Cases

* Block explorers and chain analytics
* Read-only dApps that display public data
* Monitoring and alerting systems
* Price oracles and data aggregators
* Public dashboards and visualizations
* Testing and validation tools

## See Also

* [create\_async\_public\_client](/clients/python/client/create-async-public-client) - Async variant with WebSocket support
* [create\_wallet\_client](/clients/python/client/create-wallet-client) - Full-featured client with private key
* [SeismicPublicNamespace](/clients/python/namespaces/seismic-public-namespace) - The public `w3.seismic` namespace
* [Chains Configuration](/clients/python/chains) - Pre-configured chain constants
* [Contract Instances](/clients/python/contract) - 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.

## 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`](/clients/python/namespaces/async-seismic-public-namespace) namespace provides only public read operations: [`get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key), [`get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root), [`get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count), and [`contract()`](/clients/python/contract) (with `.tread` only).

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

## Signature

```python
def create_async_public_client(
    provider_url: str,
    *,
    ws: bool = False,
) -> AsyncWeb3
```

## Parameters

| Parameter      | Type   | Required | Description                                                                                                                                                                                                          |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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 |

## Returns

| Type        | Description                                                                                                                                               |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AsyncWeb3` | An `AsyncWeb3` instance with `w3.seismic` namespace attached ([`AsyncSeismicPublicNamespace`](/clients/python/namespaces/async-seismic-public-namespace)) |

## Examples

### Basic Usage (HTTP)

```python
from seismic_web3 import create_async_public_client

# Create async public client
w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")

# Query TEE public key
tee_pk = await w3.seismic.get_tee_public_key()
print(f"TEE public key: {tee_pk.to_0x_hex()}")

# Query deposit info
root = await w3.seismic.get_deposit_root()
count = await w3.seismic.get_deposit_count()
print(f"Deposit root: {root.to_0x_hex()}, count: {count}")
```

### WebSocket Connection

```python
from seismic_web3 import create_async_public_client

# WebSocket provider for persistent connection
w3 = create_async_public_client(
    "wss://testnet-1.seismictest.net/ws",
    ws=True,
)

# Subscribe to new blocks
async for block in w3.eth.subscribe("newHeads"):
    print(f"New block: {block['number']}")

    # Query deposit count at each new block
    count = await w3.seismic.get_deposit_count()
    print(f"Current deposit count: {count}")
```

### Using Chain Configuration

```python
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)
```

### Async Application

```python
import asyncio
from seismic_web3 import create_async_public_client

async def main():
    w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")

    # Get current block
    block = await w3.eth.get_block("latest")
    print(f"Latest block: {block['number']}")

    # Get balance
    address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
    balance = await w3.eth.get_balance(address)
    print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")

    # Query deposit info
    deposit_count = await w3.seismic.get_deposit_count()
    print(f"Total deposits: {deposit_count}")

asyncio.run(main())
```

### Read-Only Contract Access

```python
from seismic_web3 import create_async_public_client

async def query_contract():
    w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")

    # Create contract wrapper (read-only)
    contract = w3.seismic.contract(
        address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        abi=contract_abi,
    )

    # Only transparent reads are available
    result = await contract.tread.balanceOf("0x1234...")
    print(f"Balance: {result}")
```

### Monitoring Pattern

```python
from seismic_web3 import create_async_public_client
import asyncio

async def monitor_deposits():
    w3 = create_async_public_client("wss://testnet-1.seismictest.net/ws", ws=True)

    last_count = await w3.seismic.get_deposit_count()
    print(f"Starting deposit count: {last_count}")

    async for block in w3.eth.subscribe("newHeads"):
        current_count = await w3.seismic.get_deposit_count()

        if current_count > last_count:
            print(f"New deposits detected! Count: {current_count}")
            print(f"Block: {block['number']}")
            last_count = current_count

asyncio.run(monitor_deposits())
```

### Parallel Queries

```python
from seismic_web3 import create_async_public_client
import asyncio

async def get_chain_stats():
    w3 = create_async_public_client("https://testnet-1.seismictest.net/rpc")

    # Run multiple queries in parallel
    block, tee_pk, deposit_root, deposit_count = await asyncio.gather(
        w3.eth.get_block("latest"),
        w3.seismic.get_tee_public_key(),
        w3.seismic.get_deposit_root(),
        w3.seismic.get_deposit_count(),
    )

    return {
        "block_number": block["number"],
        "tee_public_key": tee_pk.to_0x_hex(),
        "deposit_root": deposit_root.to_0x_hex(),
        "deposit_count": deposit_count,
    }
```

### Context Manager Pattern

```python
from seismic_web3 import create_async_public_client

async with create_async_public_client(
    "wss://testnet-1.seismictest.net/ws",
    ws=True,
) as w3:
    # WebSocket connection will be properly closed
    block = await w3.eth.get_block("latest")
    print(f"Block: {block['number']}")
```

## How It Works

The function performs three steps:

1. **Create provider**

   ```python
   if ws:
       provider = WebSocketProvider(provider_url)
   else:
       provider = AsyncHTTPProvider(provider_url)
   ```
2. **Create AsyncWeb3 instance**

   ```python
   w3 = AsyncWeb3(provider)
   ```
3. **Attach public Seismic namespace**

   ```python
   w3.seismic = AsyncSeismicPublicNamespace(w3)
   ```

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

## Client Capabilities

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

* `await get_block()`, `await get_transaction()`, `await get_balance()`
* `await call()`, `await estimate_gas()`
* All other standard read-only async `web3.py` functionality

### Public Seismic Methods (`w3.seismic`)

* [`await get_tee_public_key()`](/clients/python/namespaces/methods/get-tee-public-key) - Get TEE public key
* [`await get_deposit_root()`](/clients/python/namespaces/methods/get-deposit-root) - Query deposit merkle root
* [`await get_deposit_count()`](/clients/python/namespaces/methods/get-deposit-count) - Query deposit count
* [`contract()`](/clients/python/contract) - Create contract wrappers (`.tread` methods are async)

### NOT Available

* [`send_shielded_transaction()`](/clients/python/namespaces/methods/send-shielded-transaction) - Requires private key
* [`debug_send_shielded_transaction()`](/clients/python/namespaces/methods/debug-send-shielded-transaction) - Requires private key
* [`signed_call()`](/clients/python/namespaces/methods/signed-call) - Requires private key and encryption
* [`deposit()`](/clients/python/namespaces/methods/deposit) - Requires private key
* Contract `.swrite` and `.sread` methods - Require private key

## HTTP vs WebSocket

| 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               |
| **Use case**       | One-off queries                | Real-time monitoring, subscriptions |

## 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
* 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()`](/clients/python/client/create-async-wallet-client)
* For sync operations, use [`create_public_client()`](/clients/python/client/create-public-client)

## Use Cases

* Async block explorers and chain analytics
* Real-time monitoring dashboards with WebSocket subscriptions
* High-throughput read-only services
* Async data aggregation pipelines
* Event monitoring and alerting systems
* Price oracles with low-latency requirements

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

## See Also

* [create\_public\_client](/clients/python/client/create-public-client) - Sync variant (HTTP only)
* [create\_async\_wallet\_client](/clients/python/client/create-async-wallet-client) - Async client with private key
* [AsyncSeismicPublicNamespace](/clients/python/namespaces/async-seismic-public-namespace) - The async public `w3.seismic` namespace
* [Chains Configuration](/clients/python/chains) - Pre-configured chain constants
* [Contract Instances](/clients/python/contract) - Working with contract wrappers


# EncryptionState

Holds AES key and encryption keypair derived from ECDH

Holds the [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)-GCM key and encryption keypair derived from [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) key exchange.

## Overview

`EncryptionState` encapsulates all cryptographic material needed for shielded transactions and signed reads. It's created by [`get_encryption()`](/clients/python/client/get-encryption) during wallet client setup and attached to [`w3.seismic`](/clients/python/namespaces/seismic-namespace)`.encryption`.

The class provides [`encrypt()`](#encrypt) and [`decrypt()`](#decrypt) methods that handle AES-GCM encryption with metadata-bound Additional Authenticated Data (AAD).

## Definition

```python
@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
```

## Attributes

| Attribute                | Type                                                                               | Description                                                                        |
| ------------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `aes_key`                | [`Bytes32`](/clients/python/api-reference/types/bytes32)                           | 32-byte AES-256 key derived from ECDH + [HKDF](https://en.wikipedia.org/wiki/HKDF) |
| `encryption_pubkey`      | [`CompressedPublicKey`](/clients/python/api-reference/types/compressed-public-key) | Client's 33-byte compressed secp256k1 public key                                   |
| `encryption_private_key` | [`PrivateKey`](/clients/python/api-reference/types/private-key)                    | Client's 32-byte secp256k1 private key                                             |

## Methods

### encrypt()

Encrypt plaintext calldata with metadata-bound AAD.

#### Signature

```python
def encrypt(
    self,
    plaintext: HexBytes,
    nonce: EncryptionNonce,
    metadata: TxSeismicMetadata,
) -> HexBytes
```

#### Parameters

| Parameter   | Type                                                                                       | Description                              |
| ----------- | ------------------------------------------------------------------------------------------ | ---------------------------------------- |
| `plaintext` | `HexBytes`                                                                                 | Raw calldata to encrypt                  |
| `nonce`     | [`EncryptionNonce`](/clients/python/api-reference/types/encryption-nonce)                  | 12-byte AES-GCM nonce                    |
| `metadata`  | [`TxSeismicMetadata`](/clients/python/api-reference/transaction-types/tx-seismic-metadata) | Transaction metadata (used to build AAD) |

#### Returns

| Type       | Description                                         |
| ---------- | --------------------------------------------------- |
| `HexBytes` | Ciphertext with 16-byte authentication tag appended |

#### Example

```python
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
```

### decrypt()

Decrypt ciphertext with metadata-bound AAD.

#### Signature

```python
def decrypt(
    self,
    ciphertext: HexBytes,
    nonce: EncryptionNonce,
    metadata: TxSeismicMetadata,
) -> HexBytes
```

#### Parameters

| Parameter    | Type                                                                                       | Description                                |
| ------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------ |
| `ciphertext` | `HexBytes`                                                                                 | Encrypted data (includes 16-byte auth tag) |
| `nonce`      | [`EncryptionNonce`](/clients/python/api-reference/types/encryption-nonce)                  | 12-byte AES-GCM nonce                      |
| `metadata`   | [`TxSeismicMetadata`](/clients/python/api-reference/transaction-types/tx-seismic-metadata) | Transaction metadata (used to build AAD)   |

#### Returns

| Type       | Description         |
| ---------- | ------------------- |
| `HexBytes` | Decrypted plaintext |

#### Raises

* `cryptography.exceptions.InvalidTag` - If authentication fails (wrong key, tampered data, or mismatched metadata)

#### Example

```python
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")
```

## Examples

### Access from Client

```python
import os
from seismic_web3 import create_wallet_client, PrivateKey

private_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
w3 = create_wallet_client("https://testnet-1.seismictest.net/rpc", private_key=private_key)

# Access encryption state
encryption = w3.seismic.encryption

print(f"AES key: {encryption.aes_key.to_0x_hex()}")
print(f"Client pubkey: {encryption.encryption_pubkey.to_0x_hex()}")
```

### Manual Encryption Workflow

```python
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
```

### Custom Encryption Key

```python
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
```

### Verify Encryption/Decryption

```python
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")
```

## How It Works

### Initialization

When created, `EncryptionState` automatically initializes an internal `AesGcmCrypto` instance:

```python
def __post_init__(self) -> None:
    self._crypto = AesGcmCrypto(self.aes_key)
```

### Encryption

1. Encode metadata as AAD using [`encode_metadata_as_aad()`](/clients/python/api-reference/transaction-types/tx-seismic-metadata)
2. Call `AesGcmCrypto.encrypt(plaintext, nonce, aad)`
3. Return ciphertext with 16-byte authentication tag

### Decryption

1. Encode metadata as AAD
2. Call `AesGcmCrypto.decrypt(ciphertext, nonce, aad)`
3. Verify authentication tag (raises `InvalidTag` if fails)
4. Return plaintext

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

## Notes

* Pure computation - no I/O operations
* Works in both sync and async contexts
* Created automatically by [`create_wallet_client()`](/clients/python/client/create-wallet-client) and [`create_async_wallet_client()`](/clients/python/client/create-async-wallet-client)
* You rarely need to call [`encrypt()`](#encrypt) or [`decrypt()`](#decrypt) directly - the SDK handles this
* The internal `_crypto` field is excluded from `repr()` and comparison
* Authentication tag is always 16 bytes (AES-GCM standard)

## Security Considerations

* **Key derivation** - [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) key is derived from [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) + [HKDF](https://en.wikipedia.org/wiki/HKDF), 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

## See Also

* [get\_encryption](/clients/python/client/get-encryption) - Derive encryption state from TEE public key
* [create\_wallet\_client](/clients/python/client/create-wallet-client) - Sync client factory (creates EncryptionState)
* [create\_async\_wallet\_client](/clients/python/client/create-async-wallet-client) - Async client factory
* [EncryptionNonce](/clients/python/api-reference/types/encryption-nonce) - 12-byte nonce type
* [TxSeismicMetadata](/clients/python/api-reference/transaction-types/tx-seismic-metadata) - Metadata structure
* [Shielded Write Guide](/clients/python/guides/shielded-write) - How shielded transactions work


# get\_encryption

Derive encryption state from TEE public key using ECDH

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

## 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`](/clients/python/client/encryption-state) 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.

## Signature

```python
def get_encryption(
    network_pk: CompressedPublicKey,
    client_sk: PrivateKey | None = None,
) -> EncryptionState
```

## Parameters

| Parameter    | Type                                                                               | Required | Description                                                                         |
| ------------ | ---------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
| `network_pk` | [`CompressedPublicKey`](/clients/python/api-reference/types/compressed-public-key) | Yes      | The TEE's 33-byte compressed secp256k1 public key                                   |
| `client_sk`  | [`PrivateKey`](/clients/python/api-reference/types/private-key)                    | No       | Optional 32-byte client private key. If `None`, a random ephemeral key is generated |

## Returns

| Type                                                         | Description                                                 |
| ------------------------------------------------------------ | ----------------------------------------------------------- |
| [`EncryptionState`](/clients/python/client/encryption-state) | Fully initialized encryption state with AES key and keypair |

## Examples

### Basic Usage

```python
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()}")
```

### With Custom Client Key

```python
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)
```

### In Client Factory

```python
import os
from seismic_web3 import get_encryption, get_tee_public_key, PrivateKey
from web3 import Web3

# This is what create_wallet_client() does internally
w3 = Web3(Web3.HTTPProvider("https://testnet-1.seismictest.net/rpc"))

# Step 1: Fetch TEE public key
network_pk = get_tee_public_key(w3)

# Step 2: Derive encryption state
signing_key = PrivateKey.from_hex_str(os.environ["PRIVATE_KEY"])
encryption = get_encryption(network_pk, client_sk=None)  # Random ephemeral key

# Step 3: Attach to client
# w3.seismic = SeismicNamespace(w3, encryption, signing_key)
```

### Random Ephemeral Key

```python
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
```

### Verify Key Derivation

```python
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
```

## How It Works

The function performs three steps:

1. **Generate client key if needed**

   ```python
   if client_sk is None:
       client_sk = PrivateKey(os.urandom(32))
   ```
2. **Derive AES key via ECDH +** [**HKDF**](https://en.wikipedia.org/wiki/HKDF)

   ```python
   aes_key = generate_aes_key(client_sk, network_pk)
   ```

   This performs:

   * ECDH: Compute shared secret from `client_sk` and `network_pk`
   * HKDF: Derive 32-byte AES key from shared secret
3. **Derive client public key**

   ```python
   client_pubkey = private_key_to_compressed_public_key(client_sk)
   ```
4. **Return EncryptionState**

   ```python
   return EncryptionState(
       aes_key=aes_key,
       encryption_pubkey=client_pubkey,
       encryption_private_key=client_sk,
   )
   ```

## ECDH Key Exchange

The ECDH key exchange works as follows:

```
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)
```

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

## Random vs Deterministic Keys

| Key Type             | Pros                                                          | Cons                                                   |
| -------------------- | ------------------------------------------------------------- | ------------------------------------------------------ |
| **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 |

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

## 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)`
* The AES key is derived using ECDH + HKDF (NIST SP 800-56C)
* Called automatically by [`create_wallet_client()`](/clients/python/client/create-wallet-client) and [`create_async_wallet_client()`](/clients/python/client/create-async-wallet-client)
* You rarely need to call this directly unless implementing custom client logic

## 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` securely (encrypted at rest, never logged)
* **ECDH security** - Based on secp256k1 elliptic curve discrete logarithm problem
* **HKDF** - Uses SHA-256 for key derivation

## Common Patterns

### Ephemeral Session Keys

```python
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
```

### Persistent Keys

```python
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)
```

### Rotate Keys

```python
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)
```

## See Also

* [EncryptionState](/clients/python/client/encryption-state) - Returned encryption state class
* [create\_wallet\_client](/clients/python/client/create-wallet-client) - Sync client factory (calls get\_encryption)
* [create\_async\_wallet\_client](/clients/python/client/create-async-wallet-client) - Async client factory
* [PrivateKey](/clients/python/api-reference/types/private-key) - Client private key type
* [CompressedPublicKey](/clients/python/api-reference/types/compressed-public-key) - TEE public key type
* [Shielded Write Guide](/clients/python/guides/shielded-write) - How encryption is used in transactions


# Contract

Instantiating contracts and interacting through shielded and transparent namespaces

***

### Instantiation

```python
contract = w3.seismic.contract(address="0x...", abi=ABI)
```

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.

***

### Namespaces

`ShieldedContract` gives you five namespaces:

| Namespace | What it does                                                        | On-chain visibility         |
| --------- | ------------------------------------------------------------------- | --------------------------- |
| `.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`                                                 | Calldata plaintext          |
| `.dwrite` | Debug write — like `.write` but returns plaintext + encrypted views | Calldata encrypted          |

```python
# 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
```

Write namespaces accept optional keyword arguments for transaction parameters:

```python
tx_hash = contract.write.deposit(value=10**18, gas=100_000, gas_price=10**9)
```

***

### Example Contract

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

```solidity
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;
}
```

Token examples use the real [SRC20](https://github.com/SeismicSystems/src20) / ERC20 specs (`balanceOf`, `transfer`, `approve`, `allowance`, `totalSupply`, etc.).

***

### Encoding calldata manually

If you need to encode calldata outside of a contract call — for example, to pass it to the [low-level API](/clients/python/guides/shielded-write#low-level-api) — you can use [`encode_shielded_calldata`](https://github.com/SeismicSystems/seismic/blob/main/docs/gitbook/clients/python/namespaces/methods/encode-shielded-calldata.md). This computes the function selector using the original shielded type names (like `suint256`) but encodes the parameters using standard types (like `uint256`):

```python
from seismic_web3.contract.abi import encode_shielded_calldata

data = encode_shielded_calldata(abi, "setNumber", [42])
```


# ShieldedContract

Sync contract wrapper with shielded and transparent namespaces

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

## Overview

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

## Definition

```python
class ShieldedContract:
    def __init__(
        self,
        w3: Web3,
        encryption: EncryptionState,
        private_key: PrivateKey,
        address: ChecksumAddress,
        abi: list[dict[str, Any]],
        eip712: bool = False,
    ) -> None:
        ...
```

## Constructor Parameters

| Parameter     | Type                                                            | Required | Description                                            |
| ------------- | --------------------------------------------------------------- | -------- | ------------------------------------------------------ |
| `w3`          | `Web3`                                                          | Yes      | Synchronous Web3 instance connected to Seismic RPC     |
| `encryption`  | [`EncryptionState`](/clients/python/client/encryption-state)    | Yes      | Encryption state for shielded operations               |
| `private_key` | [`PrivateKey`](/clients/python/api-reference/types/private-key) | Yes      | 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`)      |

## Namespaces

### `.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)
* `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

### `.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`)
* `security: [`SeismicSecurityParams`](../api-reference/transaction-types/seismic-security-params.md) | None` - Security parameters for expiry

### `.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.)

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

### `.dwrite` - Debug Write

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

**Returns**: [`DebugWriteResult`](/clients/python/api-reference/transaction-types/debug-write-result)

**Positional Arguments**: `*args` - ABI function arguments

**Optional Parameters**: Same as `.write`

## Examples

### Basic Encrypted Write

```python
from seismic_web3 import create_wallet_client, ShieldedContract

w3 = create_wallet_client(
    rpc_url="https://testnet-1.seismictest.net/rpc",
    private_key=private_key,
)

contract = ShieldedContract(
    w3=w3,
    encryption=w3.seismic.encryption,
    private_key=private_key,
    address="0x1234567890123456789012345678901234567890",
    abi=CONTRACT_ABI,
)

# Encrypted write - calldata hidden on-chain
tx_hash = contract.write.setNumber(42)
print(f"Transaction: {tx_hash.to_0x_hex()}")

# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Status: {receipt['status']}")
```

### Encrypted Read

```python
# Encrypted read — calldata and result hidden, auto-decoded
number = contract.read.getNumber()  # int
print(f"Number: {number}")
```

### Transparent Operations

```python
# 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

```python
# 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)
```

### With Transaction Parameters

```python
# 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,
)
```

### Using EIP-712 Signing

```python
# 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)
```

### Instantiation via Client

```python
# Most common pattern - let the client create the contract
from seismic_web3 import create_wallet_client

w3 = create_wallet_client(
    rpc_url="https://testnet-1.seismictest.net/rpc",
    private_key=private_key,
)

# Client's contract() method creates ShieldedContract
contract = w3.seismic.contract(address=contract_address, abi=CONTRACT_ABI)

# Now use any namespace
tx_hash = contract.write.setNumber(42)
```

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

## See Also

* [AsyncShieldedContract](/clients/python/contract/async-shielded-contract) - Async version of this class
* [PublicContract](/clients/python/contract/public-contract) - Read-only contract wrapper (no encryption)
* [EncryptionState](/clients/python/client/encryption-state) - Manages encryption keys
* [DebugWriteResult](/clients/python/api-reference/transaction-types/debug-write-result) - Debug write return type
* [SeismicSecurityParams](/clients/python/api-reference/transaction-types/seismic-security-params) - Transaction expiry parameters
* [Contract Namespaces](/clients/python/contract/namespaces) - Detailed namespace documentation
* [Shielded Write Guide](/clients/python/guides/shielded-write) - Complete workflow guide


# AsyncShieldedContract

Async contract wrapper with shielded and transparent namespaces

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

## Overview

`AsyncShieldedContract` is the async version of `ShieldedContract`, providing the same five namespaces (`.write`, `.read`, `.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.

## Definition

```python
class AsyncShieldedContract:
    def __init__(
        self,
        w3: AsyncWeb3,
        encryption: EncryptionState,
        private_key: PrivateKey,
        address: ChecksumAddress,
        abi: list[dict[str, Any]],
        eip712: bool = False,
    ) -> None:
        ...
```

## Constructor Parameters

| Parameter     | Type                                                            | Required | Description                                              |
| ------------- | --------------------------------------------------------------- | -------- | -------------------------------------------------------- |
| `w3`          | `AsyncWeb3`                                                     | Yes      | Asynchronous AsyncWeb3 instance connected to Seismic RPC |
| `encryption`  | [`EncryptionState`](/clients/python/client/encryption-state)    | Yes      | Encryption state for shielded operations                 |
| `private_key` | [`PrivateKey`](/clients/python/api-reference/types/private-key) | Yes      | 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`)        |

## Namespaces

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

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

### `.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.)

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

### `.dwrite` - Debug Write

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

**Returns**: `Coroutine[DebugWriteResult]` ([`DebugWriteResult`](/clients/python/api-reference/transaction-types/debug-write-result))

**Positional Arguments**: `*args` - ABI function arguments

**Optional Parameters**: Same as `.write`

## Examples

### Basic Encrypted Write

```python
import asyncio
from seismic_web3 import create_async_wallet_client, AsyncShieldedContract

async def main():
    w3 = await create_async_wallet_client(
        provider_url="https://testnet-1.seismictest.net/rpc",
        private_key=private_key,
    )

    contract = AsyncShieldedContract(
        w3=w3,
        encryption=w3.seismic.encryption,
        private_key=private_key,
        address="0x1234567890123456789012345678901234567890",
        abi=CONTRACT_ABI,
    )

    # Encrypted write - must await
    tx_hash = await contract.write.setNumber(42)
    print(f"Transaction: {tx_hash.to_0x_hex()}")

    # Wait for confirmation
    receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
    print(f"Status: {receipt['status']}")

asyncio.run(main())
```

### Encrypted Read

```python
async def read_example(contract: AsyncShieldedContract):
    # Encrypted read — auto-decoded, must await
    number = await contract.read.getNumber()  # int
    print(f"Number: {number}")
```

### Transparent Operations

```python
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}")
```

### Debug Write

```python
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']}")
```

### Concurrent Operations

```python
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}")
```

### With Transaction Parameters

```python
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,
    )
```

### Batch Processing

```python
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")
```

### Using EIP-712 Signing

```python
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)
```

### Instantiation via Async Client

```python
async def client_pattern():
    # Most common pattern - let the client create the contract
    w3 = await create_async_wallet_client(
        provider_url="https://testnet-1.seismictest.net/rpc",
        private_key=private_key,
    )

    # Client's contract() method creates AsyncShieldedContract
    contract = w3.seismic.contract(address=contract_address, abi=CONTRACT_ABI)

    # Now use any namespace (must await)
    tx_hash = await contract.write.setNumber(42)
```

### Error Handling

```python
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}")
```

### Context Manager Pattern

```python
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
```

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

## See Also

* [ShieldedContract](/clients/python/contract/shielded-contract) - Synchronous version of this class
* [AsyncPublicContract](/clients/python/contract/async-public-contract) - Async read-only contract wrapper
* [create\_async\_wallet\_client](/clients/python/client/create-async-wallet-client) - Create async client
* [EncryptionState](/clients/python/client/encryption-state) - Manages encryption keys
* [DebugWriteResult](/clients/python/api-reference/transaction-types/debug-write-result) - Debug write return type
* [SeismicSecurityParams](/clients/python/api-reference/transaction-types/seismic-security-params) - Transaction expiry parameters
* [Contract Namespaces](/clients/python/contract/namespaces) - Detailed namespace documentation


# PublicContract

Sync contract wrapper with transparent read-only access

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

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

## Definition

```python
class PublicContract:
    def __init__(
        self,
        w3: Web3,
        address: ChecksumAddress,
        abi: list[dict[str, Any]],
    ) -> None:
        ...
```

## 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      | Contract ABI (list of function entries)             |

## Namespace

### `.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)

## Examples

### Basic Read Operations

```python
from seismic_web3 import create_public_client, PublicContract

# Create client without private key
w3 = create_public_client(
    rpc_url="https://testnet-1.seismictest.net/rpc",
)

# Create read-only contract instance
contract = PublicContract(
    w3=w3,
    address="0x1234567890123456789012345678901234567890",
    abi=CONTRACT_ABI,
)

# Read public contract state (auto-decoded)
total_supply = contract.tread.totalSupply()  # int
print(f"Total supply: {total_supply}")

balance = contract.tread.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")  # int
print(f"Balance: {balance}")
```

### Single and Multiple Returns

```python
# 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",
)
```

### Array Results

```python
# 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}")
```

### Error Handling

```python
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}")
```

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

## See Also

* [AsyncPublicContract](/clients/python/contract/async-public-contract) - Async version of this class
* [ShieldedContract](/clients/python/contract/shielded-contract) - Full contract wrapper with write operations
* [create\_public\_client](/clients/python/client/create-public-client) - Create client without private key
* [Contract Instance Guide](/clients/python/contract) - Overview of contract interaction patterns


# AsyncPublicContract

Async contract wrapper with transparent read-only access

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

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

## Definition

```python
class AsyncPublicContract:
    def __init__(
        self,
        w3: AsyncWeb3,
        address: ChecksumAddress,
        abi: list[dict[str, Any]],
    ) -> None:
        ...
```

## Constructor Parameters

| Parameter | Type                   | Required | Description                                               |
| --------- | ---------------------- | -------- | --------------------------------------------------------- |
| `w3`      | `AsyncWeb3`            | Yes      | Asynchronous AsyncWeb3 instance connected to RPC endpoint |
| `address` | `ChecksumAddress`      | Yes      | Contract address (checksummed Ethereum address)           |
| `abi`     | `list[dict[str, Any]]` | Yes      | Contract ABI (list of function entries)                   |

## Namespace

### `.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)

## Examples

### Basic Read Operations

```python
import asyncio
from seismic_web3 import create_async_public_client, AsyncPublicContract

async def main():
    # Create async client without private key
    w3 = create_async_public_client(
        provider_url="https://testnet-1.seismictest.net/rpc",
    )

    # Create read-only contract instance
    contract = AsyncPublicContract(
        w3=w3,
        address="0x1234567890123456789012345678901234567890",
        abi=CONTRACT_ABI,
    )

    # Read public contract state (must await, auto-decoded)
    total_supply = await contract.tread.totalSupply()  # int
    print(f"Total supply: {total_supply}")

    balance = await contract.tread.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")  # int
    print(f"Balance: {balance}")

asyncio.run(main())
```

### Concurrent Reads

```python
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}")
```

### Single and Multiple Returns

```python
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",
    )
```

### Array Results

```python
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}")
```

### Error Handling

```python
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}")
```

## 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` or private keys
* **No authentication**: Standard unsigned async `eth_call` operations
* **Gas not consumed**: `eth_call` is free (doesn't create transactions)

## See Also

* [PublicContract](/clients/python/contract/public-contract) - Synchronous version of this class
* [AsyncShieldedContract](/clients/python/contract/async-shielded-contract) - Full async contract wrapper with write operations
* [create\_async\_public\_client](/clients/python/client/create-async-public-client) - Create async client without private key
* [Contract Instance Guide](/clients/python/contract) - Overview of contract interaction patterns


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

***

## Overview

When you instantiate a contract:

```python
contract = w3.seismic.contract(address="0x...", abi=ABI)
```

You get access to five namespaces:

| Namespace                                               | Operation | Encryption | `msg.sender` | Broadcasts | Use Case                 |
| ------------------------------------------------------- | --------- | ---------- | ------------ | ---------- | ------------------------ |
| [`.write`](/clients/python/contract/namespaces/write)   | Write     | Yes        | Your address | Yes        | Privacy-sensitive writes |
| [`.read`](/clients/python/contract/namespaces/read)     | Read      | Yes        | Your address | No         | Access-controlled reads  |
| [`.twrite`](/clients/python/contract/namespaces/twrite) | Write     | No         | Your address | Yes        | Public writes            |
| [`.tread`](/clients/python/contract/namespaces/tread)   | Read      | No         | `0x0`        | No         | Public reads             |
| [`.dwrite`](/clients/python/contract/namespaces/dwrite) | Write     | Yes        | Your address | Yes        | Debug/testing            |

***

## Quick Comparison

### 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
* Includes security metadata (nonce, block hash, expiry)
* Higher gas cost (encryption overhead)

**Transparent** (`.twrite`, `.tread`):

* Calldata is visible on-chain
* Standard Ethereum transactions/calls
* No encryption or security metadata
* Lower gas cost (no overhead)
* Faster (no encryption computation)

### Write vs Read

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

* Broadcasts a transaction
* Consumes gas
* Modifies contract state
* Returns transaction hash
* Must wait for confirmation

**Read** (`.read`, `.tread`):

* Executes an `eth_call`
* Free (no gas cost)
* Does not modify state
* Returns result immediately
* No confirmation needed

***

## Usage Patterns

### Encrypted Write (.write)

```python
# Privacy-sensitive transaction
tx_hash = contract.write.transfer(recipient, 1000)

# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
```

**When to use**:

* Amounts, addresses, or arguments should be private
* Privacy compliance required
* Trading, voting, auctions, confidential transfers

### Signed Read (.read)

```python
# Encrypted read that proves your identity — auto-decoded
balance = contract.read.balanceOf()  # int
```

**When to use**:

* Contract checks `msg.sender` for access control
* Caller-specific data (e.g., "my balance")
* Privacy required for queries
* Result should be encrypted

### Transparent Write (.twrite)

```python
# Public transaction
tx_hash = contract.twrite.approve(spender, amount)

# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
```

**When to use**:

* Data is public anyway
* Lower gas cost matters
* No privacy requirements
* Standard Ethereum behavior

### Transparent Read (.tread)

```python
# Public read — auto-decoded
total_supply = contract.tread.totalSupply()  # int
```

**When to use**:

* Function is public (doesn't check `msg.sender`)
* No authentication needed
* Data is public

### Debug Write (.dwrite)

```python
# 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)
```

**When to use**:

* Development and testing
* Debugging encryption issues
* Verifying calldata encoding
* Auditing transaction parameters

***

## Common Pitfalls

### Using .tread for Access-Controlled Functions

**Problem**:

```python
# BAD: SRC20 balanceOf uses msg.sender — returns 0x0's balance
balance = contract.tread.balanceOf()  # 0
```

**Solution**:

```python
# GOOD: Proves your identity
balance = contract.read.balanceOf()  # Your actual balance
```

### Using .write for Public Data

**Unnecessary overhead**:

```python
# Wasteful: Encrypts already-public data
tx_hash = contract.write.approve(spender, amount)
```

**Better**:

```python
# More efficient: Use transparent write
tx_hash = contract.twrite.approve(spender, amount)
```

***

## See Also

* [Contract Instance](/clients/python/contract) — Contract wrapper reference
* [Shielded Write Guide](/clients/python/guides/shielded-write) — Full encryption workflow
* [Signed Read Guide](/clients/python/guides/signed-reads) — Authentication and privacy for reads
* [SeismicSecurityParams](/clients/python/api-reference/transaction-types/seismic-security-params) — Security parameters
* [DebugWriteResult](/clients/python/api-reference/transaction-types/debug-write-result) — Debug write return type


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

***

## 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)
4. Signs and broadcasts the transaction
5. Returns the transaction hash

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.

***

## Usage Pattern

```python
tx_hash = contract.write.functionName(arg1, arg2, ...)
```

* **Sync**: Returns `HexBytes` transaction hash immediately
* **Async**: Returns `HexBytes` transaction hash (must `await`)

***

## Parameters

### Function Arguments

Pass function arguments as positional parameters:

```python
# 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],
)
```

### Transaction Options (Keyword Arguments)

All transaction options are **optional** keyword arguments:

| 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`  | Gas price in wei (uses network default if `None`)      |
| `security`  | [`SeismicSecurityParams`](/clients/python/api-reference/transaction-types/seismic-security-params) \| `None` | `None`  | Custom security parameters (block expiry, nonce, etc.) |

***

## Examples

### Sync Usage

```python
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']}")
```

### Async Usage

```python
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']}")
```

### Sending ETH

```python
# Send 1 ETH with the transaction
tx_hash = contract.write.deposit(
    value=10**18,  # 1 ETH in wei
)
```

### Custom Gas Parameters

```python
# Set gas limit and gas price
tx_hash = contract.write.transfer(
    recipient,
    amount,
    gas=100_000,
    gas_price=20 * 10**9,  # 20 gwei
)
```

### Custom Security Parameters

```python
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,
)
```

### Combining All Parameters

```python
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,
)
```

***

## Return Value

Returns `HexBytes` containing the transaction hash.

```python
# 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)
```

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)`

***

## Privacy Guarantees

### 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)
* Gas used

But **cannot** see:

* Which function you called
* What arguments you passed
* Any data in the calldata

### What Remains Visible

These fields are **not** encrypted:

* `from` — Your wallet address
* `to` — Contract address
* `value` — ETH amount sent
* `gas` and `gas_price` — Gas parameters
* `nonce` — Transaction nonce
* Transaction metadata (block hash, expiry, encryption nonce)

***

## Security Considerations

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

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

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

***

## Error Handling

```python
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")
```

***

## Comparison with Other Namespaces

| Namespace | Encryption             | Transaction Type      | Gas Cost                       | Use Case                  |
| --------- | ---------------------- | --------------------- | ------------------------------ | ------------------------- |
| `.write`  | Encrypted calldata     | `TxSeismic` (0x4a)    | Standard + encryption overhead | Privacy-sensitive writes  |
| `.twrite` | No encryption          | `eth_sendTransaction` | Standard                       | Public writes, lower cost |
| `.dwrite` | Encrypted + debug info | `TxSeismic` (0x4a)    | Same as `.write`               | Development/debugging     |

***

## Best Practices

### Use `.write` When

* Function arguments contain sensitive data (amounts, addresses, private state)
* Privacy is required (trading, voting, auctions)
* Compliance requires on-chain confidentiality

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

### Production Checklist

* Use default security parameters (don't override unless necessary)
* Handle transaction failures gracefully
* Wait for transaction confirmation before assuming success
* Monitor gas prices and adjust if needed
* Test with `.dwrite` first to verify calldata encoding

***

## Low-Level Alternative

If you need more control (e.g., contract deployment, pre-encoded calldata):

```python
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,
)
```

See [Shielded Write Guide](/clients/python/guides/shielded-write#low-level-api) for details.

***

## See Also

* [Shielded Write Guide](/clients/python/guides/shielded-write) — Full encryption workflow
* [SeismicSecurityParams](/clients/python/api-reference/transaction-types/seismic-security-params) — Security parameter reference
* [.read Namespace](/clients/python/contract/namespaces/read) — Encrypted signed reads
* [.dwrite Namespace](/clients/python/contract/namespaces/dwrite) — Debug writes with inspection
* [.twrite Namespace](/clients/python/contract/namespaces/twrite) — Transparent writes without encryption


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

***

## 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)
4. Signs the transaction with your private key
5. Sends the signed transaction to `eth_call` endpoint
6. Node decrypts calldata inside TEE, executes the call, encrypts the result
7. Returns encrypted result (which the SDK can decrypt if needed)

Unlike standard `eth_call`, signed reads **prove your identity** to the contract via `msg.sender`, enabling access-controlled queries.

***

## Usage Pattern

```python
result = contract.read.functionName(arg1, arg2, ...)
```

* **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`

***

## Parameters

### Function Arguments

Pass function arguments as positional parameters:

```python
# No arguments
result = contract.read.balanceOf()

# Single argument
result = contract.read.balanceOf(owner_address)

# Multiple arguments
result = contract.read.getUserInfo(user_address)
```

### Call Options (Keyword Arguments)

All call options are **optional** keyword arguments:

| Parameter  | Type                                                                                                         | Default      | Description                                            |
| ---------- | ------------------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------ |
| `value`    | `int`                                                                                                        | `0`          | ETH value for the call (in wei)                        |
| `gas`      | `int`                                                                                                        | `30_000_000` | Gas limit for the call                                 |
| `security` | [`SeismicSecurityParams`](/clients/python/api-reference/transaction-types/seismic-security-params) \| `None` | `None`       | Custom security parameters (block expiry, nonce, etc.) |

**Note**: Unlike `.write`, there is no `gas_price` parameter because reads don't consume gas.

***

## Examples

### Sync Usage

```python
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
```

### Async Usage

```python
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}")
```

### Reading with Arguments

```python
# Single argument — returns decoded value directly
owner_balance = contract.read.balanceOf(owner_address)

# Multiple arguments
allowance = contract.read.allowance(owner_address, spender_address)
```

### Custom Gas Limit

```python
# Increase gas for complex reads
result = contract.read.getItems(
    offset,
    limit,
    gas=50_000_000,
)
```

### Custom Security Parameters

```python
from seismic_web3 import SeismicSecurityParams

# Use longer expiry window
security = SeismicSecurityParams(blocks_window=200)

result = contract.read.balanceOf(security=security)
```

### Simulating Value Transfer

```python
# Simulate sending 1 ETH with the read
result = contract.read.deposit(
    value=10**18,  # 1 ETH in wei
)
```

***

## 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)`): a `tuple` of decoded values
* **No outputs defined in ABI**: `None`

```python
# 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
```

***

## Why Use Signed Reads?

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

```solidity
// SRC20 balanceOf — uses msg.sender internally
function balanceOf() external view returns (suint256) {
    return balances[msg.sender];
}
```

With **signed read** (`.read`):

```python
# Proves your identity — msg.sender is your address
balance = contract.read.balanceOf()  # Returns your balance (int)
```

With **transparent read** (`.tread`):

```python
# Does NOT prove identity — msg.sender is 0x0
balance = contract.tread.balanceOf()  # Returns 0x0's balance (usually 0)
```

### 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.)
* Uses `msg.sender` in any way

***

## Privacy Guarantees

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

### What Remains Visible

These fields are **not** encrypted:

* `from` — Your wallet address
* `to` — Contract address
* Gas limit
* Transaction metadata (block hash, expiry, encryption nonce)

***

## Comparison with .tread

| 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)   |
| Use case            | Access-controlled reads            | Public reads               |

***

## Security Considerations

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

### Transaction Expiry

Calls include an expiry block number:

* Default: **100 blocks** (\~20 minutes)
* After expiry, node rejects the call

### Nonce Uniqueness

Each call uses a cryptographically random 12-byte encryption nonce. The SDK generates random nonces automatically.

### Private Key Security

Signed reads **require your private key** to sign the call. Never expose your private key or share it with untrusted parties.

***

## Error Handling

```python
try:
    balance = contract.read.balanceOf()
    print(f"Balance: {balance}")

except ValueError as e:
    print(f"Call failed: {e}")
```

***

## Best Practices

### Use `.read` When

* Contract function checks `msg.sender`
* You need to prove your identity
* Data is access-controlled (requires authentication)
* Privacy is required (both query and result)

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

### 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
* Consider caching read results if called frequently

***

## Low-Level Alternative

If you need more control (e.g., pre-encoded calldata):

```python
from hexbytes import HexBytes

result = w3.seismic.signed_call(
    to="0x...",
    data=HexBytes("0x..."),
    gas=30_000_000,
)
```

See [Signed Read Guide](/clients/python/guides/signed-reads#low-level-api) for details.

***

## Async Patterns

### Concurrent Reads

```python
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(),
)
```

### Read with Timeout

```python
import asyncio

try:
    result = await asyncio.wait_for(
        contract.read.balanceOf(),
        timeout=10.0,  # 10 seconds
    )
except asyncio.TimeoutError:
    print("Read timed out")
```

***

## See Also

* [Signed Read Guide](/clients/python/guides/signed-reads) — Full workflow and examples
* [SeismicSecurityParams](/clients/python/api-reference/transaction-types/seismic-security-params) — Security parameter reference
* [.write Namespace](/clients/python/contract/namespaces/write) — Encrypted writes
* [.tread Namespace](/clients/python/contract/namespaces/tread) — Transparent reads without encryption
* [ShieldedContract](/clients/python/contract) — Contract wrapper reference


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

***

## 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`
4. Returns the transaction hash

**No encryption** is applied — calldata is visible on-chain to anyone.

***

## Usage Pattern

```python
tx_hash = contract.twrite.functionName(arg1, arg2, ...)
```

* **Sync**: Returns `HexBytes` transaction hash immediately
* **Async**: Returns `HexBytes` transaction hash (must `await`)

***

## Parameters

### Function Arguments

Pass function arguments as positional parameters:

```python
# 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],
)
```

### Transaction Options (Keyword Arguments)

Transaction options are passed as keyword arguments:

| Parameter                       | Type  | Default | Description                             |
| ------------------------------- | ----- | ------- | --------------------------------------- |
| `value`                         | `int` | `0`     | ETH value to send (in wei)              |
| Any `eth_sendTransaction` param | `Any` | —       | Standard web3.py transaction parameters |

Common additional parameters:

* `gas` — Gas limit
* `gasPrice` — Gas price (legacy)
* `maxFeePerGas` — Max fee per gas (EIP-1559)
* `maxPriorityFeePerGas` — Max priority fee (EIP-1559)
* `nonce` — Transaction nonce (auto-generated if omitted)

***

## Examples

### Sync Usage

```python
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']}")
```

### Async Usage

```python
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']}")
```

### Sending ETH

```python
# Send 1 ETH with the transaction
tx_hash = contract.twrite.deposit(
    value=10**18,  # 1 ETH in wei
)
```

### Custom Gas (Legacy)

```python
# Legacy gas pricing
tx_hash = contract.twrite.transfer(
    recipient,
    amount,
    gas=100_000,
    gasPrice=20 * 10**9,  # 20 gwei
)
```

### EIP-1559 Gas Parameters

```python
# 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
)
```

### Custom Nonce

```python
# Explicitly set nonce
nonce = w3.eth.get_transaction_count(w3.eth.default_account)
tx_hash = contract.twrite.transfer(
    recipient,
    amount,
    nonce=nonce,
)
```

### Combining Parameters

```python
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
)
```

***

## Return Value

Returns `HexBytes` containing the transaction hash.

```python
# 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)
```

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)`

***

## Privacy Implications

### What's Visible

**Everything is visible on-chain:**

* Your address (transaction sender)
* Contract address (transaction recipient)
* Function selector (first 4 bytes of calldata)
* All function arguments (decoded in calldata)
* Value transferred
* Gas parameters
* Transaction result (success/revert)

### Example

```python
# This transaction is completely visible
tx_hash = contract.twrite.transfer(
    "0x1234...",  # Recipient visible
    1000,         # Amount visible
)
```

Anyone can:

* See you called `transfer`
* See the recipient address
* See the amount transferred
* Decode all parameters from calldata

***

## When to Use .twrite

### 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
* **Testing** — Quick tests where privacy doesn't matter
* **Compatibility** — Interacting with contracts that expect standard transactions

### Examples

```python
# 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)
```

***

## When NOT to Use .twrite

### Use `.write` Instead When

* Function arguments contain sensitive data
* Privacy is required (trading, private voting, auctions)
* Amounts or addresses should be hidden
* Compliance requires confidentiality

### Examples (Use `.write` for these)

```python
# Private balance transfer — use .write
tx_hash = contract.write.transfer(recipient, secret_amount)

# Private withdrawal — use .write
tx_hash = contract.write.withdraw(amount)
```

***

## Comparison with Other Namespaces

| Namespace | Encryption             | Transaction Type      | Gas Cost                       | Use Case                  |
| --------- | ---------------------- | --------------------- | ------------------------------ | ------------------------- |
| `.write`  | Encrypted calldata     | `TxSeismic` (0x4a)    | Standard + encryption overhead | Privacy-sensitive writes  |
| `.twrite` | No encryption          | `eth_sendTransaction` | Standard                       | Public writes, lower cost |
| `.dwrite` | Encrypted + debug info | `TxSeismic` (0x4a)    | Same as `.write`               | Development/debugging     |

***

## Error Handling

```python
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")
```

***

## Standard Web3.py Behavior

The `.twrite` namespace uses standard `eth_sendTransaction` under the hood. All web3.py transaction features work:

### Account Management

```python
# Uses default account from web3
w3.eth.default_account = "0x..."
tx_hash = contract.twrite.transfer(recipient, amount)
```

### Transaction Middleware

```python
# 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 Estimation

```python
# Gas is auto-estimated if not provided
tx_hash = contract.twrite.setNumber(42)  # Gas estimated automatically
```

***

## Best Practices

### 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
* **Test with small amounts first** — Errors are visible to everyone

### Optimization Tips

* Let web3.py estimate gas (don't hardcode unless necessary)
* Use EIP-1559 pricing on supported networks
* Batch multiple operations if possible
* Consider using `.write` for high-value transactions (extra privacy worth the cost)

### Common Patterns

```python
# 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)
```

***

## Low-Level Alternative

Direct `eth_sendTransaction` call:

```python
tx_hash = w3.eth.send_transaction({
    "to": contract_address,
    "data": "0x...",  # Encoded calldata
    "value": 0,
    "gas": 100_000,
})
```

The `.twrite` namespace is more convenient as it handles ABI encoding automatically.

***

## See Also

* [.write Namespace](/clients/python/contract/namespaces/write) — Encrypted writes for privacy
* [.tread Namespace](/clients/python/contract/namespaces/tread) — Transparent reads
* [.dwrite Namespace](/clients/python/contract/namespaces/dwrite) — Debug writes
* [Contract Instance](/clients/python/contract) — Contract wrapper reference
* [Web3.py Documentation](https://web3py.readthedocs.io/) — Standard Ethereum transactions


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

***

## Overview

When you call `contract.tread.functionName(...)`, 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.

***

## Usage Pattern

```python
result = contract.tread.functionName(arg1, arg2, ...)
```

* **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`

***

## Parameters

### Function Arguments

Pass function arguments as positional parameters:

```python
# No arguments
result = contract.tread.totalSupply()

# Single argument
result = contract.tread.balanceOf(address)

# Multiple arguments
result = contract.tread.allowance(owner, spender)
```

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

***

## Examples

### Sync Usage

```python
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}")
```

### Async Usage

```python
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}")
```

### Reading with Arguments

```python
# Single argument — returns decoded value directly
balance = contract.tread.balanceOf("0x1234...")  # int

# Multiple arguments
allowance = contract.tread.allowance("0x1234...", "0x5678...")  # int
```

### Multiple Return Values

```python
# 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 and Struct Returns

```python
# 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()
```

***

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

```python
# 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
```

***

## Key Limitation: No msg.sender

Standard `eth_call` does **not** prove your identity. The contract sees `msg.sender` as `0x0000000000000000000000000000000000000000`.

### Problem Example

SRC20's `balanceOf()` takes no arguments and returns the caller's balance:

```solidity
// SRC20 balanceOf — uses msg.sender internally
function balanceOf() external view returns (suint256) {
    return balances[msg.sender];
}
```

With **transparent read** (`.tread`):

```python
# msg.sender is 0x0 — returns 0x0's balance (int)
balance = contract.tread.balanceOf()  # Almost always 0
```

With **signed read** (`.read`):

```python
# msg.sender is your address — returns your balance (int)
balance = contract.read.balanceOf()  # Your actual balance
```

### When .tread Works

Use `.tread` when the contract function:

* Does **not** check `msg.sender`
* Is purely computational (no authentication)
* Returns public data

**Examples**:

```solidity
// 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);
```

### When .tread Fails

Use `.read` when the contract function:

* Checks `msg.sender` for permissions
* Returns caller-specific data
* Has any access control

**Examples**:

```solidity
// These require .read (signed read) — they use msg.sender internally
function balanceOf() external view returns (suint256);   // SRC20
function allowance() external view returns (suint256);    // SRC20
```

***

## Privacy Implications

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

### Example

```python
# This call is completely visible
result = contract.tread.balanceOf("0x1234...")
```

The node (and anyone monitoring) can see:

* You're checking the balance of `0x1234...`
* Which contract you're querying
* The result (the balance amount)

***

## When to Use .tread

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

### Examples

```python
# 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
```

***

## When NOT to Use .tread

### Use `.read` Instead When

* Function checks `msg.sender`
* Caller-specific data is needed
* Privacy is required
* Access control is involved

### Examples (Use `.read` for these)

```python
# 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)
```

***

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

***

## Error Handling

```python
try:
    balance = contract.tread.balanceOf(address)
    print(f"Balance: {balance}")

except Exception as e:
    print(f"Call failed: {e}")
```

***

## Best Practices

### When to Choose .tread

**Checklist**:

* [ ] Function does not check `msg.sender`
* [ ] No authentication required
* [ ] Data is public anyway
* [ ] Privacy is not a concern
* [ ] Want zero overhead (no encryption)

### When to Choose .read

**Checklist**:

* [ ] Function checks `msg.sender`
* [ ] Caller-specific data needed
* [ ] Access control involved
* [ ] Privacy required
* [ ] Need to prove identity

### Common Mistakes

**Mistake**: Using `.tread` for caller-specific functions

```python
# BAD: SRC20 balanceOf uses msg.sender — returns 0x0's balance
balance = contract.tread.balanceOf()
```

**Fix**: Use `.read` to prove your identity

```python
# GOOD: Returns your actual balance
balance = contract.read.balanceOf()
```

***

## Async Patterns

### Concurrent Reads

```python
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(),
)
```

### Read with Timeout

```python
import asyncio

try:
    result = await asyncio.wait_for(
        contract.tread.totalSupply(),
        timeout=5.0,  # 5 seconds
    )
except asyncio.TimeoutError:
    print("Read timed out")
```

***

## Standard Web3.py Behavior

The `.tread` namespace uses standard `eth_call` under the hood. All web3.py call features work:

### Block Number

You can specify a block number (though not directly via `.tread`):

```python
# 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
)
```

***

## Low-Level Alternative

Direct `eth_call`:

```python
result = w3.eth.call({
    "to": contract_address,
    "data": "0x...",  # Encoded calldata
})
```

The `.tread` namespace is more convenient as it handles ABI encoding automatically.

***

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

***

## See Also

* [.read Namespace](/clients/python/contract/namespaces/read) — Encrypted signed reads with `msg.sender`
* [.twrite Namespace](/clients/python/contract/namespaces/twrite) — Transparent writes
* [.write Namespace](/clients/python/contract/namespaces/write) — Encrypted writes
* [Contract Instance](/clients/python/contract) — Contract wrapper reference
* [Web3.py Documentation](https://web3py.readthedocs.io/) — Standard Ethereum calls


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

***

## Overview

When you call `contract.dwrite.functionName(...)`, 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
   * **tx\_hash** — Transaction hash from broadcast

**Important**: The transaction is **actually broadcast** and consumes gas. This is not a dry run.

***

## Usage Pattern

```python
result = contract.dwrite.functionName(arg1, arg2, ...)
```

* **Sync**: Returns `DebugWriteResult` immediately
* **Async**: Returns `DebugWriteResult` (must `await`)

***

## Parameters

### Function Arguments

Pass function arguments as positional parameters:

```python
# 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],
)
```

### 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`  | Gas price in wei (uses network default if `None`)      |
| `security`  | [`SeismicSecurityParams`](/clients/python/api-reference/transaction-types/seismic-security-params) \| `None` | `None`  | Custom security parameters (block expiry, nonce, etc.) |

***

## Return Value

Returns a [`DebugWriteResult`](/clients/python/api-reference/transaction-types/debug-write-result) with three fields:

```python
@dataclass(frozen=True)
class DebugWriteResult:
    plaintext_tx: PlaintextTx           # Transaction before encryption
    shielded_tx: UnsignedSeismicTx      # Transaction with encrypted calldata
    tx_hash: HexBytes                    # Transaction hash
```

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

***

## Examples

### Sync Usage

```python
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']}")
```

### Async Usage

```python
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']}")
```

### Inspect Plaintext Calldata

```python
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()}")
```

### Verify Encryption

```python
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
```

### Inspect Transaction Parameters

```python
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}")
```

### Analyze Gas Limit vs Usage

```python
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}")
```

### Compare with Production .write

```python
# 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)
```

***

## Use Cases

### Development and Testing

```python
# 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
```

### Debugging Encryption

```python
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")
```

### Auditing Transaction Details

```python
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()}")
```

### Testing Security Parameters

```python
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
```

***

## Important Warnings

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

### 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`**:

```python
# Development/testing
result = contract.dwrite.transfer(recipient, amount)
tx_hash = result.tx_hash

# Production
tx_hash = contract.write.transfer(recipient, amount)
```

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

***

## 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      |
| `.twrite` | No         | `HexBytes` (tx hash) | Yes                   | Transparent writes         |

***

## Privacy Guarantees

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

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

***

## Error Handling

```python
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")
```

***

## Best Practices

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

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

### Testing Workflow

```python
# 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)
```

***

## Low-Level Alternative

For manual control with debug info:

```python
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())
```

See [Shielded Write Guide](/clients/python/guides/shielded-write#low-level-api) for details.

***

## See Also

* [.write Namespace](/clients/python/contract/namespaces/write) — Production shielded writes (no debug info)
* [DebugWriteResult](/clients/python/api-reference/transaction-types/debug-write-result) — Return type reference
* [PlaintextTx](/clients/python/api-reference/transaction-types/plaintext-tx) — Plaintext transaction structure
* [UnsignedSeismicTx](/clients/python/api-reference/transaction-types/unsigned-seismic-tx) — Shielded transaction structure
* [Shielded Write Guide](/clients/python/guides/shielded-write) — Full encryption workflow




---

[Next Page](/llms-full.txt/1)

