Selective Disclosure
Selective disclosure is a privacy feature that allows contract owners to make specific pieces of information publicly accessible while maintaining the confidentiality of all other contract data.
The following contract information can be disclosed:
- Account data (bytecode, balance, nonce and account metadata).
- Token total supply.
- Token holder balance.
Selective disclosure is disabled by default. To enable it, set DISCLOSURE_METHODS_ENABLED=true on the Permissions
API and VITE_DISCLOSURE_METHODS_ENABLED=true on the Admin Panel.
Step-by-Step Process
1. Enable Disclosure
Disclosure settings are managed directly on the contract configuration form:
- Go to the Contracts page in the Admin Dashboard.
- Click Create Contract to register a new one, OR click on an existing contract to edit it.
- In the contract form, expand the Data disclosure section:
- Check Disclose account data to expose the address's full account-properties record (bytecode, balance, nonce and account metadata).
- Check Disclose Erc 20 Supply to expose total supply (requires the contract to be an ERC-20).
- Optionally set Disclosure start block to restrict disclosure queries to blocks at or after a specific block
number. Requests for earlier blocks are rejected with an error. Defaults to
0(no minimum).
- Click Save (or Create).
2. Configure Disclosed Addresses (ERC-20 only)
If ERC-20 Supply Disclosure is enabled, you can specify addresses whose token balances may be queried publicly via
prividium_tokenBalanceDisclosure.
- Navigate to the contract details page.
- Look for the Disclosed Addresses section.
- Add addresses to allow public balance queries for each.
- Save your changes.
3. Request Data
Once configured, users can query the data using specific JSON-RPC methods without needing an authentication token.
Available Disclosure Types
1. Account Data Disclosure
Reveals a single address's account-properties record, which includes bytecode, balance and nonce, along with the full cryptographic proof material needed to verify that record against the L1 state commitment. Works for both contracts and EOAs.
Purpose:
- Verify a contract's deployed bytecode matches expected values.
- Provide verifiable transparency for an address's balance and nonce without exposing unrelated state.
- Enable auditors and third parties to confirm an account's on-chain state against L1 without private RPC access.
RPC Endpoint: prividium_accountDataDisclosure
Request:
{
"method": "prividium_accountDataDisclosure",
"params": ["0x1234567890123456789012345678901234567890", "0x1a2b3c"]
}Parameters:
params[0](required): Address to query (contract or EOA).params[1](required): Hex-encoded block number. Must be at or after the contract's disclosure start block.
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"accountProperties": {
"versioningData": "0x0101010000000000",
"nonce": "0x01",
"balance": "0x0de0b6b3a7640000",
"bytecodeHash": "0xabcd...",
"unpaddedCodeLen": 1024,
"artifactsLen": 16,
"observableBytecodeHash": "0x1234...",
"observableBytecodeLen": 1024
},
"address": "0x1234567890123456789012345678901234567890",
"bytecode": "0x6080604052...",
"batchNumber": 175,
"stateCommitmentPreimage": {
"nextFreeSlot": "0x430",
"blockNumber": "0x1da",
"last256BlockHashesBlake": "0x7e2e68a1633628cf4ee6416a444a6af248e7cf975b3b744b6068beb14f8e98e4",
"lastBlockTimestamp": "0x69dda64c"
},
"l1VerificationData": {
"batchNumber": 175,
"numberOfLayer1Txs": 0,
"priorityOperationsHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"dependencyRootsRollingHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"l2ToL1LogsRootHash": "0x692f35c99f9c698852289ffecf07f6dd45770904521149d79aa85aae598fa375",
"commitment": "0x9dbc02b99ea4c385ef2835c23affe9cf84004b946b107b2351eee54f2c641f9b"
},
"storageProof": {
"type": "existing",
"index": 742,
"value": "0x...",
"nextIndex": 903,
"siblings": ["0xa1b2c3d4...", "0xe5f6a7b8...", "..."]
}
}
}Implementation Details:
- ZKsync OS stores the hash of account data in a virtual system contract with address
0x0000000000000000000000000000000000008003. - The method returns all the data needed to reconstruct this hash.
- Storage proofs are also sent, to be able to reconstruct proof this data using data posted in l1.
Verifying account data using Prividium SDK:
import { selectiveDisclosureActions, verifyAccountPropertiesProof } from 'prividium';
import type { Address } from 'viem';
import { createPublicClient, http } from 'viem';
const PRIVIDIUM_RPC_URL = process.env.PRIVIDIUM_RPC_URL || 'http://localhost:8000/rpc';
const L1_RPC_URL = process.env.L1_RPC_URL || 'http://localhost:5010';
const L1_DIAMOND_PROXY_ADDRESS = (process.env.L1_DIAMOND_PROXY_ADDRESS ||
'0x18f438bc08d755e164a7ae7c077e2ea93b0179ef') as Address;
export async function verifyAccountData(address: Address, blockNumber: bigint) {
const prividium = createPublicClient({ transport: http(PRIVIDIUM_RPC_URL) }).extend(selectiveDisclosureActions);
const l1Client = createPublicClient({ transport: http(L1_RPC_URL) });
const disclosure = await prividium.accountDataDisclosure(address, blockNumber);
const expectedBytecode = disclosure.bytecode === '0x' ? undefined : disclosure.bytecode;
const ok = await verifyAccountPropertiesProof(
disclosure,
l1Client,
L1_DIAMOND_PROXY_ADDRESS,
expectedBytecode,
address
);
return ok;
}2. Token Supply Disclosure
Reveals an ERC-20 token's total supply along with cryptographic storage proofs, enabling independent verification against the ZKsync state commitment.
Purpose:
- Provide verifiable transparency for token total supply without exposing individual holder balances.
- Enable auditors, DEXs, and aggregators to confirm supply figures on-chain.
What's Disclosed:
result: Hex-encoded total supply value returned bytotalSupply()callData: The ABI-encoded call used to retrieve the valuebatchNumber: The L1 batch number at which the proof was generatedstateCommitmentPreimage: ZKsync state commitment data for verificationl1VerificationData: Data required for L1 proof verificationproofs: Storage proofs for each contract slot read during execution
RPC Endpoint: prividium_tokenSupplyDisclosure
Request:
{
"method": "prividium_tokenSupplyDisclosure",
"params": ["0x1234567890123456789012345678901234567890", "0x1a2b3c"]
}Parameters:
params[0](required): ERC-20 token contract addressparams[1](required): Hex-encoded block number. Must be at or after the contract's disclosure start block.
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"result": "0x00000000000000000000000000000000000000000000000000000000000006e4",
"callData": "0x18160ddd",
"batchNumber": 175,
"stateCommitmentPreimage": {
"nextFreeSlot": "0x430",
"blockNumber": "0x1da",
"last256BlockHashesBlake": "0x7e2e68a1633628cf4ee6416a444a6af248e7cf975b3b744b6068beb14f8e98e4",
"lastBlockTimestamp": "0x69dda64c"
},
"l1VerificationData": {
"batchNumber": 175,
"numberOfLayer1Txs": 0,
"priorityOperationsHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"dependencyRootsRollingHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"l2ToL1LogsRootHash": "0x692f35c99f9c698852289ffecf07f6dd45770904521149d79aa85aae598fa375",
"commitment": "0x9dbc02b99ea4c385ef2835c23affe9cf84004b946b107b2351eee54f2c641f9b"
},
"proofs": [
{
"address": "0x7859dcea64a8f60d9ec8f31521574618d8ae8fe1",
"storageProofs": [
{
"key": "0x0000000000000000000000000000000000000000000000000000000000000003",
"proof": {
"type": "existing",
"index": 691,
"value": "0x00000000000000000000000000000000000000000000000000000000000006e4",
"nextIndex": 1056,
"siblings": ["0xf213959a...", "0x355510938b...", "..."]
}
}
]
}
]
}
}Implementation Details:
- Data Retrieval: Debug-traces a
totalSupply()call, collects touched storage slots, and gatherszks_getProoffor each slot. - Authorization: Permissions API's
checkErcTokenSupplyverifies supply disclosure is enabled for the contract.
Verifying a token supply disclosure using Prividium SDK:
import { selectiveDisclosureActions, verifyEthCallDisclosure } from 'prividium';
import { type Address, createPublicClient, http } from 'viem';
const PRIVIDIUM_RPC_URL = process.env.PRIVIDIUM_RPC_URL || 'http://localhost:8000/rpc';
const L1_RPC_URL = process.env.L1_RPC_URL || 'http://localhost:5010';
const L2_RPC_URL = process.env.L2_RPC_URL || 'http://localhost:5050';
const L1_DIAMOND_PROXY_ADDRESS = (process.env.L1_DIAMOND_PROXY_ADDRESS ||
'0x18f438bc08d755e164a7ae7c077e2ea93b0179ef') as Address;
export async function verifyTokenSupplyDisclosure(tokenAddress: Address, blockNumber: bigint) {
const prividium = createPublicClient({ transport: http(PRIVIDIUM_RPC_URL) }).extend(selectiveDisclosureActions);
const l1Client = createPublicClient({ transport: http(L1_RPC_URL) });
const l2Client = createPublicClient({ transport: http(L2_RPC_URL) });
const disclosure = await prividium.tokenSupplyDisclosure(tokenAddress, blockNumber);
const bytecodes = { [tokenAddress.toLowerCase()]: (await l2Client.getCode({ address: tokenAddress }))! };
const ok = await verifyEthCallDisclosure({
disclosure,
l1Client,
l2Client,
diamondAddress: L1_DIAMOND_PROXY_ADDRESS,
contractBytecodes: bytecodes,
batchNumber: disclosure.batchNumber
});
return ok;
}3. Token Balance Disclosure
Reveals the token balance of a specific holder address along with cryptographic storage proofs.
Purpose:
- Allow public verification of balances for pre-approved addresses (e.g., disclosed treasury or vesting wallets).
- Enable third parties to confirm a specific holder's balance without accessing private RPC.
What's Disclosed:
result: Hex-encoded balance value returned bybalanceOf(holderAddress)callData: The ABI-encoded call used to retrieve the valuebatchNumber: The L1 batch number at which the proof was generatedstateCommitmentPreimage: ZKsync state commitment data for verificationl1VerificationData: Data required for L1 proof verificationproofs: Storage proofs for each contract slot read during execution
RPC Endpoint: prividium_tokenBalanceDisclosure
Request:
{
"method": "prividium_tokenBalanceDisclosure",
"params": ["0x1234567890123456789012345678901234567890", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x1a2b3c"]
}Parameters:
params[0](required): ERC-20 token contract addressparams[1](required): Holder address to queryparams[2](required): Hex-encoded block number. Must be at or after the contract's disclosure start block.
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"result": "0x0000000000000000000000000000000000000000000000000000000000000064",
"callData": "0x70a0823100000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8",
"batchNumber": 175,
"stateCommitmentPreimage": {
"nextFreeSlot": "0x430",
"blockNumber": "0x1da",
"last256BlockHashesBlake": "0x7e2e68a1633628cf4ee6416a444a6af248e7cf975b3b744b6068beb14f8e98e4",
"lastBlockTimestamp": "0x69dda64c"
},
"l1VerificationData": {
"batchNumber": 175,
"numberOfLayer1Txs": 0,
"priorityOperationsHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"dependencyRootsRollingHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"l2ToL1LogsRootHash": "0x692f35c99f9c698852289ffecf07f6dd45770904521149d79aa85aae598fa375",
"commitment": "0x9dbc02b99ea4c385ef2835c23affe9cf84004b946b107b2351eee54f2c641f9b"
},
"proofs": [
{
"address": "0x7859dcea64a8f60d9ec8f31521574618d8ae8fe1",
"storageProofs": [
{
"key": "0x000000000000000000000000000000000000000000000000000000000000000a",
"proof": {
"type": "existing",
"index": 742,
"value": "0x0000000000000000000000000000000000000000000000000000000000000064",
"nextIndex": 903,
"siblings": ["0xa1b2c3d4...", "0xe5f6a7b8...", "..."]
}
}
]
}
]
}
}Implementation Details:
- Data Retrieval: Debug-traces a
balanceOf(holderAddress)call, collects touched storage slots, and gatherszks_getProoffor each slot. - Authorization: Permissions API's
checkBalanceDisclosureverifies that the holder address is in the contract's disclosed addresses list.
Verifying a token balance disclosure using Prividium SDK:
import { selectiveDisclosureActions, verifyEthCallDisclosure } from 'prividium';
import { type Address, createPublicClient, http } from 'viem';
const PRIVIDIUM_RPC_URL = process.env.PRIVIDIUM_RPC_URL || 'http://localhost:8000/rpc';
const L1_RPC_URL = process.env.L1_RPC_URL || 'http://localhost:5010';
const L2_RPC_URL = process.env.L2_RPC_URL || 'http://localhost:5050';
const L1_DIAMOND_PROXY_ADDRESS = (process.env.L1_DIAMOND_PROXY_ADDRESS ||
'0x18f438bc08d755e164a7ae7c077e2ea93b0179ef') as Address;
export async function verifyTokenBalanceDisclosure(tokenAddress: Address, holderAddress: Address, blockNumber: bigint) {
const prividium = createPublicClient({ transport: http(PRIVIDIUM_RPC_URL) }).extend(selectiveDisclosureActions);
const l1Client = createPublicClient({ transport: http(L1_RPC_URL) });
const l2Client = createPublicClient({ transport: http(L2_RPC_URL) });
const disclosure = await prividium.tokenBalanceDisclosure(tokenAddress, holderAddress, blockNumber);
const bytecodes = { [tokenAddress.toLowerCase()]: (await l2Client.getCode({ address: tokenAddress }))! };
const ok = await verifyEthCallDisclosure({
disclosure,
l1Client,
l2Client,
diamondAddress: L1_DIAMOND_PROXY_ADDRESS,
contractBytecodes: bytecodes,
batchNumber: disclosure.batchNumber
});
return ok;
}