EIP-712 Permits
How a read-only signature lets only you decrypt your own balance — and how to never fire it by accident.
Reading a confidential balance requires an EIP-712 typed-data signature from the token owner's wallet. This signature authorizes the Zama Gateway to decrypt the ciphertext and return the plaintext to the frontend session. It is off-chain — no gas, no transaction.
useConfidentialBalance or useConfidentialBalances with enabled: true immediately requests a wallet signature. Always gate it behind an explicit decryptRequested boolean that is only set true on a user click.How it works
Set decryptRequested = true in your component state. Nothing fires until this happens.
The hook builds a typed-data payload and asks the wallet to sign it. Off-chain: no gas, no transaction.
The signature derives a short-lived key scoped to your address and the specific contract.
The Gateway uses the session key to decrypt the on-chain ciphertext. Only your account's ciphertexts are decryptable with your key.
The decrypted bigint balance is handed to your component. It is never written back on-chain in plaintext.
decryptRequested synchronously in the onChange handler — not only in a useEffect. A one-frame delay in the effect can let the old true combine with the new token address and auto-fire a permit.const [selectedToken, setSelectedToken] = useState(pairs[0]);
const [decryptRequested, setDecryptRequested] = useState(false);
// ✓ Reset synchronously on token change
const handleTokenChange = (newToken: WrapperPair) => {
setSelectedToken(newToken);
setDecryptRequested(false); // ← same handler, not a useEffect
};
const { data: balance } = useConfidentialBalance({
tokenAddress: selectedToken.erc7984Address,
enabled: decryptRequested && !!address, // ← explicit gate
});Rejections are terminal — do not retry
If the user declines the signature, treat it as done: re-arm the button and wait for another click. Do not re-fire the query on error, on window focus, or on remount — that produces the "wallet keeps popping up" loop. ShadowLine disables the query after any decrypt error and only re-enables it on a fresh click.
