diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index c2232436..00000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"1ebdada4-6239-4cd6-9e17-3a75dc054408","pid":17384,"procStart":"Sat Jun 6 18:57:49 2026","acquiredAt":1780796215558} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 32c22714..8da21a25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,14 +79,15 @@ Completed (Phase 3B — continued, Session 11; HOOK SUPERSEDED by the Session-12 Current implementation target: -- Demo script (RangeGuardDemo.s.sol) — drive the live Sepolia pool end-to-end, then frontend dashboard +- Recorded 5-minute demo (spec §15), then the full README + demo video write-up Upcoming implementation order: 1. Sepolia hook deployment ✅ (Session 11; REDEPLOYED for Lasna in Session 12 — new hook 0xFead…a7C0) 2. ReactVM (reactive) deployment ✅ (Session 12 — Reactive Lasna 0xC0e6…B70b, live + wired + verified) -3. Demo script (RangeGuardDemo.s.sol) ← current -4. Frontend dashboard +3. Demo script (RangeGuardDemo.s.sol) ✅ (Session 13) +4. Frontend dashboard ✅ (Session 14 — frontend/, live coverage report; https://range-guard.vercel.app) +5. Recorded 5-minute demo + full README ← current --- @@ -360,7 +361,34 @@ At the start of every session, Claude must: # Current Session State -Last completed (Session 12): reactive-lib → reactive-lib-omni (Omni fork) migration + first live +Last completed (Session 14): Frontend dashboard — the LP coverage report (spec §4 Pillar 4). +React 18 + Vite + Tailwind + viem SPA in `frontend/`, NO backend — reads public Sepolia RPC only. +- TWO MODES (Option C): LIVE (default, or `?positionKey=0x…`) renders the real on-chain coverage + report for any position; `?demo=true` renders a hardcoded fork narrative from + docs/demo-run-output.md, banner-labeled "Simulated 45-day lifecycle (Sepolia fork)" — never + presented as live. Live mode is for judges/employers; demo mode is for the 4:15–4:45 recording. +- KEY DESIGN: settlement CLEARS positions[poolId][key] (strict CEI), so a closed position reads + ALL ZEROS / active=false. The coverage report is therefore reconstructed from EVENT LOGS, not + the live mapping — PositionSummary rebuilds entry/earned/payout/IL/factor from PositionRegistered + + the settlement event. This is Pillar 4 made literal: the report lives in events. +- READS: poolState (buffer health), poolConfig (targetBufferSize/coverageApr/secondsPerYear, fee = + base+buffer), positions (live PositionState). Current tick has no getter → read via PoolManager + `extsload` at keccak256(abi.encode(poolId, 6)) (v4 StateLibrary POOLS_SLOT=6), unpack Slot0 + sqrtPriceX96(160)|tick(24). Event log fetch: single eth_getLogs with topics [null,null,key] + (positionKey is topic2 on every position-scoped event), decoded by topic0; floored at + DEPLOY_BLOCK=11_005_000, chunked 9k blocks; 30s poll. +- VERIFIED live (hook 0xFead…a7C0): PositionRegistered 228.69 USDC / range $1,790–$2,208 → + Checkpoint +0.02 USDC (dt=0 baseline row skipped) → PartialPayout/COVERAGE_CAP 0.02; buffer + 10,000.81 / health 10.00%. Build + dev server pass. +- DEPLOY: Vercel auto-deploy from `main` → https://range-guard.vercel.app (Framework Vite, root + `frontend`, build `npm run build`, output `dist`, no env vars; frontend/vercel.json SPA rewrites). +- TWO HONEST NOTES: live range shows $1,790–$2,208 (tick rounding) not $1,800–$2,200; live + settlement is PartialPayout/COVERAGE_CAP not ClaimSettled/IL_CAP (IL_CAP is the ?demo=true story). +-> docs/session-14-frontend.md + +--- + +Previously completed (Session 12): reactive-lib → reactive-lib-omni (Omni fork) migration + first live ReactVM deployment on Reactive Lasna. The whole reactive stack is live and verified; the only remaining piece is the demo script (LP-deposit + swap tooling) to drive the round-trip end-to-end. @@ -403,7 +431,7 @@ docs/reactive-lib-omni-audit.md. Session record: docs/session-12-reactive-deploy NOT done: Phase-7 end-to-end (LP deposit → swap → PositionTracked → Checkpointed) — needs the demo script (RangeGuardDemo.s.sol). No live LP-deposit/swap tooling exists yet for the Sepolia pool. -Current target: Demo script (RangeGuardDemo.s.sol), then frontend dashboard. +Current target: Recorded 5-minute demo (spec §15), then the full README + demo video write-up. Carry-ins: payout recipient = v4 sender (owner=sender MVP). The Callback Proxy is PER NETWORK under Omni — for any future host chain confirm it at dev.reactive.network/origins-and-destinations before deploying the hook (it is NOT the legacy 0x…fffFfF). @@ -423,3 +451,17 @@ Session 13 carry-ins (live-demo blockers found + fixed): (proxy.depositTo{0.05 ETH}(hook)); verify `make reserves-hook`. The hook already inherits AbstractPayer (pay()) and all reactive-callable fns take the leading RVM-id address placeholder, so neither of those was the issue — only the reserve. + +Session 14 carry-ins (frontend): +1. SPEC §11 VIEW FUNCTIONS NOT IMPLEMENTED on the deployed hook (mainnet-hardening item): + getPoolConfig/getBufferHealth/getCurrentFee/getDayCountBasis/getCoverageAPR/getPositionSnapshot/ + getAccrualState/getEarnedCoverage/getEligibility/getEstimatedPayout/getCoverageProgress. The + frontend works around this by reading the public mappings directly + extsload for the live tick. + getEarnedCoverage (accrual simulated to block.timestamp) is the one worth adding — it would let an + OPEN position render live coverage without the client-side extsload/simulation. +2. README is a MINIMAL PLACEHOLDER for now (tagline + live dashboard link + "coming soon"). The full + write-up lands AFTER the demo recording, together with the demo video link. +3. Frontend is NOT auto-deployed until merged to `main` (Vercel watches main). Live-URL confirmation + in docs/session-14-frontend.md has an unchecked box to tick once the Vercel deploy is green. +4. Live demo position is CLOSED (settled) on-chain — the live coverage report is honest + sparse; + the full IL_CAP narrative is the ?demo=true view (clearly labeled simulated). diff --git a/README.md b/README.md index ca68e6d8..530efbaf 100644 --- a/README.md +++ b/README.md @@ -2,57 +2,10 @@ > Protect your liquidity. Guard your range. -A Uniswap v4 hook providing native, on-chain impermanent loss coverage -for liquidity providers. Coverage accrues over time via a day-count -convention, is funded by dynamic fee skimming, and pays out automatically -on full withdrawal. +A Uniswap v4 hook providing native on-chain impermanent loss coverage for liquidity providers, funded by dynamic fee skimming. Integrated with the Reactive Network for autonomous cross-chain automation. -## Overview +**Live dashboard:** https://range-guard.vercel.app -- Coverage accrues only while LP position is in range -- Funded by a portion of swap fees via v4 dynamic fees -- Automatic settlement on full withdrawal -- Three-cap payout system (IL cap, coverage cap, buffer cap) -- Full coverage report generated from on-chain events +**Demo video:** coming soon -## Architecture - -- Single hook contract supporting multiple pools -- Lazy accrual model (no O(N) iteration on swaps) -- Reactive Network integration for range monitoring and checkpoints -- Immutable PoolConfig per pool - -## Project Status - -See project-status.md for current implementation progress. - -## Documentation - -- spec.md — full technical specification -- context.md — architecture context and design decisions -- state-machine.md — LP position lifecycle -- invariant-mapping.md — protocol invariants -- testing-strategy.md — test philosophy and coverage goals - -## Development - -Built with Foundry. - -\```bash - -# Build - -forge build - -# Test - -forge test - -# Test with verbosity - -forge test -vvv -\``` - -## Target - -Uniswap v4 Hook Incubator — Testnet MVP (ETH/USDC demo pool) +**Documentation and full README coming soon.** diff --git a/context.md b/context.md index bdbb8925..7c4ef3a2 100644 --- a/context.md +++ b/context.md @@ -50,9 +50,17 @@ Reactive contract LIVE on Reactive Lasna (chainId 5318007): 0xC0e6b70c8FF7596254 (tx 0xed865d…5329), funded 0.05 lREACT, wired to the new hook. See docs/session-12-reactive-deployment.md. +Frontend dashboard (Session 14): COMPLETE. React 18 + Vite + Tailwind + viem SPA in frontend/, +no backend — renders the LP coverage report from LIVE Sepolia on-chain events for hook 0xFead…a7C0. +Two modes (Option C): live (default / ?positionKey=0x…, honest + verifiable; reconstructs closed +positions from event history since settlement clears positions[…]) and ?demo=true (hardcoded fork +narrative, banner-labeled "Simulated 45-day lifecycle"). Buffer health from poolState; current tick +via PoolManager extsload. Deployed on Vercel (auto from main): https://range-guard.vercel.app. +-> docs/session-14-frontend.md + Next implementation target: -- Frontend dashboard (coverage report rendered from Sepolia events) +- Recorded 5-minute demo (spec §15), then the full README + demo video write-up Completed (Session 13): demo tooling — RangeGuardDemo.s.sol (Option A, fork+vm.warp, spec §14), LiveEndToEnd.s.sol / LiveWithdraw.s.sol (Option B live broadcast), DemoLPRouter.sol (live LP whose @@ -66,8 +74,9 @@ not contracts). -> docs/session-13-demo-script.md, docs/reactive-evidence.md Planned next steps: -- Frontend dashboard (coverage report rendered from Sepolia events): query the live demo positionKey - 0x62e2311b…462d88 on hook 0xFead…a7C0 and render PositionRegistered → AccrualUpdated → settlement. +- Record the 5-minute demo (spec §15): terminal segment (RangeGuardDemo.s.sol) + coverage-report + segment (the ?demo=true dashboard view) + reactive evidence. Then write the full README and link + the demo video. Recent architecture update: diff --git a/docs/session-14-frontend.md b/docs/session-14-frontend.md new file mode 100644 index 00000000..abddb2eb --- /dev/null +++ b/docs/session-14-frontend.md @@ -0,0 +1,452 @@ +# Session 14 — Frontend Dashboard (Coverage Report) + +**Date:** 2026-06-07 +**Branch:** `feat/frontend-dashboard` +**Outcome:** Built the RangeGuard coverage-report dashboard — a React 18 + Vite + Tailwind SPA +(viem, no backend) that renders the LP coverage report from **live Sepolia on-chain events**, plus +a clearly-labeled simulated mode for the recorded demo segment. Build compiles, dev server serves, +and the live data path is verified end-to-end against the deployed hook. Not yet committed/deployed +(pending user review). + +--- + +## Opening Prompt + +> The full session-opening prompt, copied verbatim (typos included), per the +> session-13 precedent: + +``` +RangeGuard — Session 14: Frontend Dashboard +Branch: feat/frontend-dashboard (already created — do not create a new branch) +Mandatory first steps — do not skip, do not write any code until complete: + +Read spec.md (Pillar 4, Section 10 event inventory, Section 11 view functions) +Read context.md +Read project-status.md +Read docs/demo-narrative.md +Read docs/demo-run-output.md +Read docs/reactive-evidence.md +After reading all six documents, produce a written Session Review covering: + +What the coverage report must show and why it is the key differentiator +Which on-chain events map to which coverage report rows +What the buffer health panel needs to display +What the reactive evidence section needs to show +Any risks or gaps you identify before building + + +Wait for user confirmation before proceeding past the review + +Do not write closing documents until explicitly prompted at the end of the session. + +What you are building: +A React + Vite single-page application in frontend/ that renders the RangeGuard coveraort from live Sepolia on-chain events. This is the 4:15–4:45 segment of the recorded demo and the primary LP-facing transparency layer of the protocol. +Deployed to: https://range-guard.vercel.app (Vercel, connected to the main branch of the GitHub repo) + +Current deployed state (all live on Sepolia — do not redeploy anything): +Component Address +RangeGuardHook 0xFead6CeaD66f86101f0D0fc5A9B97888FA54a7C0 +PoolId 0x3e2f931d495879c5ff87e338192def0f0b824bdf07e9f9c16b02cdba34aaa61a +MockUSDC (token1) 0x04feCef5110c5e52794fdA3D935BC2Cc0ee428CA +PoolManager 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543 +DemoLPRouter 0xEA30a770E6B3C3d30074908Af13b930d6d451FEa +Callback Proxy (Lasna→Sepolia) 0xc9f36411C9897e7F959D99ffca2a0Ba7ee0D7bDA +RangeGuardReactive (Lasna) 0x5eb9c8C021fB3474aA1f2d9EE5f53f6DbA5fFee1 +Deployer / owner 0x193D1F3E085efc80e1027891FaA770E81ECC4A1d +Live demo positionKey: 0x62e2311b3a51692f0f8ce68f4cd03882e163b37aa357431ad14a4f5b41462d88 + +Tech stack: + +React 18 + Vite +Tailwind CSS for styling +viem for Sepolia RPC calls and everies (not ethers.js — viem is the modern standard for v4 ecosystem tooling) +No backend — all data from public Sepolia RPC calls +Deployed on Vercel via GitHub main branch auto-deploy + + +Project structure: +frontend/ + index.html + package.json + vite.config.js + tailwind.config.js + src/ + main.jsx + App.jsx + hooks/ + usePositionEvents.js — fetches all hook events for a positionKey + useBufferHealth.js — reads poolState public mapping + usePositionSummary.js — reads positions public mapping + components/ + CoverageReport.jsx — the main coverage report table (KEY DIFFERENTIATOR) + BufferHealthPanel.jsx — buffer balance, skimmed, paid out, health % + PositionSummary.jsx — entry notional, earned coverage, estimated payout + ReactiveEvidencePanel.jsx — cross-chain automation status + EventRow.jsx — single row in the coverage report table + lib/ + events.js — event ABIs + topic0 constants + formattC formatting, tick→price, timestamp→date + rpc.js — viem client setup (Sepolia public RPC) + +Coverage report table — the key differentiator (spec §4 Pillar 4) +Every row maps to a real on-chain event. The table must show: +Date Event Details Coverage Earned +Day 0 Position Opened Entry: 228.38 USDC | Range: [$1,800–$2,200] — +Day 15 Checkpoint In range ✓ +4.69 USDC +Day 18 Out of Range ⚠ Reactive Network detected tick crossing Paused at 4.69 USDC +Day 20 Checkpoint Out of range ✗ +0.00 USDC +Day 22 Back In Range ✓ Reactive Network detected recovery Resumed +Day 43 Checkpoint In range ✓ +7.82 USDC +Day 45 Claim Settled IL: 4.47 USDC | Payout: 2.23 USDC | IL_CAP 2.23 USDC + +Event → row mapping: + +PositionRegistered → Position Opened row +AccrualUpdated → Checkpoint row (show delta, isInRange, earnedCoverageStable) +PositionOutOfRange → Out of Range row (show earnedCoverageAtPause) +PositionBackInRange → Back In Range row (show earnedCoverageAtResume) +ClaimSettled → Claim Settled row (show IL_raw, pay merge with matching AccrualUpdated (same tx, same timestamp) + + +Buffer health panel: +Read poolState[poolId] public mapping directly (no view functions — they were not implemented in the deployed hook): + +bufferBalanceStable — current balance +totalSkimmedStable — cumulative fees collected +totalPaidOutStable — cumulative payouts + +Display: +Buffer Health +Balance: 9,999.09 USDC [████████░░] 10.0% +Target: 100,000.00 USDC +Skimmed: 1.34 USDC (lifetime fees) +Paid out: 2.25 USDC (lifetime payouts) +Compute health % as bufferBalanceStable * 100 / targetBufferSize from poolConfig[poolId]. + +Position summary panel: +Read positions[poolId][positionKey] public mapping: + +Entry notional, deposit time, tick range +Current earnedCoverageStable +active status + +Simulate live getEarnedCoverage by computing accrual to block.timestamp in the frontend (same formula as _accrue in the hook — delta = entryNotional * coverageApr * dt / secondsPerYear). + +Reactive evidence panel: +Show the cross-cnks to the live explorers: + +Sepolia hook: link to Etherscan +Lasna reactive: link to https://lasna-omni.reactscan.net/address/0x5eb9c8C021fB3474aA1f2d9EE5f53f6DbA5fFee1 +For each confirmed round-trip from reactive-evidence.md: show the Sepolia tx hash as a clickable Etherscan link +Note on the testnet observation stall — honest, as documented in reactive-evidence.md + + +Real-time updates: + +Poll Sepolia every 30 seconds for new events +Show a "Last updated: X seconds ago" indicator +Show a loading state on first load + + +Vercel deployment: +After the frontend is built and running locally (npm run dev), configure Vercel: + +Framework preset: Vite +Root directory: frontend +Build command: npm run build +Output directory: dist +No environment variables needed — all RPC calls use public Sepolia endpoints + +Add a vercel.json in frontend/: +json{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} +After deployment confirm https://range-guard.vercel.app loads and shows the coverage report. + +Styling requirementslean, professional — this is a portfolio piece and a demo artifact +Dark theme preferred (easier on screen during recording) +RangeGuard branding: tagline "Protect your liquidity. Guard your range." in the header +Color coding: green for in-range/positive, amber for out-of-range, red for zero accrual +Mobile responsive — not required but nice to have + + +Critical constraints: + +No backend — pure frontend reading public Sepolia RPC +No ethers.js — use viem throughout +Read poolConfig and poolState directly from public mappings — do not call spec §11 view functions (they were not implemented in the deployed hook) +The positionKey 0x62e2311b3a51692f0f8ce68f4cd03882e163b37aa357431ad14a4f5b41462d88 is hardcoded as the demo position — also support a URL parameter ?positionKey=0x... so any position can be viewed +All USDC amounts displayed with 2 decimal places (divide by 1e6) +Timestamps displayed as readable dates (Jun 06, 2026) not Unix timestamps +Tick values converted to prices using 1.0001^tick with decimal r ETH/USDC (token0=ETH 18 dec, token1=USDC 6 dec) + + +Session closer — do not execute until explicitly prompted: +When prompted: + +Update project-status.md — tick frontend checkbox, record Vercel deployment URL +Update CLAUDE.md current session state +Update context.md Section 2 — remove frontend, add recorded demo as final target +Update README.md — add live dashboard link https://range-guard.vercel.app, add demo video link placeholder +Generate docs/session-14-frontend.md with: session review, component architecture decisions, any RPC challenges, Vercel deployment steps, live URL confirmation, and the full opening prompt verbatim + + docs/session-14-frontend.md must include the full opening prompt used for this session copied verbatim under a section titled ## Opening Prompt. This is the same pattern established in session-13-demo-script.md. +``` + +**Follow-up directive (Option C, given after the Session Review):** + +``` +Option C. Build the dashboard to render whatever ?positionKey= resolves to from live on-chain +events — honest, sparse, fully verifiable. This is what judges and employers see at +range-guard.vercel.app. For the demo recording segment (4:15–4:45), add a second mode: ?demo=true +that renders a clearly-labeled "Demo Narrative" view populated from the demo-run-output.md fork +data — hardcoded, clearly marked as "Simulated 45-day lifecycle (Sepolia fork)" so it is never +presented as live data. The recording shows the ?demo=true view to tell the full coverage story. +The live URL without the parameter shows the real on-chain data. Both are honest — one is live, +one is clearly labeled as simulated. +``` + +--- + +## Session Review (produced before any code, verbatim summary) + +Before writing code, all six documents were read **and** the live demo position was queried +on-chain with `cast`. The on-chain ground truth materially differs from the idealized table in +the prompt, which shaped the whole build: + +| Check | Live result (hook `0xFead…a7C0`) | +|---|---| +| `positions[poolId][0x62e2…]` | **all zeros, `active=false`** — position already withdrawn/settled; struct cleared | +| `poolState[poolId]` | buffer **10,000.81 USDC**, skimmed **0.82 USDC**, paidOut **0.0164 USDC** | +| `AccrualUpdated` (strict topic2) | **3** events (1 dt=0 baseline + 2 real) | +| `PositionOutOfRange` / `BackInRange` | **0 / 0** | +| `Checkpointed` | **0** | +| `ClaimSettled` | **0** | +| `PartialPayout` | **1** — requested ≈ 1.14 USDC, actual = 0.0164 USDC, factor = **COVERAGE_CAP** | +| `PositionClosed` | **1** | + +**Conclusions carried into the build:** + +1. **The coverage report must be reconstructed from event logs, not the live mapping** — settlement + clears `positions[…]`, so a closed position reads zero. Events are the durable record (exactly + Pillar 4's premise). +2. **The live story is sparse and ends in PartialPayout/COVERAGE_CAP**, not the idealized + ClaimSettled/IL_CAP. The rich 45-day narrative is the *fork* output (`RangeGuardDemo.s.sol`), + not live chain. +3. **No view functions deployed** — read `poolConfig`/`poolState`/`positions` public getters and + derive fee/health/price client-side. +4. **Reactive transition events never landed on the live hook** (testnet observation stall) — so + the Out-of-Range / Back-In-Range rows are absent live; the evidence panel must be honest about it. + +The five review points (coverage report as differentiator; event→row mapping; buffer panel; +reactive evidence; risks/gaps) were answered in full in the session transcript and drove the +decisions below. + +--- + +## The Option C decision (live vs. demo mode) — and why + +The Session Review surfaced the core tension: the live demo position's real on-chain history is +**honest but sparse** (one in-range deposit → tiny accrual → partial payout), while the recorded +demo needs the **full coverage story** (in → out-of-range → back-in → settlement with the IL cap +binding). + +The user chose **Option C: build both, clearly separated.** + +- **Live mode** (default, or `?positionKey=0x…`): renders whatever the key resolves to from live + Sepolia events — honest, sparse, fully verifiable. This is what judges/employers see at the + bare URL. +- **Demo mode** (`?demo=true`): renders a hardcoded narrative from `docs/demo-run-output.md`, + banner-labeled **"Simulated 45-day lifecycle (Sepolia fork)"**, never presented as live data. + This is the view shown during the 4:15–4:45 recording segment to tell the complete story. + +**Why this is the right call:** both views are truthful; neither misrepresents simulated data as +live. It also makes the dashboard a stronger portfolio piece — it demonstrates the full coverage +UX even when the live position history is thin. The same normalized `ReportRow` shape feeds both +paths, so `CoverageReport`/`EventRow` render them identically with no code duplication. + +--- + +## Component architecture decisions + +``` +frontend/ + index.html, vite.config.js, tailwind.config.js, postcss.config.js, vercel.json, .gitignore + public/shield.svg — favicon / brand mark + src/ + main.jsx, index.css — entry + Tailwind + dark theme + App.jsx — URL-param routing, polling, layout; LiveDashboard + DemoDashboard + hooks/ + usePositionEvents.js — fetch + decode all hook events for a positionKey (+ block timestamps) + useBufferHealth.js — poolState + poolConfig.targetBufferSize → health % + usePositionSummary.js — positions mapping + live _accrue simulation to now + components/ + CoverageReport.jsx — the report table (KEY DIFFERENTIATOR); renders ReportRow[] + EventRow.jsx — one normalized row, tone-coded + BufferHealthPanel.jsx — balance / target / skimmed / paid out + health bar + PositionSummary.jsx — entry/earned/payout; reconstructs closed positions from events + ReactiveEvidencePanel.jsx — cross-chain status + verifiable tx links + honest stall note + lib/ + rpc.js — viem client (fallback RPCs), addresses, read helpers, log fetching + events.js — hook + PoolManager ABIs (parseAbi), LimitingFactor enum, decode + format.js — USDC, tick→price, timestamps, addresses, health % + report.js — buildReportRows(): events → normalized ReportRow[] + demoData.js — simulated narrative (?demo=true), clearly labeled +``` + +Key decisions: + +- **Normalized `ReportRow` shape** is the seam between data and UI. Both `buildReportRows()` (live) + and `demoData.js` (simulated) emit it, so the table component is mode-agnostic. +- **`LiveDashboard` / `DemoDashboard` split** in `App.jsx` (rather than conditional hook calls) + respects the Rules of Hooks — only the mounted dashboard runs its data hooks, so demo mode makes + zero RPC calls. +- **Single 30s poll tick** owned by `LiveDashboard`, passed as a `refreshTick` dependency to all + three hooks (instead of three independent intervals). `refreshTick === 0` drives the first-load + spinner; later ticks refresh silently in the background. +- **Topic-2 log filter.** Every position-scoped hook event carries `positionKey` as the second + indexed param (topic2), so a single raw `eth_getLogs` with `topics: [null, null, positionKey]` + fetches the whole lifecycle at once; logs are decoded by topic0 against the hook ABI. +- **Timestamps:** events that carry their own (`AccrualUpdated.timestamp`, + `PositionRegistered.depositTime`, transition events) use it directly; settlement/close events + (no timestamp field) get a batched `getBlock` lookup. +- **`dt == 0` accrual rows are skipped** — the registration baseline `_accrue` and any same-second + touch carry no information and would clutter the statement. +- **Tick → price:** `1.0001^tick × 1e12` (decimal-adjust for token0=ETH 18-dec, token1=USDC 6-dec), + giving USDC/ETH. +- **`extsload` for the live tick:** the deployed hook exposes no current-tick view, so + `usePositionSummary` reads PoolManager Slot0 via `extsload` at `keccak256(abi.encode(poolId, 6))` + (v4 StateLibrary `POOLS_SLOT = 6`), unpacking `sqrtPriceX96(160) | tick(24)`. Used for the + in-range check and live earned-coverage estimate. + +--- + +## RPC challenges encountered + +1. **Position mapping reads return zeros for a settled position.** `afterRemoveLiquidity` clears + `PositionState` (strict CEI), so `positions[poolId][0x62e2…]` reads all-zero / `active=false`. + A naive "read the mapping" Position Summary would show a blank 0.00 position. **Fix:** + `PositionSummary` detects `!active` and **reconstructs** entry notional, range, earned coverage, + payout, IL, and limiting factor from the **event history** (`PositionRegistered` + the + settlement event), showing a "Closed · Settled" state. This is the concrete realization of + Pillar 4 — the report lives in events, not in mutable state. +2. **No view functions on the deployed hook.** Read the public mapping getters directly + (`poolConfig`/`poolState`/`positions`) and derive everything (fee = base + buffer; health = + balance × 100 / target; price = `1.0001^tick`). +3. **Current tick has no getter.** Solved via PoolManager `extsload` (Slot0 unpack) — see above. +4. **Public-RPC robustness.** Used a viem `fallback` transport over three public Sepolia endpoints + (publicnode primary), floored `getLogs` at `DEPLOY_BLOCK = 11_005_000` (the hook's life) instead + of block 0, and chunked the scan in 9,000-block windows to stay within public-endpoint + `eth_getLogs` range limits. +5. **`PoolId` typing in viem.** It is a `bytes32` user-value-type; all ABIs declare `bytes32` and + indexed `poolId`/`positionKey`/`owner` topics line up for decoding. + +### MetaMask console noise (not an app error) + +When a wallet extension (MetaMask) is installed, the browser console may show benign messages such +as injected-provider announcements or `Failed to connect to MetaMask` from the extension's own +inpage script. **RangeGuard makes no wallet connection** — it is read-only over public RPC and uses +no `window.ethereum`. These messages originate from the extension, not the dashboard, and do not +affect data fetching or rendering. They can be ignored (or avoided by viewing in a profile without +wallet extensions). + +--- + +## Verification (live data path, against deployed hook) + +A Node smoke test imported the actual `lib/` modules (DOM-free) and ran them against live Sepolia: + +``` +=== decoded event names === +PositionRegistered, AccrualUpdated, AccrualUpdated, PartialPayout, PositionClosed +=== report rows === +Position Opened | Entry: 228.69 USDC · Range: $1,790–$2,208 => — +Checkpoint | In range ✓ · earned 0.02 USDC => +0.02 USDC +Claim Settled (Partial) | Requested: 1.14 → Paid: 0.02 USDC · COVERAGE_CAP => 0.02 USDC paid +=== buffer === +balance 10,000.81 skimmed 0.82 paidOut 0.02 health 10.00% +=== position (live mapping) active: false entryNotional: 0.00 earned: 0.00 +=== current tick: -200340 +``` + +- `dt = 0` baseline accrual correctly collapsed (2 `AccrualUpdated` → 1 checkpoint row). +- Closed position correctly read zero from the mapping and was reconstructed from events. +- `extsload` current tick (-200340) is in range and confirms the StateLibrary slot math. +- `npm run build` compiles clean (~134 kB gzip JS); `npm run dev` serves HTTP 200 with the correct + title; `/src/main.jsx` transforms. + +--- + +## Two honest notes (live vs. nominal) + +1. **Range displays $1,790–$2,208, not $1,800–$2,200.** The UI converts the *actual* on-chain + tick bounds (`1.0001^tick × 1e12`); tick-spacing rounding lands a few dollars off the nominal + round numbers. The dashboard shows the truth from chain rather than the marketing figure. +2. **Live settlement is PartialPayout / COVERAGE_CAP / 0.02 USDC**, not the idealized + ClaimSettled / IL_CAP / 2.23 USDC. The live position earned only ~0.02 USDC of coverage before + withdrawal, so the **coverage cap** bound the payout (a faithful demonstration of the three-cap + mechanism). The richer IL_CAP story is shown only under `?demo=true`, labeled simulated. + +--- + +## Vercel deployment + +`frontend/vercel.json` adds SPA rewrites: + +```json +{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] } +``` + +Vercel project configuration (no environment variables — all RPC is public Sepolia): + +| Setting | Value | +|---|---| +| Framework preset | **Vite** | +| Root directory | **`frontend`** | +| Build command | **`npm run build`** | +| Output directory | **`dist`** | + +The Vercel project is connected to the GitHub repo's **`main`** branch (auto-deploy). The live URL +**https://range-guard.vercel.app** updates when this work is merged to `main`. + +### Live URL confirmation + +- [ ] _To confirm after merge to `main`:_ https://range-guard.vercel.app loads and shows the live + coverage report (and `?demo=true` shows the simulated narrative). Update this checkbox once the + Vercel deploy is green. + +--- + +## Modes (summary) + +| URL | Mode | Source | +|---|---|---| +| `https://range-guard.vercel.app` | Live | real Sepolia events for the demo positionKey | +| `https://range-guard.vercel.app/?positionKey=0x…` | Live | real Sepolia events for any position | +| `https://range-guard.vercel.app/?demo=true` | Simulated | hardcoded fork narrative, banner-labeled | + +--- + +## Deviations from plan + +- **Added files beyond the prompt's structure:** `postcss.config.js` (required by Tailwind), + `public/shield.svg` (brand mark/favicon), `.gitignore`, `lib/report.js` (the `buildReportRows` + normalizer — pulled out of `events.js` so the ABI module stays focused), and `lib/demoData.js` + (the `?demo=true` data, an explicit consequence of the Option C decision). +- **Live data is sparser than the prompt's example table**, by design (Option C) — the live view is + honest, and the full narrative is the labeled `?demo=true` view. +- **Not yet committed or deployed** — per the user's instruction to write closing docs first and + review before committing. Vercel will not reflect the dashboard until the branch is merged to + `main`. + +--- + +## Carry-ins / follow-ups + +- **Live URL confirmation** is pending the merge to `main` (checkbox above). +- **Spec §11 view functions remain unimplemented** on the deployed hook (mainnet-hardening item); + the dashboard reads public mappings and derives everything client-side, so they are not required + for the frontend — but `getEarnedCoverage` (accrual simulated to `block.timestamp`) would let an + open position render its live coverage without the client-side `extsload`/simulation. +- **Full README** is intentionally a minimal placeholder for now; the complete write-up lands after + the demo recording (with the demo video link). diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..5869bf5f --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,14 @@ +# dependencies +node_modules/ + +# build output +dist/ + +# local env / editor +.env +.env.local +.DS_Store +*.log + +# vercel +.vercel diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..fb76d239 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,34 @@ +# RangeGuard Frontend Dashboard + +React 18 + Vite + Tailwind SPA. Reads live Sepolia events via viem. No backend — all data from public Sepolia RPC calls. + +## Run locally + +```bash +npm install +npm run dev +# → http://localhost:5173 +``` + +## Modes + +- **Live:** `http://localhost:5173` — real on-chain data for the demo positionKey +- **Demo:** `http://localhost:5173/?demo=true` — simulated 45-day lifecycle (clearly labeled as Sepolia fork, not live data) +- **Any position:** `http://localhost:5173/?positionKey=0x...` — view any position in the pool + +## Tech stack + +- React 18 + Vite +- Tailwind CSS +- viem (no ethers.js) +- Public Sepolia RPC — no API key required + +## Deploy + +Vercel configuration: + +- Framework preset: **Vite** +- Root directory: **frontend** +- Build command: **npm run build** +- Output directory: **dist** +- No environment variables needed diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..03048821 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + RangeGuard — Coverage Report + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..5e9ee9f3 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2833 @@ +{ + "name": "rangeguard-dashboard", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rangeguard-dashboard", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.21.54" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "vite": "^5.4.11" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.34", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz", + "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.368", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", + "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/ox": { + "version": "0.14.29", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.29.tgz", + "integrity": "sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.52.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.52.2.tgz", + "integrity": "sha512-HSU12p5aD/kAPZfrlbCUqdiP4P/c6hQ9AhfTS51VbLUQIjkWd1d5EjrCx/SCxZ0zhZVRn4Iv5X5WDqXPG8Ubew==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.29", + "ws": "8.20.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..34b2abb3 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "rangeguard-dashboard", + "private": true, + "version": "0.1.0", + "type": "module", + "description": "RangeGuard coverage report dashboard — live Sepolia on-chain LP impermanent-loss coverage.", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.21.54" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "vite": "^5.4.11" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 00000000..2e7af2b7 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/shield.svg b/frontend/public/shield.svg new file mode 100644 index 00000000..0f1bf987 --- /dev/null +++ b/frontend/public/shield.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 00000000..eb06998f --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,215 @@ +import { useEffect, useMemo, useState } from 'react' +import CoverageReport from './components/CoverageReport.jsx' +import BufferHealthPanel from './components/BufferHealthPanel.jsx' +import PositionSummary from './components/PositionSummary.jsx' +import ReactiveEvidencePanel from './components/ReactiveEvidencePanel.jsx' +import { usePositionEvents } from './hooks/usePositionEvents.js' +import { useBufferHealth } from './hooks/useBufferHealth.js' +import { usePositionSummary } from './hooks/usePositionSummary.js' +import { buildReportRows } from './lib/report.js' +import { healthPct, fmtSecondsAgo, shortHash } from './lib/format.js' +import { ADDRESSES, POOL_ID, DEMO_POSITION_KEY, ETHERSCAN } from './lib/rpc.js' +import { demoRows, demoBuffer, demoSummary } from './lib/demoData.js' + +const POLL_MS = 30_000 + +/* ////////////////////////////////////////////////////////////// + HEADER +////////////////////////////////////////////////////////////// */ + +function Shield() { + return ( + + + + + ) +} + +function Header() { + return ( +
+
+ +
+

RangeGuard

+

Protect your liquidity. Guard your range.

+
+
+
+
+ Uniswap v4 hook · Sepolia ·{' '} + + {shortHash(ADDRESSES.hook)} ↗ + +
+
Pool {shortHash(POOL_ID)}
+
+
+ ) +} + +function LastUpdated({ lastUpdated, now }) { + const seconds = Math.max(0, Math.floor((now - lastUpdated) / 1000)) + return ( +
+ + + + + Live · polling every 30s · updated {fmtSecondsAgo(seconds)} +
+ ) +} + +/* ////////////////////////////////////////////////////////////// + LIVE DASHBOARD +////////////////////////////////////////////////////////////// */ + +function LiveDashboard({ positionKey }) { + const [refreshTick, setRefreshTick] = useState(0) + const [lastUpdated, setLastUpdated] = useState(() => Date.now()) + const [now, setNow] = useState(() => Date.now()) + + // 30s data poll + useEffect(() => { + const id = setInterval(() => { + setRefreshTick((t) => t + 1) + setLastUpdated(Date.now()) + }, POLL_MS) + return () => clearInterval(id) + }, []) + + // 1s clock for the "updated Xs ago" label + useEffect(() => { + const id = setInterval(() => setNow(Date.now()), 1000) + return () => clearInterval(id) + }, []) + + const { events, loading: eventsLoading, error: eventsError } = usePositionEvents(positionKey, refreshTick) + const { data: buffer, loading: bufferLoading } = useBufferHealth(refreshTick) + const { data: summary, loading: summaryLoading } = usePositionSummary(positionKey, refreshTick) + + // Refresh lastUpdated when the first load resolves. + useEffect(() => { + if (!eventsLoading) setLastUpdated(Date.now()) + }, [eventsLoading]) + + const rows = useMemo(() => buildReportRows(events), [events]) + + return ( + <> +
+

Live coverage report

+ + + View demo narrative → + +
+ + {eventsError && ( +
+ RPC error fetching events: {String(eventsError.message || eventsError)}. Retrying on next + poll. +
+ )} + +
+
+ +
+
+ + + +
+
+ + ) +} + +/* ////////////////////////////////////////////////////////////// + DEMO DASHBOARD +////////////////////////////////////////////////////////////// */ + +function DemoDashboard() { + const buffer = { + ...demoBuffer, + healthPct: healthPct(demoBuffer.bufferBalanceStable, demoBuffer.targetBufferSize), + } + + return ( + <> +
+
Simulated 45-day lifecycle (Sepolia fork)
+
+ Illustrative coverage story from RangeGuardDemo.s.sol. This + is NOT live data — remove ?demo=true from the URL for the + real on-chain report. +
+
+ +
+
+ +
+
+ + + +
+
+ + ) +} + +/* ////////////////////////////////////////////////////////////// + APP +////////////////////////////////////////////////////////////// */ + +export default function App() { + const params = useMemo(() => new URLSearchParams(window.location.search), []) + const demo = params.get('demo') === 'true' + const positionKey = params.get('positionKey') || DEMO_POSITION_KEY + + return ( +
+
+ {demo ? : } + + +
+ ) +} diff --git a/frontend/src/components/BufferHealthPanel.jsx b/frontend/src/components/BufferHealthPanel.jsx new file mode 100644 index 00000000..4e02ac74 --- /dev/null +++ b/frontend/src/components/BufferHealthPanel.jsx @@ -0,0 +1,69 @@ +// Buffer health — read from poolState (balance / skimmed / paidOut) + poolConfig.targetBufferSize. +// Health % = bufferBalanceStable * 100 / targetBufferSize. +import { fmtUsdc } from '../lib/format.js' + +function Stat({ label, value, sub }) { + return ( +
+ {label} + + {value} + {sub && {sub}} + +
+ ) +} + +export default function BufferHealthPanel({ data, loading, simulated = false }) { + const pct = data ? data.healthPct : 0 + const barPct = Math.max(0, Math.min(100, pct)) + + return ( +
+
+

Buffer Health

+ IL coverage reserve +
+ + {loading && !data ? ( +
+
+
+
+ ) : ( + <> +
+
+
{fmtUsdc(data?.bufferBalanceStable)}
+
USDC balance
+
+
+
{pct.toFixed(1)}%
+
of target
+
+
+ + {/* Health bar */} +
+
+
+ +
+ + + +
+ +

+ {simulated + ? 'Simulated final state. Buffer retained ~99% of the 10,000 USDC seed — coverage is self-funding.' + : 'Target is the actuarial reserve (100k USDC); a 10k seed reads ~10% and is healthy. Skim > payouts ⇒ self-funding.'} +

+ + )} +
+ ) +} diff --git a/frontend/src/components/CoverageReport.jsx b/frontend/src/components/CoverageReport.jsx new file mode 100644 index 00000000..4cf08583 --- /dev/null +++ b/frontend/src/components/CoverageReport.jsx @@ -0,0 +1,86 @@ +// The coverage report table — RangeGuard's key differentiator (spec §4 Pillar 4). +// Every row maps to a real on-chain event. Accepts already-normalized ReportRow[] so it renders +// live (buildReportRows) and simulated (demoData) histories identically. +import EventRow from './EventRow.jsx' + +function SkeletonRows() { + return Array.from({ length: 4 }).map((_, i) => ( + + +
+ + +
+ + +
+ + +
+ + + )) +} + +export default function CoverageReport({ rows, loading, simulated = false }) { + const firstTs = rows.length ? rows[0].ts : null + + return ( +
+
+
+

Coverage Report

+

+ {simulated + ? 'Day-by-day coverage statement (simulated lifecycle)' + : 'Day-by-day coverage statement — every row is a real on-chain event'} +

+
+ {simulated ? ( + + Simulated + + ) : ( + + Live · on-chain + + )} +
+ +
+ + + + + + + + + + + {loading && rows.length === 0 ? ( + + ) : rows.length === 0 ? ( + + + + ) : ( + rows.map((row) => ) + )} + +
DateEventDetailsCoverage Earned
+ No coverage events found for this position yet. +
+ Once an LP deposits and checkpoints run, the statement populates here. +
+
+
+ +
+ {simulated + ? 'Simulated 45-day lifecycle (Sepolia fork) — illustrates the full coverage UX. Not live data.' + : 'Reconstructed entirely from emitted events — no off-chain bookkeeping. Verify any row on Etherscan.'} +
+
+ ) +} diff --git a/frontend/src/components/EventRow.jsx b/frontend/src/components/EventRow.jsx new file mode 100644 index 00000000..6d31e83c --- /dev/null +++ b/frontend/src/components/EventRow.jsx @@ -0,0 +1,65 @@ +// One row of the coverage report. Renders a normalized ReportRow (live or demo, identical shape). +import { tsToDate, dayLabel } from '../lib/format.js' +import { ETHERSCAN } from '../lib/rpc.js' + +const TONE = { + green: { text: 'text-guard-400', dot: 'bg-guard-400' }, + amber: { text: 'text-amber-400', dot: 'bg-amber-400' }, + red: { text: 'text-rose-400', dot: 'bg-rose-400' }, + neutral: { text: 'text-slate-300', dot: 'bg-slate-500' }, +} + +const TYPE_BADGE = { + open: 'bg-sky-500/10 text-sky-300 ring-sky-500/30', + checkpoint: 'bg-guard-500/10 text-guard-300 ring-guard-500/30', + outOfRange: 'bg-amber-500/10 text-amber-300 ring-amber-500/30', + backInRange: 'bg-guard-500/10 text-guard-300 ring-guard-500/30', + settled: 'bg-violet-500/10 text-violet-300 ring-violet-500/30', +} + +export default function EventRow({ row, firstTs }) { + const tone = TONE[row.tone] ?? TONE.neutral + const badge = TYPE_BADGE[row.type] ?? TYPE_BADGE.settled + + return ( + + {/* Date */} + +
{tsToDate(row.ts)}
+ {firstTs != null && ( +
{dayLabel(row.ts, firstTs)}
+ )} + + + {/* Event */} + + + + {row.label} + + + + {/* Details */} + +
{row.details}
+ {row.txHash && ( + + tx ↗ + + )} + + + {/* Coverage earned */} + + {row.coverage} + + + ) +} diff --git a/frontend/src/components/PositionSummary.jsx b/frontend/src/components/PositionSummary.jsx new file mode 100644 index 00000000..68321894 --- /dev/null +++ b/frontend/src/components/PositionSummary.jsx @@ -0,0 +1,151 @@ +// Position summary — entry notional, range, earned coverage, and (for closed positions) +// the realized payout / IL / limiting factor. +// +// Live: reads usePositionSummary (positions mapping). Because settlement CLEARS the struct, a +// closed position is reconstructed from event history (PositionRegistered + the settlement event). +import { fmtUsdc, fmtRange, tsToDate, shortHash } from '../lib/format.js' +import { LIMITING_FACTOR, LIMITING_FACTOR_LABEL } from '../lib/events.js' +import { demoSummary } from '../lib/demoData.js' + +const SETTLEMENT = ['ClaimSettled', 'PartialPayout', 'NoClaim', 'IneligibleClaim'] + +function buildLiveVM(data, events) { + const reg = events.find((e) => e.eventName === 'PositionRegistered') + const settlement = [...events].reverse().find((e) => SETTLEMENT.includes(e.eventName)) + const lastAccrual = [...events].reverse().find((e) => e.eventName === 'AccrualUpdated') + const active = !!data?.active + + const earned = active + ? data.liveEarned + : (settlement?.args.earnedCoverage ?? lastAccrual?.args.newEarnedTotal ?? null) + + return { + active, + found: !!(active || reg), + entryNotional: active ? data.entryNotionalStable : (reg?.args.entryNotionalStable ?? null), + tickLower: active ? Number(data.tickLower) : reg ? Number(reg.args.tickLower) : null, + tickUpper: active ? Number(data.tickUpper) : reg ? Number(reg.args.tickUpper) : null, + earned, + inRangeNow: data?.inRangeNow ?? false, + depositTime: active ? data.depositTime : (reg?.args.depositTime ?? null), + payout: settlement ? (settlement.args.payout ?? settlement.args.actual ?? null) : null, + ilRaw: settlement?.args.ilRaw ?? null, + factor: + settlement && settlement.args.limitingFactor != null + ? LIMITING_FACTOR[Number(settlement.args.limitingFactor)] + : null, + } +} + +function StatusBadge({ active, inRangeNow }) { + let cls = 'bg-slate-500/15 text-slate-300 ring-slate-500/30' + let label = 'Closed · Settled' + if (active && inRangeNow) { + cls = 'bg-guard-500/15 text-guard-300 ring-guard-500/40' + label = 'Active · In range' + } else if (active && !inRangeNow) { + cls = 'bg-amber-500/15 text-amber-300 ring-amber-500/40' + label = 'Active · Out of range' + } + return ( + {label} + ) +} + +function Row({ label, value, accent = false }) { + return ( +
+ {label} + {value} +
+ ) +} + +export default function PositionSummary({ data, events = [], loading, simulated = false, positionKey }) { + let vm + if (simulated) { + vm = { + active: false, + found: true, + entryNotional: demoSummary.entryNotionalStable, + tickLower: demoSummary.tickLower, + tickUpper: demoSummary.tickUpper, + earned: demoSummary.earnedCoverageStable, + inRangeNow: false, + depositTime: demoSummary.depositTime, + payout: demoSummary.payout, + ilRaw: demoSummary.ilRaw, + factor: demoSummary.limitingFactor, + } + } else { + vm = buildLiveVM(data, events) + } + + return ( +
+
+

Position Summary

+ {(vm.found || simulated) && } +
+ + {positionKey && ( +
{shortHash(positionKey, 8, 6)}
+ )} + + {loading && !data && !simulated ? ( +
+
+
+
+
+ ) : !vm.found ? ( +
+ No position found for this key. +
Check the positionKey or try the demo view.
+
+ ) : ( + <> +
+
+ {vm.earned != null ? fmtUsdc(vm.earned) : '—'} + USDC +
+
+ {vm.active ? 'Earned coverage (live estimate)' : 'Earned coverage (final)'} +
+
+ +
+ + + + {!vm.active && vm.ilRaw != null && ( + + )} + {!vm.active && vm.payout != null && ( + + )} + {!vm.active && vm.factor && ( + + )} +
+ + {!vm.active && vm.factor && LIMITING_FACTOR_LABEL[vm.factor] && ( +

+ {LIMITING_FACTOR_LABEL[vm.factor]} was the binding constraint on this payout. +

+ )} + {vm.active && ( +

+ Live estimate accrues only while in range; final payout is computed at withdrawal + against the three caps. +

+ )} + + )} +
+ ) +} diff --git a/frontend/src/components/ReactiveEvidencePanel.jsx b/frontend/src/components/ReactiveEvidencePanel.jsx new file mode 100644 index 00000000..72e8bbc8 --- /dev/null +++ b/frontend/src/components/ReactiveEvidencePanel.jsx @@ -0,0 +1,104 @@ +// Cross-chain automation status. Honest per docs/reactive-evidence.md: the reactive contract on +// Lasna provably subscribed, tracked, and detected range transitions (verified via on-chain state +// reads + rGas), and the callback *landing* on Sepolia was blocked by a transient testnet +// observation stall — infra, not contracts. We show exactly that, with verifiable tx links. +import { ADDRESSES, ETHERSCAN, REACTSCAN } from '../lib/rpc.js' +import { shortHash } from '../lib/format.js' + +const SEPOLIA_PROOFS = [ + { + label: 'LP deposit → PositionRegistered', + detail: 'Reactive tracked the position (activeKeys 0→1 on Lasna)', + tx: '0x69e31cbe8305b9f54a5ba9afac6ba8002202b70a17af69ed4b530fae7c2d9691', + }, + { + label: 'In-range swap → TickUpdated + BufferFunded', + detail: 'Buffer skim on a real trade', + tx: '0x57083e0d3defe89e99d3e3e43936e7416723483238b41a9279902f1a24d32421', + }, + { + label: 'Swap crosses out → TickUpdated', + detail: 'Reactive flipped lastKnownInRange true→false, dispatched callback (rGas charged)', + tx: '0x775a66d9ea842b3f8d1c3a712def4dcfa160435b7ca16c0a13ffdd36e630e25a', + }, + { + label: 'Swap crosses back → TickUpdated', + detail: 'Reactive flipped lastKnownInRange false→true, dispatched callback (rGas charged)', + tx: '0x3c385b2c6060edcfa630d59ac15c0519da723cd34f4317c5eeda1c3acd63380a', + }, + { + label: 'Full withdrawal → PartialPayout + PositionClosed', + detail: 'Three-cap settlement (COVERAGE_CAP) + CEI payout', + tx: '0x3dbc8b6630bbde937f8b6e41641c69e2ff62c6bbad1efd7fa508f2ebf513b515', + }, +] + +function Link({ href, children }) { + return ( + + {children} ↗ + + ) +} + +export default function ReactiveEvidencePanel() { + return ( +
+
+

