Skip to main content

How exchanges prove proof of reserves onchain

Kraken, Binance, and OKX prove reserves with a Merkle tree of customer balances and signed wallets, at one snapshot each. How the Merkle proof works, what each exchange publishes, and how an onchain check runs between reports on Goldsky.

How exchanges prove proof of reserves onchain cover image
Written by
image of Elliott Charbonneau
Elliott Charbonneau

Head of Operations

A centralized exchange proves reserves in 3 steps. A third party or the exchange itself builds a Merkle tree of every customer balance. A customer can check their own balance sits inside the published total. The exchange signs a message from its wallet addresses to prove it controls the funds the tree claims. Kraken, Binance, and OKX each publish a version of this process. None of their current pages names an outside audit firm.

Merkle-tree liability proofs

A Merkle-tree liability proof lets a customer check their balance is inside an exchange's reported total. It does this without showing any other customer's balance.

The exchange, or the party it names, takes a snapshot of every account balance and hashes each one into a leaf. It pairs leaves and hashes each pair into a parent. It repeats the pairing until one hash remains: the Merkle root. The exchange publishes that root.

A worked example with 4 customers: A holds 1 BTC, B holds 2 BTC, C holds 0.5 BTC, D holds 3 BTC. The process hashes each balance into a leaf. It hashes leaf A with leaf B into parent AB, and leaf C with leaf D into parent CD. It hashes AB with CD into the root. Customer A can rebuild the root from leaf A, leaf B, and parent CD, the 3 values handed back as a proof path. A match between the rebuilt hash and the published root means the tree counted A's balance. A change to any balance in the tree changes the root, which is how Kraken describes the method.

Merkle tree for 4 customers. Customer A receives leaf B and parent CD as hashes, computes parent AB and the root, and compares the result with the published root

How exchanges prove reserves today

Kraken, Binance, and OKX each publish a Merkle-tree liability proof and a set of signed wallet addresses. They differ on who checks the numbers and how often.

Kraken's proof-of-reserves page (opens in a new tab) states the review is "performed by an independent 3rd party accountant," without naming the firm. That accountant builds the tree from a snapshot of spot, margin, staking, and futures balances. It then compares Kraken's signed onchain wallet balances against the tree's total. Kraken's page showed a snapshot dated June 30, 2026, read 2026-11-09.

Binance builds the same kind of tree (opens in a new tab), then adds a zk-SNARK circuit. The circuit checks 3 things for each leaf. It checks that the balance is part of the total, that the balance is not negative, and that a changed leaf changes the root honestly. Binance's own page names no external auditor for this process. A user checks their own leaf and Record ID directly on Binance's site.

OKX publishes a Merkle tree and a liability report (opens in a new tab), now audited through a zk-STARK proof instead of a plain Merkle check. OKX's 27th report, from January 2025, named Hacken as the audit firm (OKX Learn, "Our 27th consecutive Proof of Reserves" (opens in a new tab)). OKX's own current pages, including its 49th report, read 2026-11-09, name no audit firm and point instead to OKX's own open-source verification tools.

Coinbase takes a different route. As a public company (opens in a new tab), Coinbase points to its quarterly external auditor review and its annual SEC filings, instead of publishing a Merkle-tree liability proof. Coinbase's Chief Security Officer wrote this on November 25, 2022.

Each of these proofs answers one question: did the exchange hold enough of an asset at one moment, for the customers who checked. Proof-of-reserves pages drew wide attention across the industry after FTX collapsed in November 2022, Coinbase wrote at the time. None of the 3 processes above runs on a continuous basis. The days between snapshots carry no fresh public signal.

Three ways to check reserves, compared

