Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Drips-Vault

Stream Health Monitor & Auto-Top-Up Vault for the Drips protocol.

A full-stack (contracts + backend worker) system that watches Drips streams, detects when an account's effective balance is running low, and automatically tops it up from a sponsor-managed vault — without the sponsor paying gas on every execution.

  • Smart contracts — a permissionless TopUpVault that refills Drips accounts when their balance drops below a configured threshold, plus a DripsBalanceReader that exposes live, per-second-decayed balances on-chain.
  • Backend — an Express API + background monitor worker that checks account health, triggers top-ups via a keeper wallet (gasless for the sponsor), and alerts operators over email / Discord / Telegram.
  • Frontend — planned (see Known limitations).

Table of contents


How it works

                  ┌─────────────────────────────────────────────────────┐
                  │                      Drips Vault                    │
                  │                                                     │
  sponsor ───────►│  TopUpVault (contracts)        DripsBalanceReader   │
  deposits        │    │  deposit/withdraw         (live balance oracle)│
  reserve         │    │  executeRule()                                  │
                  │    │  topUp(amount)                                 │
                  │    └─────────┬───────────────────────────────┬──────┘
                  │              │   pushes funds via            │  reads
                  │   GiversRegistry.give(accountId, token)      │  balanceAt
                  └──────────────┼───────────────────────────────┼──────┘
                                 ▼                               ▼
                        ┌─────────────────────────────────────────────┐
                        │         Drips protocol (v2 / mocks)         │
                        │  stream receivers, splittable, collectable  │
                        └─────────────────────────────────────────────┘
                                 ▲
                                 │ watches & keeps healthy
                        ┌────────┴──────────────────┐
                        │  Backend monitor worker   │
                        │  • health checks          │
                        │  • streams cache (events) │
                        │  • keeper relayer         │
                        │  • alerts                 │
                        │  • REST API               │
                        └───────────────────────────┘
  1. A sponsor deposits tokens into a TopUpVault and defines a rule: top up topUpAmount whenever the account's effective balance falls below threshold, with a cooldown between executions.
  2. The DripsBalanceReader keeps an account's receiver list on-chain (verified against the Drips streamsState hash) so the live, decayed balance can be computed via Drips.balanceAt(...) — no off-chain math needed.
  3. The backend worker polls accounts on a timer, computes each account's effective balance (streams + splittable + collectable), and:
    • runs executeRule() permissionlessly through the keeper wallet when the balance is below the threshold (the keeper pays gas and is reimbursed in the vault token when keeperFee is set), or
    • falls back to a forced topUp(amount) if the rule is disabled/paused.
  4. If a balance can't be restored, the worker raises alerts (email / Discord / Telegram) with cooldown deduplication, and records a check history.

Repository layout

drips-vault/
├── package.json              # npm workspaces root
├── packages/
│   ├── contracts/            # Solidity + Hardhat
│   │   ├── contracts/
│   │   │   ├── TopUpVault.sol
│   │   │   ├── DripsBalanceReader.sol
│   │   │   ├── interfaces/   # IDrips, IGiversRegistry, IDripsBalanceReader
│   │   │   └── mocks/        # MockDrips, MockERC20, MockGiversRegistry
│   │   ├── scripts/deploy.ts # local + real-network deploys
│   │   ├── abis/             # generated ABI JSON (committed)
│   │   ├── deployments/      # local.json (committed local dev addresses)
│   │   ├── test/             # Hardhat + Mocha/Chai tests
│   │   └── hardhat.config.ts
│   └── backend/              # Express API + monitor worker (TypeScript, ESM)
│       ├── src/
│       │   ├── index.ts      # entrypoint: server + worker loop
│       │   ├── api.ts        # Express routes + API-key middleware
│       │   ├── monitor.ts    # health checks + auto top-up + alerts
│       │   ├── relayer.ts    # vault rule execution (keeper)
│       │   ├── streams.ts    # stream config, effective balance, runway
│       │   ├── streams-cache.ts  # reconstruct receiver lists from events
│       │   ├── alerts.ts     # SMTP / Discord / Telegram notifiers
│       │   ├── db.ts         # node:sqlite database
│       │   ├── config.ts     # env parsing
│       │   └── abi/generated.ts # generated contract ABIs
│       ├── scripts/          # update-abis, demo-setup, re-arm
│       ├── test/             # Vitest + Supertest
│       └── .env.example

Prerequisites

  • Node.js ≥ 20 (backend uses the built-in node:sqlite)
  • npm (workspaces + allowScripts trust store)
  • Foundry anvil — used as the local EVM chain via npm run chain (or npx anvil)
  • Git

Quickstart (local)

From the repo root:

# 1. Install dependencies (one time)
npm install

# 2. Start a local chain (anvil, chain id 31337, 1s block time)
npm run chain

