Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,29 @@ GECKOTERMINAL_BASE_URL=http://pricing-proxy:8080/geckoterminal
# committed whitelist (src/monitoringV2/config/whitelist.mainnet.json).
# Bridge proposals are not exempted: the bridge type is inferred from a trivial
# usd() view call and is therefore unsafe to exclude.
# Live status (signer, voting power, qualified, helpers, gas) is at GET /guard.
#
# GUARD_ENABLED true/false. Disables the watcher entirely if false.
# GUARD_PRIVATE_KEY Hex private key (0x...) of the signer. Must hold or be
# delegated enough voting power to pass checkQualified()
# on the JUSD reserve.
# GUARD_HELPER_ADDRESS Address passed as the single helper to denyMinter().
# Use the equity holder that delegated to the signer.
# GUARD_PRIVATE_KEY Hex private key (0x...) of the signer. Missing or invalid
# while the guard is enabled is a hard config error
# (bootstrap aborts). The signer needs >= 2% of Equity
# voting power (own + delegated) to pass checkQualified();
# without it the guard skips and pages instead of denying.
# GUARD_HELPER_ADDRESS OPTIONAL. Single static seed helper unioned with the
# helper set derived from indexed Delegation events.
# Only contributes if it holds JUICE AND delegates to
# the signer; a stale value makes the on-chain helper
# check revert with EMPTY revert data (bare requires in
# Equity.votesDelegated), so the per-cycle deny pre-check
# drops the seed for that cycle and pages. If set to the
# same address as the guard signer, it is filtered out
# client-side before any contract call. Leave unset to
# use the delegation graph alone.
# GUARD_WHITELIST_FILE Absolute path to the whitelist JSON inside the
# container (e.g. /app/src/monitoringV2/config/whitelist.mainnet.json).
# An empty whitelist is deny-by-default and is logged as
# a warning at startup (so an unmounted/truncated file is
# distinguishable).
# GUARD_ENABLED=false
# GUARD_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
# GUARD_HELPER_ADDRESS=0x0000000000000000000000000000000000000000
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The monitoring service continuously syncs blockchain data to provide real-time i
- Collateral aggregation by token type
4. **Token Prices**: Fetches real-time prices from GeckoTerminal API with caching
5. **API Endpoints**: Serves data via REST API for frontend consumption
6. **Minter Guard**: Optional auto-deny watcher (opt-in via `GUARD_ENABLED=true`). At the end of every monitoring cycle it submits `denyMinter()` for any `PROPOSED` minter not on a committed whitelist (`src/monitoringV2/config/whitelist.mainnet.json`). Requires `GUARD_PRIVATE_KEY` and `GUARD_HELPER_ADDRESS`. See `.env.example`.
6. **Minter Guard**: Optional auto-deny watcher (opt-in via `GUARD_ENABLED=true`). At the end of every monitoring cycle it submits `denyMinter()` for any `PROPOSED` minter not on the committed whitelist (`src/monitoringV2/config/whitelist.mainnet.json`), bridges included. `denyMinter` is a shareholder veto (not an admin call): the signer needs ≥2% of Equity voting power (`Equity.checkQualified`), alone or via delegators, within the finite application window from `suggestMinter`. The guard verifies qualification and gas before sending (skips loudly and rate-limits those pages instead of submitting a doomed tx). Helpers are derived from indexed `Delegation` events; `GUARD_HELPER_ADDRESS` is an optional static seed for a named helper independent of the indexer. Permanently rejected denies (expired application period) stop retrying; other failures stop after a bounded attempt count with a single escalation. Live status is at `GET /guard` and rendered as the dashboard "Guard Delegation" section (where shareholders can delegate). An empty whitelist is deny-by-default and logged as a warning. See `.env.example`.

## Tech Stack

Expand Down Expand Up @@ -88,6 +88,7 @@ Swagger documentation available at: `http://localhost:3001/swagger`
| `/collateral` | Supported collateral tokens |
| `/jusd` | JUSD supply and protocol stats |
| `/minters` | Registered minters |
| `/guard` | Minter-guard live status |

## CoinGecko

Expand Down
18 changes: 17 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
VITE_API_BASE_URL=http://localhost:3001
# or for remote backend
# VITE_API_BASE_URL=https://dev.monitoring.juicedollar.com/api
# VITE_API_BASE_URL=https://dev.monitoring.juicedollar.com/api

# Wallet config for the Guard Delegation section. Both values are PUBLIC — they are baked into the
# browser bundle and visible to anyone who opens the dashboard — so they are NOT deployment secrets:
# the shipped defaults live in frontend/Dockerfile and can be overridden per build with --build-arg.
# Set them here only for the local dev server, or to point at a different endpoint or project.
#
# They are read fail-loud in src/lib/wagmi.ts, but the failure stays contained: a missing value throws
# inside the lazy WalletProvider boundary, so the read-only dashboard keeps working and only the
# Delegate button shows "wallet delegation unavailable". The status half (signer, voting power,
# qualification, helper count, gas) comes from the backend /guard endpoint and needs neither value.

# Citrea RPC URL for the wagmi http() transport. The public endpoint the backend uses also answers
# browser requests, so it works here as-is.
VITE_RPC_URL=https://rpc.citreascan.com
# WalletConnect project id — identifies the dapp to the relay, public by design.
VITE_WAGMI_ID=b49c3a590c4407316a6fd6eae6531e90
19 changes: 19 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
FROM node:lts-alpine AS build
WORKDIR /app/frontend
# The walletconnect tree (wallet delegation in the guard section) pulls ws' native addons
# bufferutil / utf-8-validate; node-gyp needs python3/make/g++ to build them on alpine, or npm ci
# fails. Build stage only — none of this reaches the nginx image below.
RUN apk add --no-cache python3 make g++
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
Expand All @@ -8,6 +12,21 @@ ARG VITE_API_BASE_URL=/api
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
ARG VITE_DEPLOYMENT_ENV
ENV VITE_DEPLOYMENT_ENV=$VITE_DEPLOYMENT_ENV
# Wallet config for the guard delegation section. Both values are PUBLIC by nature — they are baked
# into the browser bundle and visible to anyone who opens the dashboard — so they belong here as
# defaults rather than in deployment secrets, the same way VITE_API_BASE_URL does above. Override per
# build with --build-arg if a deployment needs a different endpoint or project.
# The Citrea RPC is the same public endpoint the backend uses and answers browser requests
# (access-control-allow-origin: *). The WalletConnect project id identifies the dapp to the relay.
#
# COUPLING: the delegate action builds its chain from the chain id the BACKEND reports at /guard, while
# this RPC URL is fixed here. Repointing the backend at another network (BLOCKCHAIN_ID / RPC_URL) without
# overriding VITE_RPC_URL in the same breath leaves the wallet transport talking to Citrea while the
# declared chain id says otherwise — transactions can look unconfirmed in the UI. Change both together.
ARG VITE_RPC_URL=https://rpc.citreascan.com
ENV VITE_RPC_URL=$VITE_RPC_URL
ARG VITE_WAGMI_ID=b49c3a590c4407316a6fd6eae6531e90
ENV VITE_WAGMI_ID=$VITE_WAGMI_ID
RUN npm run build

FROM nginx:alpine
Expand Down
Loading
Loading