shield-halvedShielded Write

Encrypted transactions -- lifecycle, security parameters, and the filler pipeline


How it works

When you build a transaction with .seismic() and send it via send_transaction(), the SDK:

  1. Fetches your nonce and the latest block hash from the node

  2. Populates TxSeismicElements (encryption nonce, TEE public key reference, block hash, expiry block)

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

  4. Signs and broadcasts the transaction as type 0x4A

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.


Step-by-step

1. Set up a signed provider

Shielded writes require a SeismicSignedProvider because you need a private key for both transaction signing and ECDH key derivation.

use seismic_prelude::foundry::*;
use alloy_signer_local::PrivateKeySigner;

let signer: PrivateKeySigner = "0xYOUR_PRIVATE_KEY".parse()?;
let wallet = SeismicWallet::from(signer);
let url: reqwest::Url = "https://gcp-1.seismictest.net/rpc".parse()?;

let provider = SeismicSignedProvider::<SeismicReth>::new(wallet, url).await?;
circle-info

new() is async because it fetches the TEE public key from the node and caches it for all subsequent encryption operations.

2. Define the contract interface

Use Alloy's sol! macro to define your contract interface. Shielded Solidity types (suint256, sbool, etc.) map to their standard ABI counterparts for encoding:

This generates type-safe Rust structs: ISeismicCounter::setNumberCall, ISeismicCounter::incrementCall, etc.

3. Encode calldata

Use the generated structs and abi_encode() to produce calldata bytes:

4. Build the transaction with .seismic()

The .seismic() method on a SeismicTransactionRequest marks it for encryption. Without .seismic(), the transaction is sent as a standard Ethereum type with no encryption.

5. Send and await receipt

6. Verify success


Security parameters

Every shielded transaction includes a block-hash freshness check and an expiry window. The SeismicElementsFiller automatically populates these with sane defaults:

Parameter
Default
Description

encryption_nonce

Random 12 bytes

Unique per-transaction AES-GCM nonce

encryption_pubkey

Provider's ephemeral public key

Client's ECDH public key for key derivation

blocks_window

100 blocks

Freshness window for the block hash

expires_at_block

current_block + blocks_window

Block number after which the transaction is invalid

recent_block_hash

Latest block hash

Anchors the transaction to a specific chain state

These values are set automatically by the filler pipeline. You do not need to set them manually.


What happens under the hood

The filler pipeline processes your transaction in this order:

You never call encryption functions manually. The .seismic() marker tells the filler pipeline to handle everything.

circle-info

You never need to call encryption functions manually. The provider's filler pipeline handles all cryptographic operations when you use .seismic().


Create transactions cannot be seismic

Contract deployment (TxKind::Create) always uses transparent transactions. The Seismic protocol does not support encrypting deployment bytecode. Deploy your contract with a standard transaction, then interact with it using shielded calls:


Error handling

Common failure modes and how to handle them:


Complete example

See Also

Last updated