📊 Slide Deck — Project overview and feature walkthrough (PDF)
Flask + HTMX + Web3 MVP for testnet lending, trading, risk monitoring, and human-approved agent suggestions.
- Python 3.11+
- Foundry (includes
anvilandforge) - Redis (for Celery background tasks; optional for basic dev use)
- MetaMask or any EIP-1193 compatible browser wallet
python -m venv .MidnightVenv
.\.MidnightVenv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .envEdit .env and set SECRET_KEY to a random string. Leave the rest as defaults for local Anvil development.
flask --app run.py db upgradeflask --app run.py run --debugOpen http://127.0.0.1:5000.
Run these in separate terminals:
Terminal 1 — local chain:
anvil --state chainstate.jsonThe --state flag saves chain state to chainstate.json on shutdown and reloads it on next startup, so your balances survive restarts.
Terminal 2 — deploy contracts:
.\.MidnightVenv\Scripts\Activate.ps1
forge --root contracts build
forge script contracts/script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --broadcast
python scripts/sync_contract_abis.pyCopy the printed contract addresses into .env:
LENDING_POOL_ADDRESS=0x...
WBTC_TOKEN_ADDRESS=0x...
DEBT_TOKEN_ADDRESS=0x...
PRICE_ORACLE_ADDRESS=0x...Terminal 3 — Redis (if installed):
redis-serverTerminal 4 — Celery worker (optional):
.\.MidnightVenv\Scripts\Activate.ps1
celery -A celery_worker.celery worker --loglevel=infoTerminal 5 — Flask:
.\.MidnightVenv\Scripts\Activate.ps1
flask --app run.py run --debug- Add a custom network: RPC
http://127.0.0.1:8545, Chain ID31337. - Import one of the Anvil dev accounts using its private key (printed by
anvilon startup). - Open
http://127.0.0.1:5000/auth/connectand click Connect MetaMask.
forge --root contracts test -vAll 8 lending invariant tests must pass before running the demo.
pytestruff check .
black .| Variable | Default | Description |
|---|---|---|
FLASK_ENV |
development |
development, testing, or production |
SECRET_KEY |
dev-only-change-me |
Flask session secret — change for production |
DATABASE_URL |
SQLite in instance/ |
Database connection string |
REDIS_URL |
redis://localhost:6379/0 |
Redis for Celery |
CHAIN_ID |
31337 |
EVM chain ID |
RPC_URL |
http://127.0.0.1:8545 |
JSON-RPC endpoint |
LENDING_POOL_ADDRESS |
— | Deployed LendingPool address |
WBTC_TOKEN_ADDRESS |
— | Deployed mock WBTC collateral token address |
DEBT_TOKEN_ADDRESS |
— | Deployed mUSDC debt token address |
PRICE_ORACLE_ADDRESS |
— | Deployed price oracle address |
LLM_PROVIDER |
none |
none, openai, grok, or qwen |
OPENAI_API_KEY |
— | OpenAI key (required when LLM_PROVIDER=openai) |
GROK_API_KEY |
— | xAI Grok key (required when LLM_PROVIDER=grok) |
QWEN_BASE_URL |
http://localhost:11434/v1 |
Ollama base URL (required when LLM_PROVIDER=qwen) |
QWEN_MODEL |
qwen2.5:7b |
Ollama model name |
FAUCET_PRIVATE_KEY |
— | Anvil account 0 private key for dev token minting — never use a real key |
FAUCET_AMOUNT |
10000 |
Token amount to mint per faucet request |
API_RATE_LIMIT |
60/minute |
Flask-Limiter rate limit string |
Never commit .env or real API keys.
Agent suggestions work without an LLM (LLM_PROVIDER=none uses rule-based suggestions only). To enable AI-generated suggestions, set one of:
Local Ollama (free, no API key):
LLM_PROVIDER=qwen
QWEN_BASE_URL=http://localhost:11434/v1
QWEN_MODEL=qwen2.5:7bInstall Ollama, then run ollama pull qwen2.5:7b. First response takes 30–60 s while the model loads.
OpenAI:
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...xAI Grok:
LLM_PROVIDER=grok
GROK_API_KEY=xai-...- Set
CHAIN_IDandRPC_URLin.envto the target testnet. - Fund a deployer EOA with testnet ETH from a faucet.
- Deploy contracts:
forge script contracts/script/Deploy.s.sol \
--rpc-url $RPC_URL \
--private-key $DEPLOYER_PRIVATE_KEY \
--broadcast \
--verify- Copy the printed contract addresses into
.env. - Run
python scripts/sync_contract_abis.pyto copy ABI files. - Deploy the Flask app (see Render/Fly.io below) with the updated
.envvariables.
- Create a new Web Service pointing to this repo.
- Build command:
pip install -r requirements.txt && flask --app run.py db upgrade - Start command:
gunicorn run:app --workers 2 --bind 0.0.0.0:$PORT --timeout 30 - Add a Redis add-on and set
REDIS_URL. - Set all required environment variables in the Render dashboard.
- Set
FLASK_ENV=productionandSECRET_KEYto a secure random value.
Optional: add a Background Worker with the command celery -A celery_worker.celery worker --loglevel=info.
fly launch
fly secrets set SECRET_KEY=<random> FLASK_ENV=production DATABASE_URL=<postgres-url> REDIS_URL=<redis-url>
fly deployUse fly.toml to configure the [processes] section for both web and worker if running Celery.
The API is sandbox/testnet-only for early company integrations.
Base URLs:
- Local:
http://127.0.0.1:5000/api/v1 - Testnet:
<deployed-url>/api/v1
Authentication:
X-API-Key: <your-api-key>Generate an API key from the Agent page after signing in, or via:
POST /api/v1/api-keys(requires wallet session auth)
Supported scopes:
| Scope | Description |
|---|---|
read:positions |
Read wallet positions |
read:risk |
Read health factor and risk class |
read:opportunities |
Read yield opportunities |
write:actions |
Build unsigned action transactions |
Endpoints:
| Method | Path | Scope |
|---|---|---|
GET |
/health |
none |
GET |
/positions |
read:positions |
GET |
/risk |
read:risk |
GET |
/yield-opportunities |
read:opportunities |
POST |
/actions/build |
write:actions |
POST |
/api-keys |
wallet session |
GET |
/api-keys |
wallet session |
DELETE |
/api-keys/<id> |
wallet session |
Example (PowerShell):
Invoke-RestMethod `
-Uri http://127.0.0.1:5000/api/v1/risk `
-Headers @{ "X-API-Key" = "<your-key>" }Response envelope:
{ "ok": true, "data": {}, "error": null }Standard error codes:
| Code | Meaning |
|---|---|
unauthorized |
Missing, invalid, or revoked API key |
forbidden |
Valid key, insufficient scope |
rate_limited |
Rate limit exceeded |
validation_error |
Bad request body or parameters |
unsupported_chain |
Chain not configured |
unsupported_asset |
Token not supported |
quote_unavailable |
Cannot produce a trading quote |
internal_error |
Unexpected server error |
Safety note: All transaction-building endpoints return unsigned payloads. User wallets must sign all user-fund transactions. The backend never holds private keys.
CryptoBase uses the Midnight network to keep sensitive financial data — collateral amounts, debt amounts, and health factors — hidden from the public ledger while remaining mathematically verifiable.
- Commitment — when your position is attested, your collateral and debt USD values are hashed with a secret salt:
SHA-256(amount || salt). The raw amounts cannot be recovered from the hash. - Zero-knowledge circuit — the
attest_healthcircuit incontracts/midnight/private_lending.compactproves your position is overcollateralized (collateral ≥ 150% of debt) inside a ZK proof without broadcasting the values. - Public attestation — the Midnight network records only the proof hash, the overcollateralization result (
yes/no), and the risk class (healthy/watch/danger/liquidatable). No amounts on chain.
What's hidden
| Data | Visibility |
|---|---|
| Collateral amount (USD) | Hidden — commitment hash only |
| Debt amount (USD) | Hidden — commitment hash only |
| Health factor (exact value) | Hidden — risk class only |
| Risk class | Public (Healthy / Watch / Danger / Liquidatable) |
| Overcollateralized | Public (Yes / No) |
| ZK proof hash | Public (verifiable on Midnight chain) |
| URL | Description |
|---|---|
/privacy |
Your privacy dashboard — attestation status, commitment hashes, proof hash |
/privacy/attest |
Re-attest current position (POST, login required) |
/privacy/verify/<wallet> |
Public verifier — shows risk class and proof hash for any wallet |
- Midnight Testnet — set
MIDNIGHT_RPC_URLto your Midnight node endpoint. Attestations are pushed on-chain. - Local Demo — leave
MIDNIGHT_RPC_URLempty. Commitments are computed locally, attestations are derived in-memory. All privacy properties are demonstrated without a live Midnight node.
MIDNIGHT_RPC_URL= # Midnight node RPC endpoint (empty = local demo mode)
MIDNIGHT_CONTRACT_ADDRESS= # Deployed private_lending.compact addressThe Midnight smart contract is at contracts/midnight/private_lending.compact. It defines:
private_positions— private ledger (owner-only): commitment hashes per walletpublic_attestations— public ledger: risk class and proof hash per walletattest_health— ZK circuit that proves overcollateralization without revealing amountsrevoke_attestation— clears private and public records when a position is closed
| Symptom | Fix |
|---|---|
forge not found |
Install Foundry and ensure ~/.foundry/bin is in PATH |
anvil not found |
Same as above |
| CSRF error on form submit | Clear cookies and re-sign in; check SECRET_KEY is set |
| Contract address not set | Run deploy script and copy addresses into .env |
| Celery tasks not running | Start redis-server and the Celery worker |
| Wallet signature fails | Make sure MetaMask chain matches CHAIN_ID in .env |