# 3. In a second terminal: compile + deploy the contracts locally
npm run deploy:local

# 4. In a third terminal: run the backend (Express + monitor worker)
npm run dev:backend

The API is then available at http://localhost:3000 (see API reference).

Seed a full demo

npm run demo:setup

This wires up a realistic demo against the local chain:

  • a sponsor streams 5 USDC/s to a dependency account for 7 days,
  • the reader is pointed at the account and the vault keeper is set,
  • the vault is funded with a 100,000 USDC reserve,
  • the account is registered with the backend API (alert threshold 2,500 USDC, runway 5 min),
  • the worker starts checking and will execute a top-up as soon as the balance crosses the threshold.

Check the result: GET http://localhost:3000/api/accounts and the /api/accounts/:id/checks history.


Configuration

Backend config lives in packages/backend/.env.example — copy it to .env:

cp packages/backend/.env.example packages/backend/.env

For local development you can leave everything at its default (protocol addresses are auto-loaded from packages/contracts/deployments/local.json). See Environment variables for the full reference.


Smart contracts

TopUpVault.sol

The core auto-top-up vault. Sponsors deposit tokens and define a rule per account:

Rule = { threshold, topUpAmount, cooldown, lastTopUpAt, enabled }

Key functions:

Function Description
deposit(amount) / depositFrom(account, amount) Sponsor funds the vault reserve.
withdraw(amount) Sponsor pulls unused reserve back (protects _freeReserve()).
updateRule(...) Owner sets/updates a rule for an account (threshold, top-up amount, cooldown, enable/disable).
executeRule() Permissionless — anyone can trigger a top-up when an account's effective balance drops below its threshold; the caller (keeper) pays gas and is reimbursed keeperFee in the vault token.
topUp(amount) Keeper/owner-only forced top-up (used when a rule is disabled/paused).
setKeeper / setKeeperFee Owner configures the gas-reimbursed keeper.
setBalanceReader Point the vault at a DripsBalanceReader.
pause / emergencyWithdraw Emergency controls; emergencyWithdraw returns sponsor reserve.

Executions push funds protocol-natively: tokens are transferred to the account's Giver and then give(accountId, token) is called, crediting the account's splittable/collectable balance. effectiveBalance() = reader's currentBalance() (or the raw streams balance fallback) + splittable + collectable.

DripsBalanceReader.sol

An on-chain balance oracle: stores an account's current receiver list and verifies it against Drips.hashStreams(receivers) and the account's streamsState hash, so currentBalance() can return the live, per-second-decayed balance via Drips.balanceAt(...). Falls back to the last-known balance when the stored list is stale or absent.

Interfaces & mocks

  • interfaces/IDrips.sol — minimal Drips v2 surface (streamsState, balanceAt, hashStreams, splittable, collectable, give) + StreamReceiver packing helpers.
  • interfaces/IGiversRegistry.sol, interfaces/IDripsBalanceReader.sol.
  • mocks/MockDrips (self-contained Drips v2 emulation), MockERC20, MockGiversRegistry — used for local dev and tests only.

The mocks emulate the real protocol for local development. On real networks the real Drips + GiversRegistry addresses are used (see networks.json).


Backend

API reference

Method Path Description
GET /health Liveness probe (always allowed).
GET /api/accounts List registered accounts (with latest check).
POST /api/accounts Register an account { accountId, token, autoTopUp?, alertThreshold?, runwayAlertSecs? }.
GET /api/accounts/:id Account detail.
PATCH /api/accounts/:id Partial update.
DELETE /api/accounts/:id Remove an account.
POST /api/accounts/:id/check Run a manual health check now.
POST /api/accounts/:id/topup Trigger executeRule() (or forced topUp) via the keeper; body { amount? }.
POST /api/accounts/:id/reader-refresh Refresh the on-chain reader's receiver list.
GET /api/accounts/:id/checks?limit= Check history (max 200).
GET /api/notifications?accountId=&limit= Alert/notification history.
GET /api/settings/alert-channels Redacted channel config.
PUT /api/settings/alert-channels Update runtime alert channels (email/discord/telegram).
GET /api/status Chain block/chainId/rpc, account count, channels, last scanned block.

When API_KEY is set, all routes except /health require the x-api-key header.

Environment variables

