block-proof.tact contract reconstructs a trusted StateInit by chaining masterchain, shard-block, and shard-state proofs. This verification path lets the vault validate a Jetton master’s state against a recent masterchain block without trusting raw account data from the message body.
What this contract proves
The DEX vault uses this logic when a Jetton wallet cannot be validated with a simpleStateInit proof. Given a StateProof, the contract proves that:
mcBlockSeqnostill belongs to the last 16 masterchain blocks available throughPREVMCBLOCKS.mcBlockHeaderProofmatches the trusted masterchain block hash.- The validated masterchain block contains the shard descriptor for the Jetton master’s address.
shardBlockHeaderProofmatches that shard block hash, and itsstate_updateexposes the trusted new shard-state hash.shardChainStateProofmatches that shard-state hash and contains the target account inShardAccounts, the shard-state dictionary keyed by account ID.
StateInit and uses it to derive the expected wallet address.
StateProof
Cryptographic trust chain
Each step depends on the hash extracted by the previous one. The contract never trusts an account state directly from the message body.How the validation logic works
The flow has two parts. An off-chain service assemblesStateProof, then the contract revalidates each hash on-chain.
Masterchain anchor
getJettonMasterState starts from PREVMCBLOCKS, which exposes the last 16 masterchain blocks to the TON Virtual Machine (TVM). The contract loads the newest block with getLastMcBlock(), checks that last.seqno - proof.mcBlockSeqno <= 16, and then retrieves the requested block with getMcBlockBySeqno(proof.mcBlockSeqno).
This step establishes the first trusted value: the masterchain block rootHash. Everything else in the message must match data reachable from that hash.
Masterchain proof validation
The helpervalidateMerkleProof performs the low-level proof check:
XCTOS unwraps the cell into a slice and reports whether it is exotic. The contract then parses the exotic payload as MerkleProof, checks the tag, compares the embedded hash field with the trusted hash, and returns the referenced content cell.
That returned cell is parsed as BlockHeader. The contract intentionally reads only the fields needed for the next checks:
stateUpdatefor shard-state validation later.extrato reachMcBlockExtraand the shard hashes.
Shard resolution
For masterchain blocks, the relevant shard metadata lives inMcBlockExtra, the masterchain-specific extension inside BlockExtra:
shardHashes.get(0)!!, so this path only works for the basechain. The resulting value is a BinTree ShardDescr, a binary tree whose leaves store shard descriptors. findShardInBinTree walks that tree with the Jetton master’s address bits:
- Parse the address as
VarAddress, the variable-length internal-address form, to get the hash part as aSlice. - For each of the first
shardBitLenbits, move left on0and right on1. - Skip the leaf tag bit.
- Parse the remaining slice as
ShardDescr.
ShardDescr.rootHash becomes the trusted shard block hash.
Shard-block proof validation
getShardAccounts repeats the same proof pattern for the shard block:
- Validate
shardBlockHeaderProofagainst the trusted shard block hash. - Parse the returned content as
BlockHeader. - Read
stateUpdateand unwrap it as an exoticMerkleUpdate. - Check that the tag is
4.
shardUpdate.newHash, which is the hash of the shard state after that block.
Off-chain proof preparation
The contract does not assembleStateProof itself. The off-chain service prepares the proof bundle before the vault call:
- Select a target masterchain block that is still available through
PREVMCBLOCKS. - Fetch the account state and proof cells with
getRawAccountState. - Patch the returned
AccountStateinto the pruned shard-state path. - Compute
shardBitLen, whichfindShardInBinTreelater uses as the shard-prefix length.
shardBitLen is derived from the masterchain proof depth:
BinTree ShardDescr depth from a masterchain-block Merkle proof. It does not derive a general rule for other proof layouts.
Shard-state proof composition
This contract uses a composed proof instead of provingShardState and AccountState separately.
The off-chain service first calls getRawAccountState, which returns:
- the serialized
AccountState; - a shard block proof;
- a shard-state proof where most branches are pruned.
validateMerkleProof(shardChainStateProof, shardUpdate.newHash) returns the patched ShardState. The contract then loads the second reference from ShardStateUnsplit, the unsplit shard-state layout used by this proof flow, which contains the ShardAccounts dictionary. The contract does not rebuild that branch itself. It expects shardChainStateProof to already contain the restored account-state path.
Account lookup and state reconstruction
The final lookup happens insideShardAccounts:
- Parse the Jetton master address into its 256-bit account ID.
- Load the augmented hashmap from
ShardAccounts. - Run
augHashmapLookup(..., 256)with that account ID. - Fail if the account is not present.
ShardAccount is then converted into StateInit by parseStateFromShardAccount. The function assumes the account is active, takes the last two references from Account, and interprets them as code and data.
StateInit to derive the expected Jetton wallet address and compare it with sender().
Trade-offs and assumptions
- Basechain only.
getShardRootHashalways readsshardHashes.get(0). - Recent masterchain blocks only.
PREVMCBLOCKSexposes only the last 16 masterchain blocks. ShardStateUnsplitonly. The code does not handle split shard states.- Active accounts only.
parseStateFromShardAccountassumesAccountActive. - No
StateInitlibrary support. The parser reads onlycodeanddata. - Block-boundary state only. The proof reconstructs the shard state after a specific block, not an intermediate execution state.