For the complete documentation index, see llms.txt. This page is also available as Markdown.

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. Auto-detects shielded parameters and encrypts only when needed.

  • contract.swrite.functionName() -- force shielded via getShieldedContract. 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

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


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.

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.


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:


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

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.

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.

See Also

Last updated