Variable Default Description
RPC_URL http://127.0.0.1:8545 EVM RPC endpoint.
CHAIN_ID 31337 Expected chain id.
DRIPS_ADDRESS from deployments/local.json Drips protocol address.
GIVERS_REGISTRY_ADDRESS from deployments/local.json GiversRegistry address.
TOKEN_ADDRESS from deployments/local.json Token to monitor (e.g. USDC).
VAULT_ADDRESS from deployments/local.json TopUpVault address.
READER_ADDRESS from deployments/local.json DripsBalanceReader address.
KEEPER_PRIVATE_KEY Anvil dev key #1 Keeper wallet paying gas for top-ups (dev-only default!).
CHECK_INTERVAL_SECS 30 Monitor worker poll interval.
ALERT_COOLDOWN_SECS 300 Min. seconds between alerts per account.
PORT 3000 HTTP server port.
CORS_ORIGIN http://localhost:5173 Comma-separated allowed origins.
API_KEY (empty) When set, requires x-api-key on all routes except /health.
DB_PATH ./data/monitor.db SQLite database file.
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM / ALERT_EMAIL_TO (empty) Email alerts (disable by leaving SMTP_HOST empty).
DISCORD_WEBHOOK_URL (empty) Discord webhook alerts.
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID (empty) Telegram alerts.

Testing

npm test                 # contracts (hardhat test) + backend (vitest run)
npm run test:contracts   # only Solidity tests (14 tests: vault, reader, mocks)
npm run test:backend     # only backend tests (api/repo/streams)
npm run lint             # backend typecheck (tsc --noEmit)
  • Contracts: Mocha/Chai via @nomicfoundation/hardhat-toolbox — admin access control, deposits/withdrawals, threshold/cooldown rule execution, keeper fees, emergency withdrawal, pause/disable paths.
  • Backend: Vitest + Supertest — API CRUD & validation, API-key auth, repo layer, stream packing/effective-balance/runway math.

Scripts

Command (from root) What it does
npm run chain Start anvil (chain id 31337, 1s block time).
npm run deploy:local Compile + deploy mocks, reader and vault; writes deployments/local.json.
npm run dev:backend Run the backend in watch mode (tsx watch).
npm run demo:setup Seed the local demo (streams, vault reserve, account registration).
npm run build:contracts / build:backend Compile contracts + ABIs / TypeScript build.
npm run test Full test suite.

Package-level helpers:

  • packages/contracts: npm run abi (regenerate abis/*.json), npm run deploy:hardhat.
  • packages/backend: npm run check-once (run one monitor cycle then exit), npm run typecheck, tsx scripts/re-arm.mts (re-fund the demo sponsor's stream after depletion).

Deploying to a real network

  1. Configure packages/contracts/hardhat.config.ts networks (sepolia, mainnet) with RPC_URL_SEPOLIA / RPC_URL_MAINNET and DEPLOYER_PRIVATE_KEY.

  2. Create/edit packages/contracts/networks.json so the target network entry has the real Drips and GiversRegistry addresses (protocol reference addresses are pre-populated for mainnet / sepolia / optimism; verify them before deploying).

  3. Deploy:

    npm run deploy:local --workspace @drips-vault/contracts  # local only
    npx hardhat run scripts/deploy.ts --network sepolia       # testnet
    npx hardhat run scripts/deploy.ts --network mainnet       # mainnet

    The script deploys TopUpVault + DripsBalanceReader and writes deployments/<network>.json. For real networks it reads TOKEN_ADDRESS (or netCfg.usdc), ACCOUNT_ID (or derives it from SPONSOR_ADDRESS) and VAULT_OWNER.

  4. Point the backend at the deployed addresses via env vars (or leave unset — it falls back to deployments/local.json), set a real KEEPER_PRIVATE_KEY, and restart.


Security notes

  • Keeper key is a dev placeholder: the default KEEPER_PRIVATE_KEY is Anvil's public dev key #1 — never use it outside local development.
  • executeRule() is permissionless by design: anyone can trigger a top-up when the threshold is crossed, so the sponsor never pays gas — but the vault's topUp and admin functions are owner/keeper-gated.
  • Emergency controls (pause, emergencyWithdraw) protect the sponsor reserve.
  • The API supports an optional API_KEY guard on all routes except /health.
  • Alert-channel settings can be updated at runtime via the API and are persisted in SQLite.

Known limitations

  • Frontend is not yet implemented. The packages/frontend workspace is declared in the root package.json but does not exist yet, so the root build, dev:frontend, build:frontend and the frontend leg of lint will error until it is added. Use npm run build:contracts / build:backend / test individually.
  • No linter/formatter is configured for the backend (the lint script is a TypeScript typecheck).
  • TopUpVault.sol references a docs/ARCHITECTURE.md that does not exist yet — architecture notes live in this README for now.
  • The committed deployments/local.json and packages/backend/data/*.db files are local-dev runtime artifacts; in production you may want to exclude them from version control.

License

No license is declared for the repository. TopUpVault.sol is MIT-licensed; the Drips protocol is covered by its own terms — check before redistributing.

About

Stream Health Monitor & Auto-Top-Up Vault for the Drips protocol — monitors stream health, keeps streams topped up automatically, and records top-ups in a ledger (Solidity contracts, Node backend, frontend).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages