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:
Shielded types cannot be
public. Thesuint256value type means the automatic getter would try to return a shielded type from an external function, which the compiler rejects.Vanilla
eth_callhas no sender identity. On Seismic, thefromfield is zeroed out for unsignedeth_callrequests. This means a contract cannot verifymsg.senderin a view function called via a normaleth_call--msg.senderwould just beaddress(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
fromfield is cryptographically verified, so the contract can trustmsg.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_readflag in SeismicElements should be set totrue, 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:
Note the naming here. The mapping balanceOf is internal (no public modifier), so there is no collision with the function name. The function acts as the explicit getter that replaces the auto-generated one.
This function does three things:
Checks
msg.sender-- Only the account owner can query their own balance. With a vanillaeth_call,msg.senderwould beaddress(0)and this check would always fail. With a signed read,msg.senderis the actual caller.Casts to
uint256-- The shieldedsuint256value is cast to a regularuint256for the return value. Shielded types cannot be returned from external functions.Returns the balance -- The return value is encrypted to the caller's key by the Seismic node before being sent back. Even though the function returns a
uint256, the response is encrypted because the call was made with a signed read.
Allowance checking
The same pattern applies to allowances:
Here, either the owner or the spender can check the allowance. Both parties have a legitimate reason to know the value.
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
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:
Both approaches produce the same signed read.
Security model
The signed read has several layers of protection:
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, we will add authorized roles (such as compliance officers) who can view specific balances when required -- without compromising privacy for everyone else.
Last updated