ApproachWhat it provesHow oftenWho runs itWhat it cannot prove
Attestation reportFinancial statements and cold-storage address ownership match claimed holdings, reviewed by an external auditor, at one point in time (Coinbase's blog, 2022)Quarterly external review, annual SEC audit (Coinbase's blog)An independent external auditor paid by the exchangeWhether the ratio held on any date other than the audit date (Coinbase's blog)
Merkle liability proofA specific customer balance is inside the published total, and signed wallet balances meet or exceed that total, at the snapshot date (Kraken's proof-of-reserves page)Kraken: no fixed interval stated on its own page, roughly quarterly across recent blog posts. OKX: monthly, its 49th report as of this runAn unnamed third-party accountant (Kraken) or OKX's own open-source tooling, after naming Hacken in earlier reportsOff-chain liabilities such as lending, and the ratio between snapshots (Coinbase's blog, 2022)
Continuous onchain checkOnchain token supply compared against a custodian's reported reserve figure, on every scheduled run (Goldsky's proof-of-reserves solutions page)As often as every few minutes, for example a 5-minute cron schedule in Goldsky's docs (Goldsky's treasury and reserves guide)The issuer's own team, using its own custodian connection and threshold (Goldsky's solutions page)The accuracy of the custodian's reported figure itself, since the check reads the number the custodian's API returns (Goldsky's docs)

A reader comparing these 3 rows sees the same gap each time. Every method proves a number at one point, and only the third row updates that number on a set schedule.

Hours between reserve checks, log scale: onchain check 5 minutes, OKX monthly Merkle proof 720 hours, quarterly attestation 2,160 hours

What an onchain check adds between attestations

A Merkle-tree proof and a signed-wallet check answer whether an exchange held enough assets on one date. Neither one answers whether the ratio stayed healthy since. An onchain check closes that gap. It reads wallet balances from the chain on a schedule. It compares them against reported liabilities, with an alert if the two fall out of line.

Chainlink runs one version of this (opens in a new tab) as a published feed. A Chainlink Proof of Reserve feed reports a reserve figure any contract can read. The figure comes from a third-party auditor, a custodian's direct feed, or the token issuer's own self-reported API. Chainlink's own documentation flags the self-reported path as carrying more risk. It states Chainlink Labs is not responsible for the accuracy of a self-reported number.

A feed like this gives any application a number to read on every update. It does not say who runs the underlying check or how often it refreshes, beyond what the feed's own documentation states.

Run it on Goldsky

A team builds a continuous reserve check on Goldsky with 2 pieces. A Turbo pipeline decodes a token's mint and burn events into a supply ledger. A Compose task reads that ledger, calls the custodian's API, and records the result. Goldsky explains why a team picks Turbo Pipelines over the retired Mirror product in its Turbo vs Mirror guide.

Continuous reserve check on Goldsky: a Turbo pipeline builds a supply ledger, a Compose task compares it with the custodian's reserve figure and records the ratio onchain

The pipeline below checks a token's own supply against its reserves, copied from Goldsky's proof-of-reserves and treasury guide (opens in a new tab):

yaml
name: tip20-supply-monitor
resource_size: s

sources:
  tempo_tip20_logs:
    type: dataset
    dataset_name: tempo.raw_logs
    version: 1.0.0
    start_at: latest
    filter: address = lower('0xYOUR_TIP20_TOKEN')

transforms:
  decoded:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        address AS token,
        _gs_log_decode(
          _gs_fetch_abi('https://raw.githubusercontent.com/your-org/abis/main/tip20.json', 'raw'),
          topics,
          data
        ) AS decoded,
        block_timestamp,
        transaction_hash,
        _gs_op
      FROM tempo_tip20_logs

  supply_events:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        token,
        decoded.event_signature AS event,
        CAST(decoded.event_params[2] AS DECIMAL(38, 0)) AS amount,
        to_timestamp(block_timestamp) AS at,
        transaction_hash,
        _gs_op
      FROM decoded
      WHERE decoded IS NOT NULL
        AND decoded.event_signature IN ('Mint', 'Burn')

sinks:
  supply_ledger:
    type: postgres
    from: supply_events
    schema: treasury
    table: supply_events
    secret_name: MY_POSTGRES
    primary_key: id

This example tracks a single issuer's TIP-20 token on Tempo. The pipeline decodes Mint and Burn events into a table. Circulating supply becomes the running total of mints minus burns, held as a SQL view. An exchange wants the same check for its own wallet balances, not a token's total supply. It points the source at its own deposit and hot-wallet addresses. It reads balances directly there, instead of decoding Mint and Burn events. The same mint-and-burn pattern appears in Goldsky's stablecoin issuance data post, applied to USDC and USDT.

A Compose task reads the supply total with wallet.readContract. It fetches the custodian's reserve figure, computes the ratio, and writes a healthy ratio onchain with wallet.writeContract. Below the threshold, the same task posts an alert. It can also pause the token if it holds the token's pause permission. A team sets the schedule itself, for example every 5 minutes in Goldsky's docs example.

This turns a point-in-time Merkle proof into a ratio that updates on a set schedule, with an alert before the gap between checks widens. A team that wants the ratio readable by other contracts can publish it through a Chainlink-compatible interface. Goldsky's custom oracle guide describes that same publishing pattern for a fund's net asset value.

FAQ

Does a Merkle-tree proof show an exchange is solvent?
No. It shows that signed wallet balances met or exceeded the balances in the tree at one snapshot. It does not cover off-chain liabilities such as lending, per Coinbase's own 2022 breakdown of proof-of-reserves methods.

How often do Kraken, Binance, and OKX publish a proof?
OKX publishes monthly, with its 49th report read on 2026-11-09. Kraken's own page does not state a fixed publishing interval. Binance's own proof-of-reserves page also does not state a fixed publishing interval.

What does an onchain check add that a Merkle proof does not?
A reading on a schedule a team sets, for example every 5 minutes in Goldsky's treasury and reserves guide. A Merkle proof only gives a reading at the moment of each published report.

Can the onchain check pause a token automatically?
Yes, if the team gives the Compose task the token's pause permission. Otherwise a shortfall only sends an alert, per Goldsky's proof-of-reserves solutions page.

Does Chainlink Proof of Reserve replace an exchange's own Merkle-tree proof?
No. A Chainlink Proof of Reserve feed publishes a reserve figure for any contract to read. The figure comes from a third party, a custodian, or the issuer. It answers whether total reserves back a token, not whether one customer's balance sat inside a liability tree.

© Endless Sky Inc. All rights reserved.

PrivacyTermsSecurity

System status