← Back to Blog

On-Chain Meets Off-Chain: Verifiable State Reconciliation

May 14, 2026 | 15 min read | Engineering

Every system that bridges on-chain and off-chain state has a reconciliation problem. A tokenized securities platform reads smart contract balances from Ethereum, reads NAV calculations from an off-chain valuation engine, reads KYC status from a compliance database, and must reconcile all three into a single coherent view: this investor holds X tokens, the tokens are worth Y per unit, and the investor is cleared to hold them. If any of these states disagree, the system is in an inconsistent state. If the inconsistency is not detected, the consequences range from incorrect portfolio reporting to regulatory violation.

The reconciliation itself is a computation. It reads from multiple sources, applies logic (do the on-chain balances match the off-chain records? is the token supply consistent with the NAV? is the investor still KYC-cleared?), and produces a result: reconciled or not reconciled, with details. In most systems today, this reconciliation runs as a cron job. It reads on-chain state via an RPC node, reads off-chain state from a database, compares them, and writes the result to another database. If the reconciliation finds a discrepancy, it might send an alert. If it finds no discrepancy, it logs "reconciliation passed" and moves on.

The problem is that the reconciliation result itself is unverifiable. A log entry that says "reconciliation passed at block 19,847,223" does not prove that the reconciliation actually checked the right data, applied the right logic, or produced the right result. It is a claim, not a proof. An operator who wants to cover up a discrepancy can modify the reconciliation logic, re-run it, and produce a "passed" result. An attacker who compromises the reconciliation service can produce false results. A bug in the reconciliation code can produce incorrect results that go undetected because nobody can independently verify them.

2
State Domains (On-Chain + Off-Chain)
0
Proof of Reconciliation Correctness
12s
Avg Ethereum Block Time (State Moves)

The Two-State Problem

On-chain state and off-chain state are fundamentally different data models. On-chain state is globally replicated, deterministically computed, and immutably recorded. When a smart contract says the token supply is 1,000,000, every node in the network agrees, and the history of how it got to 1,000,000 is publicly verifiable. Off-chain state is locally stored, computationally derived, and mutable. When a valuation engine says the NAV is $12.47, that number exists in a database on a specific server, and the history of how it was computed depends on whatever logging the system provides.

Reconciling these two state models means comparing a high-integrity source (the blockchain) with a low-integrity source (the database) and producing a result that has the integrity of neither. The reconciliation result is stored off-chain, so it inherits the mutability and opacity of the off-chain world. Even though one of its inputs is immutable on-chain data, the reconciliation output is just another database entry that can be modified without detection.

This is the fundamental architectural flaw in every on-chain/off-chain bridge. The bridge takes immutable, verifiable data from the blockchain and combines it with mutable, opaque data from off-chain systems, and the result is mutable and opaque. The integrity of the blockchain is lost at the bridge boundary. The reconciliation is supposed to detect inconsistencies between the two worlds, but the reconciliation itself lives in the low-integrity world and cannot prove its own correctness.

Where Reconciliation Breaks in Practice

Consider a tokenized securities platform where tokens represent fractional ownership of a bond portfolio. The on-chain state includes token balances per address, total token supply, and transfer history. The off-chain state includes the bond portfolio NAV, per-investor KYC status, accredited investor verification, and distribution calculations. The reconciliation must verify that the total token supply on-chain matches the shares outstanding off-chain, that every token holder is KYC-cleared, and that the NAV per token published on-chain matches the off-chain NAV calculation.

In practice, this reconciliation breaks in three ways. First, timing mismatches: the on-chain state is read at block N, but the off-chain state was computed at time T, and block N might not correspond to time T. A transfer that occurs between the off-chain computation and the on-chain read produces a false discrepancy. Second, source ambiguity: the reconciliation reads on-chain state from an RPC node, but which RPC node? If it reads from a node that is behind the chain tip, it gets stale state. If the node has a different view due to a reorg, it gets incorrect state. Third, logic opacity: when the reconciliation passes, nobody can verify that it checked the right things. When it fails, nobody can determine exactly which check failed and why, because the reconciliation logic is a black box that produces a boolean result.

Your Reconciliation Is a Black Box

A cron job that reads on-chain state, reads off-chain state, compares them, and writes "reconciliation passed" to a database is not auditable. It is not verifiable. It does not prove what data it read, what logic it applied, or whether the result is correct. An auditor looking at a database full of "reconciliation passed" entries has no way to distinguish genuine passes from forged ones, correct logic from buggy logic, or current-state reads from stale-state reads.

Cachee Approach: Fingerprinted Reconciliation

