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
8 changes: 7 additions & 1 deletion .github/workflows/frontend-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ jobs:
uses: actions/checkout@v4

- name: Build Docker image
run: docker build --build-arg VITE_DEPLOYMENT_ENV=dev -f frontend/Dockerfile -t ${{ env.DOCKER_TAGS }} .
# Unset secrets are not a build failure: wallet config is read lazily inside the guard section,
# so the dashboard still renders and the delegate button shows "wallet delegation unavailable".
run: docker build --build-arg VITE_DEPLOYMENT_ENV=dev --build-arg VITE_RPC_URL=${{ secrets.VITE_RPC_URL }} --build-arg VITE_WAGMI_ID=${{ secrets.VITE_WAGMI_ID }} -f frontend/Dockerfile -t ${{ env.DOCKER_TAGS }} .

deploy:
name: Deploy Frontend to DEV
Expand Down Expand Up @@ -54,8 +56,12 @@ jobs:
push: true
tags: ${{ env.DOCKER_TAGS }}
platforms: linux/arm64
# Unset secrets are not a build failure: wallet config is read lazily inside the guard section,
# so the dashboard still renders and the delegate button shows "wallet delegation unavailable".
build-args: |
VITE_DEPLOYMENT_ENV=dev
VITE_RPC_URL=${{ secrets.VITE_RPC_URL }}
VITE_WAGMI_ID=${{ secrets.VITE_WAGMI_ID }}

- name: Install cloudflared
run: |
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/frontend-prd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ jobs:
uses: actions/checkout@v4

- name: Build Docker image
run: docker build --build-arg VITE_DEPLOYMENT_ENV=prd -f frontend/Dockerfile -t ${{ env.DOCKER_TAGS }} .
# Unset secrets are not a build failure: wallet config is read lazily inside the guard section,
# so the dashboard still renders and the delegate button shows "wallet delegation unavailable".
run: docker build --build-arg VITE_DEPLOYMENT_ENV=prd --build-arg VITE_RPC_URL=${{ secrets.VITE_RPC_URL }} --build-arg VITE_WAGMI_ID=${{ secrets.VITE_WAGMI_ID }} -f frontend/Dockerfile -t ${{ env.DOCKER_TAGS }} .

deploy:
name: Deploy Frontend to PRD
Expand Down Expand Up @@ -54,8 +56,12 @@ jobs:
push: true
tags: ${{ env.DOCKER_TAGS }}
platforms: linux/arm64
# Unset secrets are not a build failure: wallet config is read lazily inside the guard section,
# so the dashboard still renders and the delegate button shows "wallet delegation unavailable".
build-args: |
VITE_DEPLOYMENT_ENV=prd
VITE_RPC_URL=${{ secrets.VITE_RPC_URL }}
VITE_WAGMI_ID=${{ secrets.VITE_WAGMI_ID }}

- name: Install cloudflared
run: |
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
13 changes: 12 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
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 / Guard Delegation section (required for the Delegate button). Read fail-loud in
# src/lib/wagmi.ts — a missing value throws inside the lazy WalletProvider boundary (no silent
# fallback), so the rest of the read-only dashboard keeps running. Only the Delegate button uses
# these; the read path (signer / voting-power % / gas / helpers) comes from the backend /guard
# endpoint and works without them.

# Citrea RPC URL for the wagmi http() transport (browser-visible — use a public/rate-limited endpoint).
VITE_RPC_URL=https://your-citrea-rpc-provider.com
# WalletConnect / Web3Modal project id (reuse the dapp's id).
VITE_WAGMI_ID=your-walletconnect-project-id
8 changes: 8 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,10 @@ 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
ARG VITE_RPC_URL
ENV VITE_RPC_URL=$VITE_RPC_URL
ARG VITE_WAGMI_ID
ENV VITE_WAGMI_ID=$VITE_WAGMI_ID
RUN npm run build

FROM nginx:alpine
Expand Down
Loading
Loading