Reactive Network Automation

+ Sepolia ⇄ Lasna +
+

+ Coverage accrual is lazy and range-gated; the hook can't watch itself. A contract on the + Reactive Lasna ReactVM subscribes to the hook's events and dispatches accrual callbacks + cross-chain — no keeper. +

+ + {/* Topology */} +
+
+
Hook · Sepolia
+ {shortHash(ADDRESSES.hook)} +
+
+
Reactive · Lasna
+ + {shortHash(ADDRESSES.reactive)} + +
+
+ + {/* Proven round-trip steps */} +
    + {SEPOLIA_PROOFS.map((p) => ( +
  • + +
    +
    {p.label}
    +
    {p.detail}
    + {shortHash(p.tx)} +
    +
  • + ))} +
+ + {/* Honest stall note */} +
+
⏳ Known testnet limitation
+

+ Detection + dispatch are proven on Lasna (state flips + rGas). The final callback{' '} + landing on Sepolia was blocked by a transient + Lasna→Sepolia log-observation stall (after block ~11005952) — infra, not the contracts. + Two Omni-fork pitfalls were found and fixed along the way: the obsolete{' '} + vmOnly check (→ onlySystem) and the + Callback-Proxy reserve funding model (→ depositTo). +

+
+
+ ) +} diff --git a/frontend/src/hooks/useBufferHealth.js b/frontend/src/hooks/useBufferHealth.js new file mode 100644 index 00000000..1f710c4b --- /dev/null +++ b/frontend/src/hooks/useBufferHealth.js @@ -0,0 +1,41 @@ +// Read the pool's buffer accounting from the public poolState mapping (+ target from poolConfig). +// No view functions exist on the deployed hook — we read the mappings directly. +import { useEffect, useState } from 'react' +import { readPoolState, readPoolConfig } from '../lib/rpc.js' +import { healthPct } from '../lib/format.js' + +export function useBufferHealth(refreshTick) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let cancelled = false + if (refreshTick === 0) setLoading(true) + + ;(async () => { + try { + const [state, config] = await Promise.all([readPoolState(), readPoolConfig()]) + if (cancelled) return + setData({ + bufferBalanceStable: state.bufferBalanceStable, + totalSkimmedStable: state.totalSkimmedStable, + totalPaidOutStable: state.totalPaidOutStable, + targetBufferSize: config.targetBufferSize, + healthPct: healthPct(state.bufferBalanceStable, config.targetBufferSize), + }) + setError(null) + } catch (err) { + if (!cancelled) setError(err) + } finally { + if (!cancelled) setLoading(false) + } + })() + + return () => { + cancelled = true + } + }, [refreshTick]) + + return { data, loading, error } +} diff --git a/frontend/src/hooks/usePositionEvents.js b/frontend/src/hooks/usePositionEvents.js new file mode 100644 index 00000000..234ec127 --- /dev/null +++ b/frontend/src/hooks/usePositionEvents.js @@ -0,0 +1,51 @@ +// Fetch + decode every hook event for a given positionKey, with resolved timestamps. +// Re-runs whenever `refreshTick` changes (App polls every 30s). +import { useEffect, useState } from 'react' +import { fetchPositionLogs, fetchBlockTimestamps } from '../lib/rpc.js' +import { decodeHookLog } from '../lib/events.js' + +export function usePositionEvents(positionKey, refreshTick) { + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let cancelled = false + // Only show the full-screen spinner on the very first load, not on background polls. + if (refreshTick === 0) setLoading(true) + + ;(async () => { + try { + const raw = await fetchPositionLogs(positionKey) + const decoded = raw.map(decodeHookLog).filter(Boolean) + + // Events without their own timestamp field need a block lookup. + const needBlock = decoded + .filter((e) => e.args.timestamp == null && e.args.depositTime == null) + .map((e) => e.blockNumber) + const blockTs = needBlock.length ? await fetchBlockTimestamps(needBlock) : {} + + const withTs = decoded.map((e) => { + const own = e.args.timestamp ?? e.args.depositTime + const ts = own != null ? Number(own) : blockTs[e.blockNumber.toString()] + return { ...e, timestamp: ts } + }) + + if (!cancelled) { + setEvents(withTs) + setError(null) + } + } catch (err) { + if (!cancelled) setError(err) + } finally { + if (!cancelled) setLoading(false) + } + })() + + return () => { + cancelled = true + } + }, [positionKey, refreshTick]) + + return { events, loading, error } +} diff --git a/frontend/src/hooks/usePositionSummary.js b/frontend/src/hooks/usePositionSummary.js new file mode 100644 index 00000000..00e4f721 --- /dev/null +++ b/frontend/src/hooks/usePositionSummary.js @@ -0,0 +1,71 @@ +// Read the live PositionState from the public positions mapping, and (for open positions) +// simulate earned coverage to block.timestamp the same way the hook's _accrue does: +// delta = entryNotional * coverageApr * dt / (1e18 * secondsPerYear) [in-range only] +// +// Settlement CLEARS the struct, so a closed position reads active=false / all-zero — the caller +// (PositionSummary component) then reconstructs entry + payout from event history instead. +import { useEffect, useState } from 'react' +import { readPosition, readPoolConfig, readCurrentTick } from '../lib/rpc.js' + +const ONE_E18 = 10n ** 18n + +export function usePositionSummary(positionKey, refreshTick) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let cancelled = false + if (refreshTick === 0) setLoading(true) + + ;(async () => { + try { + const [pos, config, slot] = await Promise.all([ + readPosition(positionKey), + readPoolConfig(), + readCurrentTick().catch(() => null), + ]) + if (cancelled) return + + const currentTick = slot ? slot.tick : null + const inRangeNow = + pos.active && currentTick != null + ? Number(pos.tickLower) <= currentTick && currentTick < Number(pos.tickUpper) + : false + + // Live accrual estimate to "now" for an open position. + let liveEarned = pos.earnedCoverageStable + if (pos.active && inRangeNow) { + const nowSec = BigInt(Math.floor(Date.now() / 1000)) + const dt = nowSec - BigInt(pos.lastAccrualTime) + if (dt > 0n) { + const delta = + (pos.entryNotionalStable * config.coverageApr * dt) / (ONE_E18 * config.secondsPerYear) + liveEarned = pos.earnedCoverageStable + delta + } + } + + setData({ + ...pos, + currentTick, + inRangeNow, + liveEarned, + coverageApr: config.coverageApr, + secondsPerYear: config.secondsPerYear, + minHoldSeconds: config.minHoldSeconds, + }) + setError(null) + } catch (err) { + if (!cancelled) setError(err) + } finally { + if (!cancelled) setLoading(false) + } + })() + + return () => { + cancelled = true + } + }, [positionKey, refreshTick]) + + return { data, loading, error } +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 00000000..5cd007c8 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,34 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + color-scheme: dark; +} + +html, +body, +#root { + height: 100%; +} + +body { + margin: 0; + background: radial-gradient(1200px 600px at 50% -10%, #11202a 0%, #0a0e14 55%); + color: #e5e9f0; + -webkit-font-smoothing: antialiased; + font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; +} + +/* Thin scrollbars for the dark theme */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} +::-webkit-scrollbar-thumb { + background: #2b3445; + border-radius: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} diff --git a/frontend/src/lib/demoData.js b/frontend/src/lib/demoData.js new file mode 100644 index 00000000..f145bcb0 --- /dev/null +++ b/frontend/src/lib/demoData.js @@ -0,0 +1,98 @@ +// Simulated "Demo Narrative" data — rendered ONLY under ?demo=true and ALWAYS labeled +// "Simulated 45-day lifecycle (Sepolia fork)". This is the fork output from +// docs/demo-run-output.md (RangeGuardDemo.s.sol), used for the recorded coverage-report +// segment (4:15–4:45) so the full IL-coverage story can be shown end-to-end. It is never +// presented as live on-chain data. + +// Day 0 anchored so Day 45 lands on ~Jun 06, 2026 (the recording date). +const BASE = Math.floor(Date.UTC(2026, 3, 22) / 1000) // Apr 22, 2026 +const day = (n) => BASE + n * 86400 + +// ReportRow[] — same shape buildReportRows() produces, so renders it unchanged. +export const demoRows = [ + { + key: 'demo-0', + type: 'open', + label: 'Position Opened', + ts: day(0), + details: 'Entry: 228.38 USDC · Range: $1,800–$2,200', + coverage: '—', + tone: 'neutral', + }, + { + key: 'demo-15', + type: 'checkpoint', + label: 'Checkpoint', + ts: day(15), + details: 'In range ✓ · earned 4.69 USDC', + coverage: '+4.69 USDC', + tone: 'green', + }, + { + key: 'demo-18', + type: 'outOfRange', + label: 'Out of Range ⚠', + ts: day(18), + details: 'Reactive Network detected tick crossing', + coverage: 'Paused at 4.69 USDC', + tone: 'amber', + }, + { + key: 'demo-20', + type: 'checkpoint', + label: 'Checkpoint', + ts: day(20), + details: 'Out of range ✗ · earned 4.69 USDC', + coverage: '+0.00 USDC', + tone: 'red', + }, + { + key: 'demo-22', + type: 'backInRange', + label: 'Back In Range ✓', + ts: day(22), + details: 'Reactive Network detected recovery', + coverage: 'Resumed at 4.69 USDC', + tone: 'green', + }, + { + key: 'demo-43', + type: 'checkpoint', + label: 'Checkpoint', + ts: day(43), + details: 'In range ✓ · earned 12.51 USDC', + coverage: '+7.82 USDC', + tone: 'green', + }, + { + key: 'demo-45', + type: 'settled', + label: 'Claim Settled', + ts: day(45), + details: 'IL: 4.47 USDC · Earned: 12.51 USDC · IL_CAP', + coverage: '2.23 USDC paid', + tone: 'green', + }, +] + +// Final-state buffer (docs/demo-run-output.md [Final Summary]). Values in 1e6 stable units. +export const demoBuffer = { + bufferBalanceStable: 9_999_090_000n, // 9,999.09 USDC + totalSkimmedStable: 1_340_000n, // 1.34 USDC + totalPaidOutStable: 2_250_000n, // 2.25 USDC + targetBufferSize: 100_000_000_000n, // 100,000.00 USDC +} + +export const demoSummary = { + active: false, // lifecycle complete (settled) + closed: true, + entryNotionalStable: 228_380_000n, // 228.38 USDC + earnedCoverageStable: 12_510_000n, // 12.51 USDC + payout: 2_230_000n, // 2.23 USDC + ilRaw: 4_470_000n, // 4.47 USDC + limitingFactor: 'IL_CAP', + tickLower: -201420, + tickUpper: -199320, + depositTime: day(0), + settledTime: day(45), +} diff --git a/frontend/src/lib/events.js b/frontend/src/lib/events.js new file mode 100644 index 00000000..48c7ea31 --- /dev/null +++ b/frontend/src/lib/events.js @@ -0,0 +1,58 @@ +// RangeGuardHook event + read ABIs, and the LimitingFactor enum. +// +// Every coverage-report row maps to one of these events (spec §4 / §10). All position-scoped +// events carry `positionKey` as the second indexed param (topic2), which is how rpc.js filters them. +import { parseAbi, decodeEventLog } from 'viem' + +// Solidity: enum LimitingFactor { NONE, IL_CAP, COVERAGE_CAP, BUFFER_CAP } +export const LIMITING_FACTOR = ['NONE', 'IL_CAP', 'COVERAGE_CAP', 'BUFFER_CAP'] + +export const LIMITING_FACTOR_LABEL = { + NONE: 'No cap (no IL)', + IL_CAP: 'IL cap (50% of loss)', + COVERAGE_CAP: 'Coverage cap (earned coverage)', + BUFFER_CAP: 'Buffer cap (10% of buffer)', +} + +// On the wire the enum is a uint8; declare it as such for decoding. +export const HOOK_ABI = parseAbi([ + // ---- events (coverage report) ---- + 'event PositionRegistered(bytes32 indexed poolId, bytes32 indexed positionKey, address indexed owner, int24 tickLower, int24 tickUpper, uint128 entryAmt0, uint128 entryAmt1, uint256 entryNotionalStable, int24 entryTick, uint32 depositTime, uint256 coverageApr, uint256 secondsPerYear)', + 'event AccrualUpdated(bytes32 indexed poolId, bytes32 indexed positionKey, uint256 dt, uint256 delta, uint256 newEarnedTotal, bool isInRange, uint256 timestamp)', + 'event PositionOutOfRange(bytes32 indexed poolId, bytes32 indexed positionKey, int24 tickLower, int24 tickUpper, int24 currentTick, uint256 earnedCoverageStable, uint256 timestamp)', + 'event PositionBackInRange(bytes32 indexed poolId, bytes32 indexed positionKey, int24 tickLower, int24 tickUpper, int24 currentTick, uint256 earnedCoverageStable, uint256 timestamp)', + 'event Checkpointed(bytes32 indexed poolId, bytes32 indexed positionKey, uint256 timestamp)', + 'event ClaimSettled(bytes32 indexed poolId, bytes32 indexed positionKey, address indexed owner, int24 tickLower, int24 tickUpper, uint256 ilRaw, uint256 earnedCoverage, uint256 payout, uint8 limitingFactor)', + 'event PartialPayout(bytes32 indexed poolId, bytes32 indexed positionKey, address indexed owner, int24 tickLower, int24 tickUpper, uint256 requested, uint256 actual, uint8 limitingFactor)', + 'event NoClaim(bytes32 indexed poolId, bytes32 indexed positionKey, address indexed owner, int24 tickLower, int24 tickUpper, uint256 vHodl, uint256 vActual)', + 'event IneligibleClaim(bytes32 indexed poolId, bytes32 indexed positionKey, address indexed owner, int24 tickLower, int24 tickUpper, bytes32 reason)', + 'event PositionClosed(bytes32 indexed poolId, bytes32 indexed positionKey, address owner)', + // ---- public-mapping getters ---- + 'function poolState(bytes32) view returns (uint256 bufferBalanceStable, uint256 totalSkimmedStable, uint256 totalPaidOutStable)', + 'function poolConfig(bytes32) view returns (uint24 baseLpFeeBps, uint24 bufferBps, uint256 coverageApr, uint256 secondsPerYear, uint32 minHoldSeconds, uint16 maxPayoutPctOfIl, uint16 maxPayoutPctOfBuffer, uint256 maxAccruedCoverageMultiple, uint256 targetBufferSize, uint32 minCheckpointInterval, address admin)', + 'function positions(bytes32, bytes32) view returns (uint128 entryAmt0, uint128 entryAmt1, int24 entryTick, int24 tickLower, int24 tickUpper, uint32 depositTime, uint32 lastAccrualTime, bool active, uint256 entryNotionalStable, uint256 earnedCoverageStable, uint128 liquidity)', +]) + +export const POOL_MANAGER_ABI = parseAbi(['function extsload(bytes32 slot) view returns (bytes32)']) + +// Decode one raw log against the hook ABI. Returns null for anything that does not match +// (e.g. an event with this positionKey shape that we do not render). +export function decodeHookLog(log) { + try { + const { eventName, args } = decodeEventLog({ + abi: HOOK_ABI, + data: log.data, + topics: log.topics, + strict: false, + }) + return { + eventName, + args, + txHash: log.transactionHash, + blockNumber: BigInt(log.blockNumber), + logIndex: Number(log.logIndex), + } + } catch { + return null + } +} diff --git a/frontend/src/lib/format.js b/frontend/src/lib/format.js new file mode 100644 index 00000000..250f6e7e --- /dev/null +++ b/frontend/src/lib/format.js @@ -0,0 +1,89 @@ +// Display formatting: USDC amounts, tick→price, timestamps, addresses. + +export const USDC_DECIMALS = 6 +const USDC_DIVISOR = 1_000_000 + +// Format a stable (USDC, 6-dec) amount as a 2-decimal string with thousands separators. +// Accepts bigint | number | string. `sign: true` prefixes a + for non-negative values. +export function fmtUsdc(v, { sign = false } = {}) { + if (v === null || v === undefined) return '—' + const bn = typeof v === 'bigint' ? v : BigInt(v) + const num = Number(bn) / USDC_DIVISOR + const body = Math.abs(num).toLocaleString('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }) + const prefix = num < 0 ? '-' : sign ? '+' : '' + return `${prefix}${body}` +} + +// Tick -> price in USDC per ETH. token0 = ETH (18 dec), token1 = USDC (6 dec): +// raw price = 1.0001^tick (token1/token0 in raw units); decimal-adjust by 10^(18-6) = 1e12. +export function tickToPrice(tick) { + return Math.pow(1.0001, Number(tick)) * 1e12 +} + +export function fmtPrice(price) { + if (!isFinite(price)) return '—' + return '$' + price.toLocaleString('en-US', { maximumFractionDigits: 0 }) +} + +// Render a [tickLower, tickUpper] band as a price range string. +export function fmtRange(tickLower, tickUpper) { + return `${fmtPrice(tickToPrice(tickLower))}–${fmtPrice(tickToPrice(tickUpper))}` +} + +// Unix seconds -> "Jun 06, 2026" +export function tsToDate(ts) { + if (ts === null || ts === undefined) return '—' + return new Date(Number(ts) * 1000).toLocaleDateString('en-US', { + month: 'short', + day: '2-digit', + year: 'numeric', + }) +} + +// Unix seconds -> "Jun 06, 2026 · 14:32 UTC" +export function tsToDateTime(ts) { + if (ts === null || ts === undefined) return '—' + const d = new Date(Number(ts) * 1000) + const date = d.toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' }) + const time = d.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZone: 'UTC', + }) + return `${date} · ${time} UTC` +} + +// Relative-day label from the first event ("Day 0", "Day 15"). +export function dayLabel(ts, firstTs) { + if (ts == null || firstTs == null) return '' + const days = Math.floor((Number(ts) - Number(firstTs)) / 86400) + return `Day ${days}` +} + +export function shortHash(h, lead = 6, tail = 4) { + if (!h) return '' + return `${h.slice(0, 2 + lead)}…${h.slice(-tail)}` +} + +export function fmtSecondsAgo(seconds) { + if (seconds < 5) return 'just now' + if (seconds < 60) return `${seconds}s ago` + const m = Math.floor(seconds / 60) + return `${m}m ${seconds % 60}s ago` +} + +// coverageApr is 1e18 fixed-point; render as a percent. +export function fmtApr(apr) { + if (apr == null) return '—' + return `${(Number(apr) / 1e16).toFixed(0)}%` +} + +// Health percent of target = balance * 100 / target (both 1e6 stable units). +export function healthPct(balance, target) { + if (!target || BigInt(target) === 0n) return 0 + return (Number(balance) / Number(target)) * 100 +} diff --git a/frontend/src/lib/report.js b/frontend/src/lib/report.js new file mode 100644 index 00000000..305a884b Binary files /dev/null and b/frontend/src/lib/report.js differ diff --git a/frontend/src/lib/rpc.js b/frontend/src/lib/rpc.js new file mode 100644 index 00000000..3018e5a0 --- /dev/null +++ b/frontend/src/lib/rpc.js @@ -0,0 +1,195 @@ +// Viem client setup + on-chain read helpers for the live RangeGuard deployment on Sepolia. +// +// No backend, no API keys — everything is read from public Sepolia RPC endpoints. +import { createPublicClient, fallback, http, keccak256, encodeAbiParameters } from 'viem' +import { sepolia } from 'viem/chains' +import { HOOK_ABI, POOL_MANAGER_ABI } from './events.js' + +/* ////////////////////////////////////////////////////////////// + LIVE DEPLOYMENT (Sepolia) +////////////////////////////////////////////////////////////// */ + +export const ADDRESSES = { + hook: '0xFead6CeaD66f86101f0D0fc5A9B97888FA54a7C0', + poolManager: '0xE03A1074c86CFeDd5C142C4F04F1a1536e203543', + mockUsdc: '0x04feCef5110c5e52794fdA3D935BC2Cc0ee428CA', + demoLpRouter: '0xEA30a770E6B3C3d30074908Af13b930d6d451FEa', + callbackProxy: '0xc9f36411C9897e7F959D99ffca2a0Ba7ee0D7bDA', + reactive: '0x5eb9c8C021fB3474aA1f2d9EE5f53f6DbA5fFee1', // Reactive Lasna + deployer: '0x193D1F3E085efc80e1027891FaA770E81ECC4A1d', +} + +export const POOL_ID = '0x3e2f931d495879c5ff87e338192def0f0b824bdf07e9f9c16b02cdba34aaa61a' + +// Canonical demo position used by the recorded walkthrough. Overridable via ?positionKey=. +export const DEMO_POSITION_KEY = '0x62e2311b3a51692f0f8ce68f4cd03882e163b37aa357431ad14a4f5b41462d88' + +// The hook was deployed in Session 12; its earliest events are ~block 11,005,575. +// Floor the log scan here so getLogs never sweeps the whole chain. +export const DEPLOY_BLOCK = 11005000n + +// Explorers +export const ETHERSCAN = 'https://sepolia.etherscan.io' +export const REACTSCAN = 'https://lasna-omni.reactscan.net' + +// Public Sepolia RPCs (fallback in order). publicnode verified working for eth_getLogs + extsload. +const RPC_URLS = [ + 'https://ethereum-sepolia-rpc.publicnode.com', + 'https://1rpc.io/sepolia', + 'https://rpc.sepolia.org', +] + +export const client = createPublicClient({ + chain: sepolia, + transport: fallback(RPC_URLS.map((u) => http(u, { timeout: 15_000 })), { rank: false }), +}) + +/* ////////////////////////////////////////////////////////////// + READ HELPERS +////////////////////////////////////////////////////////////// */ + +// poolState(poolId) -> { bufferBalanceStable, totalSkimmedStable, totalPaidOutStable } (all 1e6) +export async function readPoolState() { + const [bufferBalanceStable, totalSkimmedStable, totalPaidOutStable] = await client.readContract({ + address: ADDRESSES.hook, + abi: HOOK_ABI, + functionName: 'poolState', + args: [POOL_ID], + }) + return { bufferBalanceStable, totalSkimmedStable, totalPaidOutStable } +} + +// poolConfig(poolId) -> immutable per-pool configuration. +export async function readPoolConfig() { + const r = await client.readContract({ + address: ADDRESSES.hook, + abi: HOOK_ABI, + functionName: 'poolConfig', + args: [POOL_ID], + }) + // viem returns named-tuple as array in declaration order. + const [ + baseLpFeeBps, + bufferBps, + coverageApr, + secondsPerYear, + minHoldSeconds, + maxPayoutPctOfIl, + maxPayoutPctOfBuffer, + maxAccruedCoverageMultiple, + targetBufferSize, + minCheckpointInterval, + admin, + ] = r + return { + baseLpFeeBps, + bufferBps, + coverageApr, + secondsPerYear, + minHoldSeconds, + maxPayoutPctOfIl, + maxPayoutPctOfBuffer, + maxAccruedCoverageMultiple, + targetBufferSize, + minCheckpointInterval, + admin, + } +} + +// positions(poolId, positionKey) -> live PositionState. Note: settlement CLEARS this struct, +// so a closed position reads all-zero / active=false. Callers must fall back to event history. +export async function readPosition(positionKey) { + const r = await client.readContract({ + address: ADDRESSES.hook, + abi: HOOK_ABI, + functionName: 'positions', + args: [POOL_ID, positionKey], + }) + const [ + entryAmt0, + entryAmt1, + entryTick, + tickLower, + tickUpper, + depositTime, + lastAccrualTime, + active, + entryNotionalStable, + earnedCoverageStable, + liquidity, + ] = r + return { + entryAmt0, + entryAmt1, + entryTick, + tickLower, + tickUpper, + depositTime, + lastAccrualTime, + active, + entryNotionalStable, + earnedCoverageStable, + liquidity, + } +} + +// Current pool tick, read from the PoolManager via extsload (StateLibrary layout). +// v4 stores pools in mapping at POOLS_SLOT = 6; Slot0 packs sqrtPriceX96(160) | tick(24) | ... +export async function readCurrentTick() { + const POOLS_SLOT = 6n + const stateSlot = keccak256( + encodeAbiParameters([{ type: 'bytes32' }, { type: 'uint256' }], [POOL_ID, POOLS_SLOT]), + ) + const raw = await client.readContract({ + address: ADDRESSES.poolManager, + abi: POOL_MANAGER_ABI, + functionName: 'extsload', + args: [stateSlot], + }) + const word = BigInt(raw) + const sqrtPriceX96 = word & ((1n << 160n) - 1n) + let tick = (word >> 160n) & 0xffffffn + if (tick >= 0x800000n) tick -= 0x1000000n // sign-extend int24 + return { tick: Number(tick), sqrtPriceX96 } +} + +/* ////////////////////////////////////////////////////////////// + EVENT LOG FETCHING +////////////////////////////////////////////////////////////// */ + +// Fetch every hook event whose indexed positionKey (topic2) matches, across DEPLOY_BLOCK..latest, +// chunked to stay within public-RPC block-range limits. Returns raw logs (undecoded). +export async function fetchPositionLogs(positionKey) { + const latest = await client.getBlockNumber() + const step = 9000n + const logs = [] + for (let from = DEPLOY_BLOCK; from <= latest; from += step + 1n) { + const to = from + step > latest ? latest : from + step + // topics: [topic0=any event, topic1=any poolId, topic2=positionKey] + const chunk = await client.request({ + method: 'eth_getLogs', + params: [ + { + address: ADDRESSES.hook, + topics: [null, null, positionKey], + fromBlock: `0x${from.toString(16)}`, + toBlock: `0x${to.toString(16)}`, + }, + ], + }) + logs.push(...chunk) + } + return logs +} + +// Resolve block timestamps for a set of logs (events without their own timestamp field need this). +export async function fetchBlockTimestamps(blockNumbers) { + const unique = [...new Set(blockNumbers.map((b) => BigInt(b)))] + const entries = await Promise.all( + unique.map(async (bn) => { + const block = await client.getBlock({ blockNumber: bn }) + return [bn.toString(), Number(block.timestamp)] + }), + ) + return Object.fromEntries(entries) +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 00000000..54b39dd1 --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 00000000..70a75ef4 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,28 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,jsx}'], + theme: { + extend: { + colors: { + // RangeGuard dark palette + ink: { + 900: '#0a0e14', + 800: '#0f141d', + 700: '#161c28', + 600: '#1f2735', + 500: '#2b3445', + }, + guard: { + // brand accent (teal/green — "guard your range") + 400: '#34d399', + 500: '#10b981', + 600: '#059669', + }, + }, + fontFamily: { + mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'monospace'], + }, + }, + }, + plugins: [], +} diff --git a/frontend/vercel.json b/frontend/vercel.json new file mode 100644 index 00000000..0f32683a --- /dev/null +++ b/frontend/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 00000000..838675bb --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { port: 5173 }, +}) diff --git a/project-status.md b/project-status.md index 889ec60e..2c5a1a84 100644 --- a/project-status.md +++ b/project-status.md @@ -1,5 +1,5 @@ RangeGuard Project Status -Last Updated: 2026-06-05 (Session 12 — reactive-lib-omni migration + ReactVM deployment on Lasna) +Last Updated: 2026-06-07 (Session 14 — frontend dashboard / coverage report) How to use this file The Roadmap is the single source of truth for progress — one checkbox per item. @@ -14,11 +14,27 @@ invariant; correctness before gas. Now -Active target: Frontend dashboard (coverage report rendered from Sepolia events). The demo tooling is -complete (Session 13): 14 Sepolia fork tests + RangeGuardDemo.s.sol (Option A) + LiveEndToEnd/ -LiveWithdraw (Option B) + DemoLPRouter. The frontend should query the live demo positionKey -0x62e2311b3a51692f0f8ce68f4cd03882e163b37aa357431ad14a4f5b41462d88 (hook 0xFead…a7C0) and render -PositionRegistered → AccrualUpdated → settlement from on-chain events. +Active target: Recorded 5-minute demo (spec §15). All protocol code, deployment, demo tooling, and +the frontend dashboard are complete. Remaining work is the recording itself + the full README/demo +video write-up afterward. + +Just completed (Session 14): Frontend dashboard — coverage report (frontend/, React 18 + Vite + +Tailwind + viem, no backend). Renders the LP coverage report from LIVE Sepolia on-chain events; +verified end-to-end against hook 0xFead…a7C0. Two modes (Option C): +- LIVE (default, or ?positionKey=0x…): real on-chain events for any position — honest + verifiable. + Settlement CLEARS positions[…], so a closed position reads zero from the mapping; the dashboard + reconstructs entry/earned/payout from EVENT HISTORY (PositionRegistered + settlement). Buffer + health from poolState; current tick via PoolManager extsload (Slot0). Polls every 30s. +- DEMO (?demo=true): hardcoded fork narrative from docs/demo-run-output.md, banner-labeled + "Simulated 45-day lifecycle (Sepolia fork)" — never presented as live. Used for the recording's + 4:15–4:45 coverage-report segment. +Live data verified: PositionRegistered (228.69 USDC, range $1,790–$2,208) → Checkpoint +0.02 USDC → +PartialPayout/COVERAGE_CAP 0.02 USDC; buffer 10,000.81 / health 10.00%. Two honest notes: live range +reads $1,790–$2,208 (tick rounding, not the nominal $1,800–$2,200); live settlement is +PartialPayout/COVERAGE_CAP, not ClaimSettled/IL_CAP (the IL_CAP story is the ?demo=true view). +Deployed via Vercel (auto-deploy from main): https://range-guard.vercel.app — Framework Vite, root +frontend, build npm run build, output dist, no env vars. +-> docs/session-14-frontend.md Just completed (Session 13): demo scripts + live end-to-end, and TWO Reactive Omni-fork blockers found + fixed: @@ -227,7 +243,10 @@ Reactive contract ✅ (complete — see Completed section / session-10 doc) / LiveWithdraw.s.sol (Option B live broadcast) + DemoLPRouter.sol + 14 Sepolia fork tests. Live lifecycle broadcast on Sepolia; reactive react() proven (after the vmOnly→onlySystem fix + redeploy). See docs/session-13-demo-script.md, docs/reactive-evidence.md. -- [ ] Frontend dashboard (coverage report rendered from Sepolia events) ← NOW +- [x] Frontend dashboard (Session 14: coverage report from LIVE Sepolia events; React+Vite+viem at + frontend/; live mode + ?demo=true simulated mode; Vercel https://range-guard.vercel.app — + see docs/session-14-frontend.md) +- [ ] Recorded 5-minute demo (spec §15) + full README / demo video write-up ← NOW Phase 4: Protocol Invariants (cross-cutting)