The solution is to treat reconciliation as a computation and apply computation fingerprinting to it. The reconciliation reads on-chain state at a specific block height and off-chain state at a specific version, applies reconciliation logic, and produces a result. The fingerprint binds the result to the exact inputs: SHA3-256(block_height || block_hash || on_chain_state_hash || off_chain_state_version || off_chain_state_hash || reconciliation_logic_version || parameters || timestamp).

This fingerprint transforms the reconciliation from a claim into a proof. If anyone wants to verify the reconciliation, they can retrieve the on-chain state at the specified block height (which is publicly verifiable), retrieve the off-chain state at the specified version (which is stored in Cachee's temporal version history), re-run the reconciliation logic at the specified version, and check that they get the same result. The fingerprint proves that the reconciliation was performed with these specific inputs. The triple PQ signatures prove that the result has not been modified since it was computed.

TemporalBinding: Anchoring to Block Height

The timing mismatch problem -- reconciling on-chain state at block N with off-chain state at time T -- is solved by TemporalBinding. Every reconciliation result is bound to a specific block height and a specific off-chain state version. The binding is part of the computation fingerprint, so it cannot be changed after the fact. The reconciliation does not claim "these states were reconciled at some point." It claims "the on-chain state at block 19,847,223 was reconciled with the off-chain state version ae3f7b, and the result was X." Both the block height and the state version are verifiable: the block height can be checked against any archive node, and the state version can be checked against Cachee's temporal history.

TemporalBinding also solves the problem of reorgs. If the on-chain state at block N changes due to a reorg (which can happen on any proof-of-work chain and in rare edge cases on proof-of-stake chains), the reconciliation fingerprint includes the block hash, not just the block height. A reorg that changes the block at height N will produce a different block hash, which means the fingerprint is no longer valid. The system detects this automatically and triggers a re-reconciliation against the new canonical block at that height. The re-reconciliation produces a new fingerprint, and both the original and the re-reconciliation are preserved in the audit log, creating a complete record of the state change.

Merkle Proof Anchoring

For the highest level of provenance, reconciliation results can be anchored on-chain via Merkle proof. A batch of reconciliation fingerprints is hashed into a Merkle tree, and the Merkle root is written to a smart contract. This creates a bridge in the opposite direction: instead of reading on-chain state into off-chain computation, you are writing off-chain computation results back on-chain. The Merkle root on-chain proves that these specific reconciliation results existed at this specific block height. Any individual reconciliation result can be verified against the Merkle root using its Merkle proof, without revealing the other results in the batch.

This bidirectional anchoring -- on-chain state read into off-chain computation, off-chain computation anchored back on-chain -- creates a closed loop of verifiability. The on-chain state is verifiable because it is on-chain. The off-chain computation is verifiable because it is fingerprinted and signed. The reconciliation is verifiable because it binds the two together with a fingerprint. And the anchoring makes the reconciliation itself verifiable on-chain. There is no gap in the verification chain.

Use Cases

Tokenized Securities NAV Reconciliation

A tokenized bond fund issues tokens on Ethereum. The fund's NAV is computed off-chain using bond prices, accrued interest, FX rates, and fee calculations. The NAV per token is published on-chain via an oracle. The reconciliation must verify that the on-chain NAV matches the off-chain NAV, that the on-chain token supply matches the off-chain shares outstanding, and that the total on-chain value (supply times NAV per token) matches the off-chain total NAV.

With Cachee, each component is fingerprinted independently. The off-chain NAV computation has its own fingerprint (as described in the NAV audit trail post). The on-chain state read has its own fingerprint binding it to a specific block height and block hash. The reconciliation has its own fingerprint binding it to both inputs. An auditor can verify each component independently: verify the NAV computation, verify the on-chain state read, and verify the reconciliation logic. If any component fails verification, the auditor knows exactly which one and why.

Stablecoin Reserve Verification

A stablecoin issuer claims that every token is backed by one dollar of reserves. The on-chain state is the token supply (publicly verifiable). The off-chain state is the reserve balance at a bank (reported by the issuer or an attestation firm). The reconciliation must verify that the reserve balance equals or exceeds the token supply. Today, this reconciliation is published as a monthly attestation letter from an accounting firm. The letter says "as of [date], we observed that the reserve balance was $X and the token supply was Y, and X >= Y." The letter does not prove that the observation was correct, timely, or tamper-free.

With Cachee, the reserve balance is cached with a computation fingerprint that binds it to the bank's API response, the query timestamp, and the API version. The token supply is cached with a fingerprint binding it to the specific block height and block hash. The reconciliation fingerprint binds both together. The result is signed with three PQ algorithms. The entire chain -- reserve observation, token supply observation, reconciliation logic, and result -- is independently verifiable. A user does not need to trust the attestation firm. They can verify the math themselves using the CAB bundles.

Cross-Chain Bridge State Proof

Cross-chain bridges lock assets on one chain and mint synthetic assets on another. The bridge must continuously verify that the locked assets on Chain A match the minted assets on Chain B. If they diverge, the bridge is insolvent. Most bridge exploits in 2024 and 2025 involved state reconciliation failures: the bridge's off-chain relayer reported incorrect state, and the destination chain minted unbacked assets.

Cachee adds a verification layer to bridge reconciliation. The locked asset state on Chain A is cached with a fingerprint binding it to the specific block height and block hash on Chain A. The minted asset state on Chain B is cached with a fingerprint binding it to the specific block height and block hash on Chain B. The reconciliation fingerprint binds both together and includes the bridge contract addresses, the asset identifiers, and the reconciliation logic version. If the locked and minted amounts diverge, the reconciliation result reflects the discrepancy, and the fingerprint proves exactly which block heights were compared and what the states were at those heights.

This does not prevent bridge exploits by itself -- if the bridge's relayer is compromised, it can still submit incorrect state transitions. But it creates an auditable record of every reconciliation that was performed, making it possible to detect when a discrepancy first appeared, what state was reported at that time, and whether the reported state matched the actual on-chain state. Post-incident forensics become tractable because the reconciliation history is cryptographically committed, not just logged.

Implementation: Reconciliation with TemporalBinding

import json
import hashlib
from datetime import datetime, timezone
from web3 import Web3
from cachee import CacheeClient, ComputationFingerprint, TemporalBinding

client = CacheeClient(
    host="cachee.internal:6380",
    attestation=True,
    key_type="Owner",
    audit_log=True
)

w3 = Web3(Web3.HTTPProvider("https://eth-mainnet.alchemyapi.io/v2/KEY"))

class StateReconciler:
    """
    Reconciles on-chain token state with off-chain NAV state.
    Every reconciliation is fingerprinted and temporally bound
    to specific block height + off-chain state version.
    """

    RECONCILIATION_VERSION = "recon-v2.4.0"

    def __init__(self, token_address: str, fund_id: str):
        self.token_address = token_address
        self.fund_id = fund_id

    def read_onchain_state(self) -> dict:
        """Read on-chain state at current block, with block binding."""
        block = w3.eth.get_block("latest")
        contract = w3.eth.contract(
            address=self.token_address,
            abi=self._load_abi()
        )

        total_supply = contract.functions.totalSupply().call(
            block_identifier=block.number
        )
        nav_oracle = contract.functions.navPerToken().call(
            block_identifier=block.number
        )

        state = {
            "block_number": block.number,
            "block_hash": block.hash.hex(),
            "block_timestamp": block.timestamp,
            "total_supply": total_supply,
            "nav_per_token_onchain": nav_oracle,
            "total_value_onchain": total_supply * nav_oracle,
            "read_at": datetime.now(timezone.utc).isoformat()
        }

        # Cache on-chain state with block binding
        fp = ComputationFingerprint(
            input_data={
                "block_number": block.number,
                "block_hash": block.hash.hex(),
                "contract": self.token_address
            },
            computation="onchain_state_read",
            parameters={"chain": "ethereum", "network": "mainnet"},
            version=self.RECONCILIATION_VERSION,
            hardware_class="production"
        )

        client.set_with_fingerprint(
            key=f"onchain:{self.token_address}:{block.number}",
            value=json.dumps(state),
            fingerprint=fp,
            ttl=86400
        )

        return state

    def read_offchain_state(self) -> dict:
        """Read off-chain NAV state from Cachee temporal store."""
        nav_entry = client.temporal_get(
            key=f"nav:{self.fund_id}",
            timestamp=datetime.now(timezone.utc).isoformat()
        )

        return {
            "nav_total": json.loads(nav_entry.value)["nav_total"],
            "nav_per_token": json.loads(nav_entry.value)["nav_per_token"],
            "tokens_outstanding": json.loads(nav_entry.value)["tokens_outstanding"],
            "methodology_version": json.loads(nav_entry.value)["methodology_version"],
            "computed_at": json.loads(nav_entry.value)["computed_at"],
            "state_version": nav_entry.fingerprint,
            "state_verified": nav_entry.signatures_valid
        }

    def reconcile(self) -> dict:
        """
        Reconcile on-chain and off-chain state.
        Result is fingerprinted with TemporalBinding to
        specific block height + off-chain state version.
        """
        onchain = self.read_onchain_state()
        offchain = self.read_offchain_state()

        # Perform reconciliation checks
        checks = {
            "supply_match": (
                onchain["total_supply"] == offchain["tokens_outstanding"]
            ),
            "nav_match": abs(
                onchain["nav_per_token_onchain"] - offchain["nav_per_token"]
            ) < 0.001,  # tolerance for rounding
            "total_value_match": abs(
                onchain["total_value_onchain"] - offchain["nav_total"]
            ) < 1.0,    # $1 tolerance
            "offchain_verified": offchain["state_verified"]
        }

        result = {
            "fund_id": self.fund_id,
            "reconciled": all(checks.values()),
            "checks": checks,
            "onchain_block": onchain["block_number"],
            "onchain_block_hash": onchain["block_hash"],
            "offchain_state_version": offchain["state_version"],
            "onchain_nav": onchain["nav_per_token_onchain"],
            "offchain_nav": offchain["nav_per_token"],
            "onchain_supply": onchain["total_supply"],
            "offchain_supply": offchain["tokens_outstanding"],
            "reconciled_at": datetime.now(timezone.utc).isoformat(),
            "reconciliation_version": self.RECONCILIATION_VERSION
        }

        # Create TemporalBinding to block height + state version
        binding = TemporalBinding(
            block_height=onchain["block_number"],
            block_hash=onchain["block_hash"],
            offchain_version=offchain["state_version"],
            timestamp=result["reconciled_at"]
        )

        # Fingerprint the reconciliation
        fp = ComputationFingerprint(
            input_data={
                "onchain_hash": hashlib.sha3_256(
                    json.dumps(onchain, sort_keys=True).encode()
                ).hexdigest(),
                "offchain_hash": hashlib.sha3_256(
                    json.dumps(offchain, sort_keys=True).encode()
                ).hexdigest(),
                "block_height": onchain["block_number"],
                "block_hash": onchain["block_hash"]
            },
            computation="state_reconciliation",
            parameters={"tolerance_nav": 0.001, "tolerance_value": 1.0},
            version=self.RECONCILIATION_VERSION,
            hardware_class="production"
        )

        # Cache with binding and attestation
        client.set_with_fingerprint(
            key=f"recon:{self.fund_id}:{onchain['block_number']}",
            value=json.dumps(result),
            fingerprint=fp,
            temporal_binding=binding,
            ttl=0  # reconciliation records never expire
        )

        return result


# --- Run reconciliation ---
reconciler = StateReconciler(
    token_address="0xABCD...1234",
    fund_id="alpha-re-fund-001"
)
result = reconciler.reconcile()

if result["reconciled"]:
    print(f"Reconciled at block {result['onchain_block']}")
else:
    failed = [k for k, v in result["checks"].items() if not v]
    print(f"DISCREPANCY at block {result['onchain_block']}: {failed}")

State Anchoring: On-Chain Proof of Off-Chain Computation

The reconciliation fingerprint exists off-chain in Cachee. For many use cases, this is sufficient: an auditor can verify the fingerprint chain, check the signatures, and confirm the reconciliation history. But some use cases require on-chain proof that the reconciliation occurred. Stablecoin reserve verification, for example, benefits from publishing the reconciliation proof on-chain so that any token holder can verify it without contacting the issuer.

Cachee supports Merkle proof anchoring for this purpose. A batch of reconciliation fingerprints -- say, all reconciliations performed in the last hour -- is hashed into a Merkle tree. The Merkle root is published to a smart contract on the target chain. Any individual reconciliation fingerprint can then be verified against the Merkle root using its Merkle proof (a set of sibling hashes in the tree). This verification is cheap: it requires computing a few SHA3-256 hashes and comparing the result to the on-chain Merkle root. No access to Cachee is required. The verification is entirely on-chain.

This creates a trust bridge between on-chain and off-chain worlds. The on-chain Merkle root proves that a specific set of reconciliations existed at a specific block height. The Merkle proof proves that a specific reconciliation is part of that set. The computation fingerprint proves what inputs the reconciliation consumed. The triple PQ signatures prove that the fingerprint has not been modified. The temporal binding proves which block height and off-chain state version were reconciled. Every link in this chain is independently verifiable, and no single party needs to be trusted.

Handling State Divergence

When reconciliation detects a discrepancy between on-chain and off-chain state, the response depends on the type and severity of the discrepancy. Cachee's fingerprinting enables precise diagnostic because every reconciliation records exactly what was compared and what diverged.

Supply mismatch: The on-chain token supply does not match the off-chain shares outstanding. This typically indicates a transfer that occurred after the off-chain state was computed but before the on-chain state was read. The fingerprint records both the off-chain computation timestamp and the on-chain block timestamp, making it easy to identify timing-related false discrepancies. The system can automatically re-reconcile using the off-chain state version that corresponds to the on-chain block timestamp.

NAV mismatch: The on-chain NAV (from the oracle) does not match the off-chain NAV (from the valuation engine). This could indicate an oracle update delay, a methodology difference, or a genuine error. The fingerprint records both NAV values, the off-chain methodology version, and the oracle contract address, enabling root cause analysis. If the oracle simply has not updated yet, the reconciliation records the discrepancy and its likely cause. If the methodology versions differ, the fingerprint makes this immediately visible.

Verification failure: The off-chain state fails signature verification -- the PQ signatures on the cached NAV are invalid. This indicates either tampering or key rotation without re-signing. The fingerprint records the verification failure, and the audit log preserves the failed verification as evidence. This is the kind of failure that should trigger an immediate alert, because it suggests that the off-chain state may have been modified.

Continuous Reconciliation vs. Periodic Attestation

Traditional stablecoin reserve attestations are performed monthly by an accounting firm. The firm observes the reserve balance on a specific date, observes the token supply on the same date, and issues a letter. Between attestation dates, the reserve could be drawn down and replenished without anyone knowing. The attestation provides a point-in-time snapshot, not continuous assurance.

Cachee-backed reconciliation runs continuously. Every block (or every N blocks, depending on the desired frequency), the system reads on-chain state, reads off-chain state, reconciles them, and caches the result with a fingerprint and temporal binding. The result is a continuous, hash-chained record of every reconciliation, not a monthly snapshot. If the reserve drops below the token supply for even one block, the reconciliation record shows it. The hash chain proves that the record has not been modified after the fact.

This continuous reconciliation model is what regulators are moving toward. The SEC's proposed custody rule for digital assets contemplates continuous reporting, not periodic attestation. MiCA requires "ongoing compliance," not quarterly audits. The infrastructure to support continuous, verifiable reconciliation needs to exist before the regulations require it. Building reconciliation on a system that produces verifiable computations by default means the compliance infrastructure is ready when the regulations arrive.

Reconciliation as Verifiable Computation

On-chain state is verifiable by construction. Off-chain computation can be made verifiable through computation fingerprinting. Reconciliation bridges the two by binding off-chain computation to specific on-chain state with cryptographic proofs. TemporalBinding anchors each reconciliation to a block height and off-chain state version. Merkle proof anchoring publishes reconciliation proofs on-chain. The result is a closed verification loop where every component -- on-chain state, off-chain computation, and the reconciliation between them -- is independently verifiable. No trust required. Check the math.

Architecture for Production Reconciliation

A production reconciliation system operates as a pipeline with four stages. Stage one reads on-chain state from an archive node (not a standard RPC node, because archive nodes retain historical state at every block height, enabling re-verification). Stage two reads off-chain state from Cachee's temporal version store. Stage three executes the reconciliation logic and produces a fingerprinted result. Stage four anchors the result -- either in Cachee's hash-chained audit log for off-chain verification, or via Merkle proof on-chain for public verification.

Each stage produces its own fingerprint. The on-chain state read is fingerprinted with the block height, block hash, and contract addresses. The off-chain state read is fingerprinted with the state version and source. The reconciliation is fingerprinted with both input fingerprints plus the reconciliation logic version. The anchoring is fingerprinted with the reconciliation fingerprint plus the Merkle tree parameters. This layered fingerprinting creates a complete data lineage from raw on-chain data to published proof, with every transformation step independently verifiable.

The pipeline runs on a configurable schedule: every block for high-value assets, every 10 blocks for medium-value assets, every 100 blocks for low-value assets. The schedule is part of the cache contract, which specifies the reconciliation freshness SLA for each asset type. If the pipeline falls behind schedule, the cache contract reports a violation, and the system can alert operators or pause trading until reconciliation catches up.

The reconciliation pipeline itself is stateless. All state is in Cachee (off-chain state versions, reconciliation history, audit log) and on-chain (token balances, oracle values, Merkle roots). The pipeline can be stopped, restarted, scaled horizontally, or migrated to a different host without losing any reconciliation history. The history is cryptographically committed in Cachee's hash chain, not in the pipeline's local state. This makes the reconciliation infrastructure itself auditable: an auditor can verify that the pipeline has been running continuously by checking the hash chain for gaps, without needing access to the pipeline's infrastructure.

Bridge on-chain and off-chain state with cryptographic proof. Every reconciliation fingerprinted, temporally bound, and independently verifiable.

Get Started Verifiable Computation Docs