A daily reserve coverage reconciliation tool: compares a synthetic exchange's customer-liability ledger against public on-chain wallet balances and flags coverage breaches.
Built as a portfolio demonstration to mirror the daily safeguarding workflow at a regulated crypto exchange. Synthetic data; not an audit of any real entity.
- Aggregates customer liabilities by asset from a 100-customer synthetic ledger using DuckDB SQL.
- Pulls live reserve-wallet balances from public block explorers (mempool.space for BTC, Etherscan for ETH and ERC-20s).
- Computes per-asset coverage ratios, flags exceptions (OK / WATCH / BREACH), and renders both an HTML treasury dashboard and an Excel workbook.
flowchart LR
ledger["ledger.csv"] --> aggregate["DuckDB liability summary"]
wallets["wallets.yaml"] --> chain["public chain APIs"]
prices["price snapshots"] --> reconcile["reconcile.py"]
aggregate --> reconcile
chain --> reconcile
reconcile --> html["HTML dashboard"]
reconcile --> xlsx["Excel workbook"]
git clone https://github.com/austingnarley/customer-liability-recon.git
cd customer-liability-recon
python3 -m venv .venv
source .venv/bin/activate
cp .env.example .env
# Add your free Etherscan API key to .env, or export ETHERSCAN_API_KEY in your shell.
make install
make demoOutput lands in output/recon_<date>.html and output/recon_<date>.xlsx.
src/ledger.py generates and validates the synthetic customer ledger, while
src/sql/liability_summary.sql performs the DuckDB aggregation. src/chain.py
fetches public wallet balances with retry, rate limiting, and SQLite caching.
src/reconcile.py applies the treasury logic, and src/report.py renders the
HTML and Excel outputs.
make test runs the suite. Every push runs lint + tests in CI. A scheduled
workflow runs the reconciliation daily at 13:00 UTC and commits the new report.
For the daily workflow, add ETHERSCAN_API_KEY as a repository secret and allow
GitHub Actions to write repository contents. The workflow uses contents: write
and force-adds report files because local generated reports are ignored.
- The customer ledger is synthetic. Real exchanges have far more assets and more complex liability structures, including margin, futures, staking rewards, and custodial versus non-custodial balances.
- Wallet addresses are public block-explorer demo data, not a real exchange's reserves. Choosing wallets tagged as belonging to a specific exchange would be misleading.
- Price snapshots are static. A production system would integrate with a controlled price source or oracle.
- Coverage thresholds (OK >= 100%, WATCH >= 99%, BREACH < 99%) are illustrative. Real thresholds are set by regulation, internal risk appetite, and control design.
- This is a portfolio demonstration, not a proof-of-reserves attestation or a claim about any institution's solvency.
After four years in bank and payments operations at MUFG, U.S. Bank, and Escrow.com, I wanted a hands-on artifact that connects traditional reconciliation rigor to on-chain reserve verification. This project is what a daily safeguarding report might look like if the "cash held in bank accounts" line were replaced with live block-explorer data.
MIT
